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) throughconsumeShipmentStock. - If
stockOverrideAtis set, shortfall is reconciled and a variance is recorded with sourceOPERATOR_OVERRIDE. - Updates shipped quantities, syncs the order status, and enqueues
stock.sync.requestedfor 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, andCANCELLED(fulfillment.error.stockOverrideAfterDispatch). - Refuses when an override is not required.
- Refuses lot-tracked products (
fulfillment.error.stockOverrideBlockedForLotTracked). - Refuses when
TenantSetting.blockOversellis 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.