Skip to main content

Production Orders

When you’re ready to make it real—send designs to production, configure size runs, and track fulfillment.

Order Lifecycle

1

Spec Review

Order submitted, awaiting quality check
2

Queued

Approved and waiting for print queue
3

Printing

Design being printed on garment
4

Finishing

Quality inspection, packaging
5

Ready

Shipped or ready for pickup

Creating Orders

From Gallery or Collections:
  1. Select design(s) to produce
  2. Click “Send to Production”
  3. Choose garment blank
  4. Select sizes and quantities
  5. Proceed to Stripe checkout
  6. 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.