W111: Create purchase order
Opens a new purchase order against a vendor. Triggered from the Purchase Orders page, "New PO" button. The PO header is persisted with its line items and the header-level surcharges (shipping and setup).
Steps
-
Generate the next PO number. Use the per-tenant counter for this org to produce
PO-2026-0001,PO-2026-0002, and so on. Numbers are unique per org and the sequence resets each calendar year. -
Validate referenced rows. Call
Vendors.getByIdandLocations.getByIdto confirm both belong to this org and are active. Reject on miss or soft-delete. -
Create the PO header. Call
PurchaseOrders.createPowith the full header:vendorId,targetType(materialsorfinished_goods),date, optionalshipDateandexpectedDeliveryDate,shipToLocationId,shippingCosts,setupCosts, the seedstatus(Planningby default), optionalqboIdandnotes. Persistedcostsis left at zero on create; it gets rolled up by the receipt workflows. -
Add each line item. For every input line, call
PurchaseOrders.addItemwith the material id (materials PO) or SKU id (FG PO) and the unit cost. The reference must satisfy thematerial_id XOR sku_idinvariant per row and must match the PO'stargetType. -
Reconcile the inventory ledger to the seed status. Run the status → ledger reconciliation projector. It writes an
ORDERentry per line item — on the materials ledger or the FG ledger pertargetType— whenever the seed status is notPlanningorCancelled. A PO created at the defaultPlanningwrites nothing; one seeded atPlacedor beyond seeds itsORDERentries immediately.RECEIVEstays receipt-driven (W122).
Returns
The new PO with hydrated items and the computed expectedTotal (sum of items' quantity × unit_cost + shippingCosts + setupCosts).
Business rules
targetTypeis fixed at creation. Line items must reference the matching catalog (materials xor SKUs); changing target type later is not allowed.- Vendors and locations must be active. Soft-deleted rows are rejected at step 2.
- Status is the L1 ENUM. The initial status must be one of the seven
org.po_statusvalues (Planning,Placed,In Transit,Partial,Received,Paid,Cancelled); anything else is rejected at the boundary. - Ledger reconciliation is idempotent. Step 5 computes the desired
ORDERset from the seed status and only writes what's missing, so create and later updates (W114) share the same projector and re-runs are no-ops. - Header surcharges are part of the cost model.
shippingCostsandsetupCostsare stored on the PO header and feed bothexpectedTotalandactualTotalderivations on read. costsis a persisted rollup, not user-supplied. It is maintained by the receipt workflows (W122/W125/W126) and equalssum(receipt_items.qty × unit_cost) + shippingCosts + setupCosts. On create it is zero.
Errors
NotFoundError. Vendor, location, or a referenced material or SKU was not found.ValidationError. A line item references the wrong catalog for the target type or violates the XOR invariant.