Order Lifecycle
This is the most non-obvious part of the system, and the one most worth
reading before touching orders.service.ts. Every transition below was
verified end-to-end against a real running instance (real HTTP calls, not
mocks) — not inferred from reading code alone.
Who is FromOrg, who is ReceiverOrg
For a Request order (a facility asking its parent store for stock):
FromOrg is the parent store (the one that will approve and fulfil),
ReceiverOrg is the requesting facility. For a Return order (a
facility sending stock back), it's the other way round: FromOrg is the
facility initiating the return, ReceiverOrg is the parent store
receiving it.
The happy path (Request order, full receipt)
- Draft — the facility calls
POST /orders/requestwith a product + quantity. This creates the order (if one doesn't already exist in Draft for that facility) and adds a line item withquantityApprovedstill unset. - → Pending — the parent store assigns a batch and an approved
quantity per line via
PUT /orders/:id/assign-batch. (There's a second, more granular path —submitOrderRequestthenmatchBatchToOrderDetailsthenvalidateOrder— that goes through an explicitSubmittedstatus first;assign-batchis the direct single-step version of the same transition, both landing inPending.) - → Approved —
PATCH /orders/approveOrder/:id. This is where the real stock movement happens: for each batch-tracked line, it debits the parent store's stock and credits the facility's stock (twoStockMovementrows), then marks the lineisPosted. The whole sequence runs inside a database transaction, so a failure partway through can't leave stock debited on one side without the matching credit on the other. - Acknowledge each line — the facility calls
PATCH /orders/acknowledgeProductper line withquantityReceived. - → Complete —
PATCH /orders/acknowledgeOrder/:id. If every line'squantityReceivedequals itsquantityApproved, the order goes straight toComplete.
The shortfall branch
If any line's quantityReceived is less than its quantityApproved,
step 5 above takes a different path: acknowledgeOrder requires a
shortfallReason (one of a fixed set of options, defined via an
AttributeDefinition — e.g. "Damaged in transit", "Wrong quantity
shipped", "Lost/missing") and the order moves to
PendingShortfallApproval instead of Complete.
The parent store then resolves it via PATCH /orders/approveShortfall/:id, choosing a destination:
Inventory— the shortfall quantity is treated as returned stock; it's credited back to the parent store's balance.Wastage— the shortfall quantity is recorded as lost, with no balance credited anywhere (arecordStockMovementWithoutBalanceChangecall, not a real debit/credit).
Either way, the order ends in Complete.
The return-order branch
A separate flow, for a facility returning stock it already has (not tied
to a prior request's shortfall): POST /orders/InitiateReturnOrder →
PATCH /orders/SubmitReturnOrder/:id → the parent store acknowledges
each returned line (PATCH /orders/AcknowledgeReturnOrderProduct) → then
acknowledges the whole order (PATCH /orders/AcknowledgeReturnOrder/:id),
which posts the actual debit-facility/credit-parent-store stock
movements and completes it.
Every write sequence above is transaction-wrapped
ApproveOrder, AcknowledgeOrder (both branches), ApproveShortfall,
RejectShortfall, requestOrder, InitiateReturnOrder,
AcknowledgeReturnOrder, and the batch-assignment step all wrap their
multi-step writes in a database transaction — a crash or thrown error
partway through rolls back cleanly rather than leaving stock genuinely
inconsistent. This wasn't always true; it was a deliberate fix, closing a
real bug class (a debit posted with no matching credit if the process
died mid-sequence).