Skip to main content

Dispatch a fulfillment order

Status changes go through FulfillmentWorkflowService.transitionStatusWithTx. Courier tracking that marks a shipment picked up uses shipShipmentWithTx instead.

Operator path​

LABEL_CREATING and SHIPPED throw fulfillment.error.stockOverrideRequired when requiresStockOverride is true.

READY_TO_SHIP and SHIPPED throw shipment.error.activeAwbRequired unless a non-cancelled forward shipment has an awbNo.

SHIPPED on this path:

  • Consumes remaining quantity (quantityRequired - quantityShipped) through consumeShipmentStock.
  • If stockOverrideAt is set, shortfall is reconciled and a variance is recorded with source OPERATOR_OVERRIDE.
  • Updates shipped quantities, syncs the order status, and enqueues stock.sync.requested for the line product ids.

CANCELLED releases reservations and syncs the order. DELIVERED syncs the order and does not enqueue stock sync in this branch.

PUT /v1/operations/fulfillment-orders/:id/manual-awb and PUT /v1/operations/fulfillment-orders/:id/stock-override are the HTTP entries beside the status route. Label purchase itself is POST /v1/operations/fulfillment-orders/batch/labels on fulfillment_queue. See Labels.

Stock override​

approveDispatchWithoutStock (PUT /v1/operations/fulfillment-orders/:id/stock-override):

  • Refuses SHIPPED, DELIVERED, and CANCELLED (fulfillment.error.stockOverrideAfterDispatch).
  • Refuses when an override is not required.
  • Refuses lot-tracked products (fulfillment.error.stockOverrideBlockedForLotTracked).
  • Refuses when TenantSetting.blockOversell is true.

The comment says the approval is taken once and then label and dispatch use the normal path. It records who approved.

Courier scan path​

shipShipmentWithTx is used when tracking maps PICKED_UP or IN_TRANSIT to shipped. It requires an active AWB and shipment items tied to fulfillment lines.

It refuses any line with quantityBackordered greater than 0 (fulfillment.error.cannotShipBackorderedItems). There is no override on this path. The comment says a courier scan is not a person at the shelf.

Fully shipped lines become SHIPPED. A remainder becomes PARTIALLY_SHIPPED. Both enqueue stock.sync.requested when product ids were consumed. PARTIALLY_SHIPPED may ship again. The state map includes a self-edge.

Idempotency​

Operator ship and courier ship are different methods. Whether both can consume the same units depends on quantityShipped checks. The courier path rejects a quantity that would exceed quantityRequired. A double scan that passes that check was not proven impossible.