Skip to content

Latest commit

 

History

History
179 lines (146 loc) · 9.15 KB

File metadata and controls

179 lines (146 loc) · 9.15 KB

Workflow State Machines

This is the canonical reference for operational status workflows in IMS. The source of truth is the state-machine code in lib/domain/workflows; the tables below are generated from those transition constants with npm run docs:workflows.

Workflow guards are used by mutation paths that change sales order, shipment, purchase order, refund-derived, and stock transfer statuses. UI labels and help articles should stay high level and link here instead of duplicating transition rules.

Sales Orders

Status Allowed next statuses Notes
ALLOCATED PICKING, PROCESSING, SHIPPED, CANCELLED, ON_HOLD, PARTIALLY_REFUNDED, REFUNDED -
CANCELLED None Terminal.
COMPLETED DELIVERED, PARTIALLY_REFUNDED, REFUNDED -
DELIVERED PARTIALLY_REFUNDED, REFUNDED -
DRAFT PROCESSING, PENDING_PAYMENT, ALLOCATED, CANCELLED, ON_HOLD, PARTIALLY_REFUNDED, REFUNDED -
ON_HOLD DRAFT, PENDING_PAYMENT, PROCESSING, ALLOCATED, PICKING, PACKING, CANCELLED, PARTIALLY_REFUNDED, REFUNDED -
PACKING SHIPPED, CANCELLED, ON_HOLD, PARTIALLY_REFUNDED, REFUNDED -
PARTIALLY_REFUNDED REFUNDED Can move only to REFUNDED.
PENDING_PAYMENT PROCESSING, DRAFT, ALLOCATED, CANCELLED, ON_HOLD, PARTIALLY_REFUNDED, REFUNDED -
PICKING PACKING, SHIPPED, CANCELLED, ON_HOLD, PARTIALLY_REFUNDED, REFUNDED -
PROCESSING ALLOCATED, CANCELLED, ON_HOLD, PARTIALLY_REFUNDED, REFUNDED -
REFUNDED None Terminal.
SHIPPED COMPLETED, DELIVERED, PARTIALLY_REFUNDED, REFUNDED -

The DELIVERED transition can be driven automatically by delivery-tracking polling (TrackShip or the active shopping connector, via /api/cron/delivery-status). That cron path runs the same transition guard and side effects as a manual delivery — it does not write the status directly. Because the cron has no user session it skips only the permission check (not the state-machine guard, unlike the WooCommerce status-sync path), so if an order has moved out of a deliverable state (e.g. cancelled or refunded after dispatch) between the poll's SHIPPED query and the under-lock write, the guard rejects the change and the cron logs a delivery_status_skipped warning instead of forcing it.

COMPLETED vs DELIVERED. Both are manual, operator-set terminal-ish statuses reached from SHIPPED (and COMPLETED → DELIVERED). DELIVERED means the carrier confirmed delivery and can be set automatically by the delivery-status cron; COMPLETED is a manual "this order is done from our side" marker for businesses that don't track delivery (no automatic trigger sets it). Neither is forced by any sync; both still allow PARTIALLY_REFUNDED/REFUNDED afterwards. Use DELIVERED when delivery tracking is in play, COMPLETED otherwise.

Manual status edits are rejected on archived orders (unarchive first); automated pushes (WooCommerce force-sync, the delivery-status cron) still apply. Deleting a payment that takes an already-paid order in an advanced status (SHIPPED/COMPLETED/DELIVERED/PARTIALLY_REFUNDED) back below fully-paid does not auto-revert the status but raises a payment_status_mismatch warning activity log so the operator can decide (it fires only on a genuine paid→unpaid transition, not for orders that were never fully paid, e.g. credit terms).

Shipments

Status Allowed next statuses Notes
PACKED SHIPPED -
PENDING PICKING -
PICKING PACKED -
SHIPPED None Terminal; stock dispatch and cost-layer snapshots happen on this transition.

Purchase Orders

Status Allowed next statuses Notes
CANCELLED None Terminal.
CLOSED None Terminal.
DRAFT RFQ_SENT, PO_SENT, CANCELLED -
INVOICED PARTIALLY_RETURNED, RETURNED, CLOSED -
PARTIALLY_RECEIVED RECEIVED, PARTIALLY_RETURNED, RETURNED, CLOSED, CANCELLED -
PARTIALLY_RETURNED RETURNED -
PO_SENT SHIPPED, PARTIALLY_RECEIVED, RECEIVED, PARTIALLY_RETURNED, RETURNED, CLOSED -
QUOTE_RECEIVED PO_SENT, CLOSED -
RECEIVED INVOICED, PARTIALLY_RETURNED, RETURNED, CLOSED -
RETURNED None Terminal.
RFQ_SENT QUOTE_RECEIVED, PO_SENT, CLOSED -
SHIPPED PARTIALLY_RECEIVED, RECEIVED, CLOSED -

