📥 AutoReceiveOnOrder auto-receipt
Updated
← Developer wiki For the user: what the interface shows.
PurchasingSettings.AutoReceiveOnOrder receives the goods with the same click
that places the order. "Place order" writes Ordered, then Received — two
saves, because the transition table cuts off the jump Draft → Received.
Where the checks live#
Receipt checks (the RECEIVING cell when warehouse discipline is enabled, an
active tax rate as of the receipt date) live in IPurchaseReceiptService —
one implementation for both paths:
| Path | Call |
|---|---|
The "Receive order" button (ReceiveOrderCommand) |
ReceiveAsync(orderId) |
"Place order" with AutoReceiveOnOrder (PlaceOrderCommand) |
ValidateReceiveAsync while still in Draft, then ReceiveAsync |
Two copies of these checks would drift silently, and the drift is visible only on the posting entry, so both commands go through one door.
ReceiveQty on a line is split in the same ReceiveAsync, before the move to
Received. Zero on all lines is left alone: old orders are received in full.
Otherwise the line quantity becomes "receive now", and the remainder becomes a
new PurchaseOrder in Ordered linked to this one. There is no posting for the
remainder until it is received. ReceiveQty greater than Quantity is a line
refusal, and the subtype does not change.
A refusal leaves Draft, not Ordered#
PlaceOrder calls ValidateReceiveAsync before leaving Draft. The user
pressed one "place and receive" button: a failed receipt check must not leave
the order in Ordered, a state they did not choose.
The check is repeated inside ReceiveAsync: between the first check and the
transition the document was saved, and the receipt must judge the state in which
it is posted.
Off (the default) — only Ordered; receipt is not checked at all.
Trap when writing the test#
Turning on InventorySettings.EnforceWarehouseTasks is not a passive flag.
Its OnAfterSave calls PrepareAllYardsAsync(), and that calls
InferTypePurposesAsync(), which sets StoreCellType.Purpose by the type
NAME when the field is empty (the cell role still lives in the name as a
fallback). A type named "Receiving" becomes a receiving type by itself after
discipline is enabled.
Therefore the "cell with no assigned purpose" failure scenario cannot be built
that way: the purpose must be set explicitly and strictly after discipline is
enabled, otherwise the inference from the name will overwrite it back. The test
must check its own precondition (IsWarehouseDisciplineOnAsync,
GetCellPurposeAsync); otherwise a failure only means "something is wrong" and
sends you looking for a bug in the command.
Tests: PlaceOrderCommandTest.AutoReceiveOnOrderReceives (success) and
AutoReceiveBlockedKeepsDraft (refusal leaves a draft).
Same topic for other readers: Analyst · User