Order lifecycle
The unified state machine of an Order across every ingestion channel (manual, public store, WhatsApp, Instagram). Audience: new dev with Node/React experience, no Eziseller context.
1. Overview
An Order is the canonical record of a customer purchase — the hand-off point between sales channels (messaging, storefront, dashboard) and fulfillment (invoice, shipping label, payment reconciliation). Orders can be created directly (manual dashboard entry, public checkout) or indirectly via a DraftOrder that is parsed from a WhatsApp/Instagram message and later approved by the seller. Once persisted, every Order follows a single 8-state OrderStatus machine, with two orthogonal sub-states for financialStatus and fulfillmentStatus. This doc is the single source of truth for "what state can an order be in, and how does it get there?"
2. Architecture
Manual and public checkout write directly to orders; messaging channels first produce a DraftOrder with a confidence score, which the seller reviews before it is converted. Fulfillment artefacts (invoice, label, tracking) hang off the Order and gate further state transitions.
3. Data model
See schema.prisma:L401-L517 for field-level detail. Order.customer is a denormalised JSON blob (not a FK to Customer) — the Customer model is a separate CRM-style index populated from order snapshots.
4. Key flows
4.1 Draft approval (WhatsApp/Instagram)
Implemented in draft-order-service.ts:L211-L318. The draft fetch, claim, order insert, and back-link all run inside a single prisma.$transaction — see gotchas.
4.2 Status transition (generic)
See routes/orders.ts:L933-L1069. Legal values are enforced by an allow-list, not the Prisma enum alone.
5. Lifecycle / state machine
Two entry states exist for historical reasons: direct-created orders default to pending (schema.prisma:L408), while orders converted from an approved draft are created as new (draft-order-service.ts:L257). The "can this order progress?" logic in routes/orders.ts:L19-L81 gates confirmed -> packed on the presence of invoiceUrl and labelUrl/fulfillment, but other transitions are currently open (any valid enum value is accepted by PUT /:id/status). Terminal states: delivered, cancelled, refunded.
Parallel sub-state machines:
financialStatus:pending | paid | partially_paid | refunded | partially_refunded— driven byPaymentTransactionrows, updated via the same status endpoint.fulfillmentStatus:unfulfilled | partial | fulfilled— driven byFulfillmentrows.
Edits to order items/customer are blocked once status is in a non-editable terminal set (see routes/orders.ts:L690-L697).
6. Key files
- backend/prisma/schema.prisma:L205-L245 —
OrderStatus,OrderSource,FinancialStatus,FulfillmentStatus,DraftOrderStatusenums - backend/prisma/schema.prisma:L401-L451 —
Ordermodel - backend/prisma/schema.prisma:L478-L539 —
DraftOrder+DraftOrderItem - backend/routes/orders.ts:L19-L81 —
calculateCanProgressprogression rules - backend/routes/orders.ts:L377-L625 —
POST /api/orders(manual create) - backend/routes/orders.ts:L933-L1090 —
PUT /api/orders/:id/status(transition endpoint, inventory restoration) - backend/routes/draft-orders.ts — draft review/approve/reject HTTP surface
- backend/lib/draft-order-service.ts:L211-L318 —
approveDraftOrder(draft -> order in a transaction) - backend/lib/draft-order-service.ts:L323 —
rejectDraftOrder - backend/lib/order-creation-service.ts:L27-L174 — messaging entry points (
createDraftOrderFromMessage, legacycreateDirectOrderFromMessage) - backend/lib/order-helpers.ts — shared builders for order creation across channels
7. Env vars & config
No env vars are specific to the order state machine itself. Related subsystems pull their own config:
| Var | Required | Purpose | What breaks |
|---|---|---|---|
DATABASE_URL | yes | Postgres connection | All order reads/writes |
FRONTEND_URL | yes | CORS allow-list | Dashboard cannot call status endpoint |
Draft expiry is hard-coded to 24h (draft-order-service.ts:L72-L74).
8. Gotchas & troubleshooting
- Concurrent draft approvals would create two orders. Cause: the draft fetch and order insert used to run outside a transaction. Fix (commit
7e74ee6): everything including the initialfindFirstnow runs insideprisma.$transaction, and the draft is immediately updated toapprovedto claim the row (draft-order-service.ts:L214-L245). Do not re-introduce a pre-transaction fetch. - Two entry states (
newvspending). Draft-sourced orders start atnew, all others atpending. UI filters that assume a single "just created" status will miss half the orders. - Status enum is validated twice. Prisma enforces the enum, but the route also keeps its own allow-list (routes/orders.ts:L941-L943). Adding a value to the enum without updating this list causes a 400 at runtime.
- Inventory is only restored on
cancelled, never onrefunded. See routes/orders.ts:L1020-L1037. If a shipped order is refunded, stock is not automatically returned. Order.customeris denormalised JSON. Updating theCustomerCRM record does not retroactively change past orders — snapshots are intentional.- Legacy
createDirectOrderFromMessagestill exists (order-creation-service.ts:L53) but the default path routes through drafts. Do not call it from new code.
9. Extension points
- To add a new ingestion source: add a value to
OrderSourceinschema.prisma, create a channel-specific service that callsorder-helpers/DraftOrderService, and wire it into any dashboards filtering bysource. - To add a new status: extend
OrderStatus, update the allow-list inroutes/orders.ts, add a case incalculateCanProgress, and update the frontend status badge map. Consider whether it is terminal (blocks edits). - To add transition guards: extend
calculateCanProgressand reject disallowed transitions inPUT /:id/status— the endpoint currently only validates enum membership, not state-machine legality.
10. Related docs
- draft-orders.md — draft review queue, expiry, AI confidence
- order-creation-manual.md
- order-creation-public-checkout.md
- order-creation-whatsapp.md
- order-creation-instagram.md
- ../tax-and-invoices/tax-engine.md
- ../tax-and-invoices/invoice-numbering.md
- ../../02-integrations/shipping/shipping-overview.md