Refunds

Status Allowed next statuses Notes
CREDIT_NOTE_SYNCED PAID Derived from accounting credit-note sync state.
PAID None Terminal; derived from linked refund payments.
RECORDED CREDIT_NOTE_SYNCED, PAID Derived when a SalesOrderRefund row exists.

Stock Transfers

Status Allowed next statuses Notes
CANCELLED None Terminal.
DRAFT IN_TRANSIT, CANCELLED -
IN_TRANSIT RECEIVED -
RECEIVED None Terminal.

Domain Notes

Sales Orders And Shipments

Sales order status tracks the commercial order lifecycle. Shipment status tracks physical fulfilment for one warehouse shipment.

Order status and shipment status are intentionally separate. Shipment SHIPPED performs stock dispatch and cost-layer snapshot work. Sales order SHIPPED is an aggregate state that is reached only after shipment rows exist and all shipments have reached shipment SHIPPED.

PARTIALLY_REFUNDED and REFUNDED are order statuses set by refund creation. REFUNDED is terminal; PARTIALLY_REFUNDED can move to REFUNDED when later refunds bring the total refunded amount up to the order total.

Purchase Orders

Purchase order status tracks supplier procurement, shipment, receipt, invoicing, closure, and return state. Receipt actions move orders to PARTIALLY_RECEIVED or RECEIVED; supplier return actions can move eligible orders to PARTIALLY_RETURNED or RETURNED.

Returns vs. supplier bills. A return reverses stock and FIFO cost layers but does not adjust supplier invoices already recorded against the PO. When a return leaves a line billed for more than the quantity now kept (received − returned), the PO detail page shows an amber over-billing alert and a return_overbilled_bill WARNING activity-log row (naming the bills and the over-billed amount). Reducing the AP liability is a manual step — raise a supplier credit; IMS does not yet post a credit memo automatically (audit-C4).

Cancellation and already-consumed cost. Cancelling a PO reverses the remaining (still-on-hand) receipt cost layers. Units already sold or used keep their COGS booked against the cancelled receipt. When that consumed quantity is non-zero, cancellation emits a cancelled_consumed_cogs_standing WARNING and the UI surfaces the consumed units and value for finance review — IMS does not reverse COGS for stock that has already left (audit-H8). Cancellation remains blocked entirely once a supplier invoice exists.

Refunds

Refunds do not currently have a persisted status column. The refund workflow is derived from SalesOrderRefund, accounting credit-note sync state, and linked refund payments.

Stock Transfers

Stock transfer status tracks inter-warehouse movement. IN_TRANSIT means stock has left the source warehouse and is not yet available at the destination.

Stock / concurrency invariants (audit-M-stock). A few cross-cutting guards worth knowing:

  • Transfers don't strand allocations. Dispatch availability is the source warehouse's on-hand minus its reservedQty (availableForTransfer), and StockLevel.reservedQty is per-(product, warehouse) and kept in sync with order allocations — so a transfer can never drain stock an order is holding in that warehouse.
  • Manual receipt + WMS booked-in don't double-count. reconcileBookedInQuantities nets the locally-received quantity (read under a FOR UPDATE lock on the PO or the stock transfer) so a manual receipt already covering a line is not re-added when the Mintsoft booked-in webhook approves the same ASN.
  • Opening stock can't duplicate. applyOpeningStock takes a FOR UPDATE lock on the stock level before checking for an existing opening cost layer, so concurrent calls serialise and the second is rejected.
  • Non-negative stock in the DB. quantity >= 0 and reservedQty >= 0 are fully VALIDATEd CHECK constraints (every existing row checked). reservedQty <= quantity is a NOT VALID constraint — enforced on new writes but not yet validated against historical rows (that cleanup is deferred), so the transfer guard above (not just the constraint) is what protects allocations at dispatch.
  • FIFO ordering at the destination of a received transfer follows the dispatch-time cost-layer snapshot order. If transfers are received out of dispatch order the recreated layers can be marginally out of strict FIFO order at the destination — this is cosmetic for reporting and accepted (the cost basis per layer is preserved).