Receive a purchase order
Receiving a purchase order is the moment paperwork becomes inventory. Until you click Receive, the PO is a planning document — no stock, no journal entry, no supplier balance movement. Confirming receive opens FIFO layers at landed cost, posts the AP credit (or cash, if you’re paying now), and locks the line items so they can’t drift after the fact. Everything happens in one database transaction; if any step fails, none of it lands.
Before you click Receive
The PO needs to be in draft, confirmed, or ordered state with at least one line. Walk the PO once before confirming:
- Are the quantities correct against what physically arrived? Adjust if the supplier short-shipped — receive only what’s on the truck, not what was ordered.
- Are the landed costs sensible? Glance at the Allocated and Landed columns; if a line is wildly off, the extras’ weights are wrong. See Landed cost.
- For batched lines, is the lot number entered? For expiring batches, is the expiry date set?
- For serialized lines, no special prep — the receive flow asks for the identifiers in step 3 below.
The receive flow
Click Receive
Permission-gated. The button is hidden if the PO is already received or voided. The dialog opens.
Pick a payment status
Three options:
- Unpaid — the full landed total credits AP (2010). Settle later via the supplier payment dialog.
- Partial — enter the cash amount handed over now. AP credits for the difference.
- Paid in full — debit AP and credit the active shift’s cash sub-account (1010-xxx) immediately.
For serialized lines: enter identifiers
Each serialized line opens an IMEI/serial entry table sized to its quantity — one identifier per unit. The table accepts scanner input row-by-row (place focus in the first cell, scan, Enter or Tab to advance). Unique-constraint violations surface inline if you’ve entered an identifier that’s already in stock.
This is the only flow where serialized identifiers are captured at the same moment as the inventory ingress — ad-hoc restock has its own version of the same UI, but PO receive is the formal path for shipments of phones, tablets, and registered equipment.
Confirm
One press, one database transaction. Everything below happens together or none of it does:
- For each line, an
inventory_transactionsingress row is inserted at the landed unit cost (base + allocated extras share).qty_remaining = quantity— that row IS the new FIFO layer. - For serialized lines, one
serialized_itemsrow per identifier is inserted with the same per-unit cost. - For batched lines, one
inventory_batchesrow is inserted alongside the FIFO ingress with the lot number and expiry. - The product’s WAC is updated via
updateProductCost. - The journal entry posts. With a supplier: DR 1200 Inventory / CR 2010 AP for the unpaid portion, plus an immediate DR 2010 / CR 1010-xxx (active shift cash) for any cash paid. Without a supplier (rare; cash purchase from petty): DR 1200 Inventory / CR 3010 Owner’s Equity.
- The PO row updates to
status='received',received_at=now,received_by_user_id.
Quick PO vs Formal PO
Two PO shapes flow into the same Receive button, with slightly different semantics on the back end:
- Formal PO — the multi-line editor reachable from Procurement → Purchase Orders → New or Restock from this supplier. Supports extras, weight-based landed cost, attachments. The standard flow for any shipment with paperwork. Once received, the PO is largely locked — line items can’t be edited.
- Quick PO — a single-line PO type generated by the ad-hoc restock path under the hood. Reachable for edits via the Quick PO Edit action on the inventory history page. The handler reverses the previous ingress and writes a fresh one at the new values, preserving FIFO correctness for the common case of “I typed the wrong cost on a recent restock.”
You’ll rarely need to know the difference unless you’re editing a previously received quick restock. For new shipments, raise a Formal PO whenever there’s freight, customs, or paperwork to attach.
After receive
The PO is now locked. Reports pick it up:
- Inventory Valuation — the new layer at landed cost.
- AR/AP Aging — the supplier balance from the receive date.
- General Ledger — the JE block tagged
source_type='inventory_adjustment'with the PO number on every line. - Inventory Moves — each line as a separate ingress row.
Two paths back out:
- Quick PO Edit — for quick POs, adjust quantity, cost, lot/expiry, identifier on the inventory history page. The previous ingress reverses, a fresh one writes at the new values.
- Void — for formal POs, only reachable from
received, only valid while every unit is still on hand. Reverses the inventory and JE, drainsqty_remainingto zero. If any units have already sold, the void is rejected — see Purchase orders for the void flow and the “issue a credit note instead” alternative.
Common errors
”Supplier balance went up by more than I expected”
You added an extra cost after adding lines but didn’t redistribute weights. Open the PO, check the Allocated column on each line, fix the weights, save before receive — extras follow weight ratios, not line subtotals.
”Cost on a PO line shows zero”
The product has no ingress history (it’s brand new) and the field was empty at save. The line will save and a zero-cost FIFO layer will open on receive — and any sale that draws from it will post zero COGS, distorting gross profit. Edit the line, type a real cost, save, then receive. If you genuinely received a free sample, do it deliberately and add a note.
”PO is locked but I made a typo on quantity”
For a formal PO that’s already received: void it, raise a new one, receive the new one. For a quick PO: use Quick PO Edit on the inventory history page. Either way, the original audit row stays intact — the correction is layered on top, not on the original.