Skip to main content
Version: v1.0.0(int)

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

  1. 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.

  2. Validate referenced rows. Call Vendors.getById and Locations.getById to confirm both belong to this org and are active. Reject on miss or soft-delete.

  3. Create the PO header. Call PurchaseOrders.createPo with the full header: vendorId, targetType (materials or finished_goods), date, optional shipDate and expectedDeliveryDate, shipToLocationId, shippingCosts, setupCosts, the seed status (Planning by default), optional qboId and notes. Persisted costs is left at zero on create; it gets rolled up by the receipt workflows.

  4. Add each line item. For every input line, call PurchaseOrders.addItem with the material id (materials PO) or SKU id (FG PO) and the unit cost. The reference must satisfy the material_id XOR sku_id invariant per row and must match the PO's targetType.

  5. Reconcile the inventory ledger to the seed status. Run the status → ledger reconciliation projector. It writes an ORDER entry per line item — on the materials ledger or the FG ledger per targetType — whenever the seed status is not Planning or Cancelled. A PO created at the default Planning writes nothing; one seeded at Placed or beyond seeds its ORDER entries immediately. RECEIVE stays receipt-driven (W122).

Returns

The new PO with hydrated items and the computed expectedTotal (sum of items' quantity × unit_cost + shippingCosts + setupCosts).

Business rules

  • targetType is 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_status values (Planning, Placed, In Transit, Partial, Received, Paid, Cancelled); anything else is rejected at the boundary.
  • Ledger reconciliation is idempotent. Step 5 computes the desired ORDER set 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. shippingCosts and setupCosts are stored on the PO header and feed both expectedTotal and actualTotal derivations on read.
  • costs is a persisted rollup, not user-supplied. It is maintained by the receipt workflows (W122/W125/W126) and equals sum(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.