Production Orders
When you’re ready to make it real—send designs to production, configure size runs, and track fulfillment.
Order Lifecycle
Spec Review
Order submitted, awaiting quality check
Queued
Approved and waiting for print queue
Printing
Design being printed on garment
Finishing
Quality inspection, packaging
Ready
Shipped or ready for pickup
Creating Orders
From Gallery or Collections:
- Select design(s) to produce
- Click “Send to Production”
- Choose garment blank
- Select sizes and quantities
- Proceed to Stripe checkout
- Order created on payment success
Database Schema
-- Production orders
create table public.production_orders (
id uuid primary key default gen_random_uuid(),
user_id uuid references auth.users(id) on delete cascade,
project_id uuid references public.projects(id) on delete set null,
reference text unique, -- Human-readable order number
status text default 'draft', -- draft, submitted, approved, in_production, fulfilled, canceled
notes text,
metadata jsonb default '{}',
created_at timestamptz default now(),
updated_at timestamptz default now()
);
-- Line items (sizes/quantities)
create table public.production_line_items (
id uuid primary key default gen_random_uuid(),
order_id uuid references public.production_orders(id) on delete cascade,
size text not null,
quantity integer not null default 1,
technique text default 'dtg', -- dtg, screen, embroidery
metadata jsonb default '{}',
created_at timestamptz default now()
);
-- Fulfillment events (status history)
create table public.fulfillment_events (
id uuid primary key default gen_random_uuid(),
order_id uuid references public.production_orders(id) on delete cascade,
status text not null,
message text,
metadata jsonb default '{}',
occurred_at timestamptz default now()
);
Status Flow
The order status follows a defined progression:
// lib/production/orders.ts
const STATUS_FLOW: ProductionStatus[] = [
"Spec Review",
"Queued",
"Printing",
"Finishing",
"Ready"
];
const STATUS_MAP: Record<ProductionOrderState, ProductionStatus> = {
draft: "Spec Review",
submitted: "Queued",
approved: "Printing",
in_production: "Finishing",
fulfilled: "Ready",
canceled: "Spec Review",
};
Order Summary Type
interface ProductionOrderSummary {
id: string;
reference: string | null;
notes: string | null;
status: ProductionStatus;
orderStatus: ProductionOrderState;
createdAt: string;
updatedAt: string;
prompt: string | null;
priority: "Rush" | "Standard" | "Hold";
etaDays: number;
imageUrl: string | null;
garment: Garment | null;
metadata: Record<string, unknown>;
lineItems: LineItem[];
events: FulfillmentEvent[];
}
Priority Levels
Rush
Expedited processing, 1-2 day ETA
Standard
Normal queue, 3-5 day ETA
Hold
Paused until manually released
Realtime Tracking
Production updates stream via Supabase Realtime:
// hooks/useProductionRealtime.ts
export function useProductionRealtime(orderId: string) {
const [order, setOrder] = useState<ProductionOrderSummary | null>(null);
useEffect(() => {
const supabase = createClient();
const channel = supabase
.channel(`order:${orderId}`)
.on('postgres_changes', {
event: 'UPDATE',
schema: 'public',
table: 'production_orders',
filter: `id=eq.${orderId}`,
}, (payload) => {
// Refetch full order with relations
fetchOrder(orderId).then(setOrder);
})
.on('postgres_changes', {
event: 'INSERT',
schema: 'public',
table: 'fulfillment_events',
filter: `order_id=eq.${orderId}`,
}, (payload) => {
// Add new event to timeline
setOrder(prev => prev ? {
...prev,
events: [...prev.events, payload.new as FulfillmentEvent],
} : null);
})
.subscribe();
return () => { supabase.removeChannel(channel); };
}, [orderId]);
return order;
}
Order Detail Page
/orders/[id] displays:
- Design preview image
- Order reference number
- Current status badge
- Priority indicator
- ETA countdown
- Line items (sizes/quantities)
- Event timeline
- Garment details
Route Map Visualization
The production page includes a visual route map showing order progress:
// lib/productionRouteStops.ts
export const routeStops = [
{ id: "spec-review", label: "Spec Review", icon: ClipboardCheck },
{ id: "queued", label: "Queued", icon: ListOrdered },
{ id: "printing", label: "Printing", icon: Printer },
{ id: "finishing", label: "Finishing", icon: Sparkles },
{ id: "ready", label: "Ready", icon: Package },
];
API Endpoints
POST /api/production
Create new production order:
// Request
{
designUrl: string;
garmentId: string;
sizes: { size: string; quantity: number }[];
priority?: "rush" | "standard" | "hold";
notes?: string;
}
// Response: Stripe checkout session URL
{ checkoutUrl: string }
GET /api/production/[id]
Fetch order details:
// Response
{
order: ProductionOrderSummary;
}
PATCH /api/production/[id]
Update order (ops only):
// Request
{
status?: ProductionOrderState;
priority?: string;
notes?: string;
}
Stripe Integration
Production orders use Stripe checkout:
// Create checkout session
const session = await stripe.checkout.sessions.create({
mode: 'payment',
line_items: lineItems.map(item => ({
price_data: {
currency: 'usd',
product_data: {
name: `${garment.title} - ${item.size}`,
images: [designUrl],
},
unit_amount: Math.round(garment.price * 100),
},
quantity: item.quantity,
})),
metadata: {
orderId: order.id,
userId: userId,
},
success_url: `${origin}/order-success?order=${order.id}`,
cancel_url: `${origin}/orders/${order.id}`,
});
Webhook Handling
Stripe webhooks update order status:
// POST /api/stripe/webhook
case 'checkout.session.completed': {
const orderId = session.metadata?.orderId;
if (orderId) {
await supabase
.from('production_orders')
.update({ status: 'submitted' })
.eq('id', orderId);
// Create fulfillment event
await supabase
.from('fulfillment_events')
.insert({
order_id: orderId,
status: 'Queued',
message: 'Payment confirmed, order queued for production',
});
}
break;
}
Ops Dashboard
Operators access /ops/orders to manage all production:
- View all orders across users
- Filter by status, priority, date
- Update order status
- Add notes and messages
- Bulk status updates
Ops access requires the is_operator flag set in user_profiles table.