The first purchase order model has a status: draft, sent, received. Receiving marks it done and adds the stock. It survives until the first delivery where the supplier sends 80 of 100 units and promises the rest on Thursday.
Receipts are events against lines
The PO is the intent. What arrives is a separate record, and there can be several:
po_lines
po_id, sku, quantity_ordered, unit_cost_minor
po_receipts
po_id, received_at, supplier_note, received_by
po_receipt_lines
receipt_id, po_line_id, quantity_received, batch_id
Outstanding quantity is ordered minus the sum of received. The PO has no status column at all — its state is derived: nothing received is open, some received is partial, all received is closed, and “closed short” is a decision someone records rather than a state the system guesses.
The stock movement is written on receipt, not on order
An obvious point that gets broken constantly, usually to make the “incoming stock” number look right. Ordering stock does not create stock. If you need to show what is on order, that is a query over open PO lines, not a movement.
The same discipline as the transfer: goods exist in a location when they physically arrive there, and at no other moment.
Cost is captured per receipt
Suppliers change prices between order and delivery, and split deliveries can arrive at different costs. The batch created by each receipt carries the cost from that receipt line. Averaging at the product level loses the link between what you paid and what you sold, and margin reporting quietly stops meaning anything.
Over-receipt is a real case
Suppliers send more than ordered, sometimes deliberately. Blocking it forces warehouse staff to either refuse stock that is physically present or create a fake PO — both worse than accepting it. Allow it, flag it on the receipt, and let purchasing resolve the difference. The system should describe reality, not argue with it.
What this costs
Two extra tables instead of a status column. The alternative is discovering mid-build that “received” cannot express what happened, and retrofitting receipts into code that assumed one delivery per order.