W129: Create work order
Opens a new work order to produce finished goods from materials and (optionally) sub-assembly SKUs. Triggered from the Work Orders page, "New WO". The workflow validates inputs, rolls up the material unit cost recursively through the BOM, snapshots the recipe into work_order_inputs so later BOM edits do not affect this WO.
Steps
-
Generate the next WO number. Use the per-tenant counter for this org to produce
WO-2026-0001,WO-2026-0002, and so on (sequence resets each calendar year). -
Validate the work-site location. Confirm it exists and is active.
-
Validate output SKUs have a BOM. For each output line, confirm the SKU exists and has a BOM in this org. A SKU without a BOM cannot be produced.
-
Create the WO header. Call
WorkOrders.createWowith header fields (woNumber,orderDate, optionalexpectedDeliveryDate,workSiteLocationId,notes) and the initial status (Draftby default). -
Add each output line with its rolled-up cost. For every line, call
WorkOrders.addItemwith the SKU id, quantity, computedmaterialUnitCost, and the user-suppliedconversionCost. The cost rollup recurses through the BOM: eachbom_inputcontributesquantity × unit_cost, whereunit_costfor a material input is the FIFO weighted-average from the materials ledger (covering the nextquantityunits to be consumed from the oldest layers at the work-site location), and for a SKU sub-assembly input is recursively computed via the same formula against that SKU's BOM (a SKU input with no BOM contributes its FG-ledger FIFO weighted-average instead). Multi-level BOMs work the same way at any depth. -
Snapshot the recipe into
work_order_inputs. For every output line, callWorkOrders.addInputsForItemwith the resolved inputs from step 5. Each snapshot row carriesmaterial_id XOR input_sku_id, the per-output-unit quantity from BOM, and the computedunitCostat snapshot time. This freezes both identity and quantity (BOM edits afterwards do not affect this WO) but leaves cost values updatable by W027's cascade. -
Reconcile the inventory ledger to the seed status. Run the status → ledger reconciliation projector. For the current status it writes: an FG
ORDERper output line and aDEMANDper BOM-snapshot input — materials or FG ledger per input kind — once status leavesDraft(and is notCancelled); and anALLOCATEper input once status reachesProductionor beyond (In Transit,Partial,Received,Completed). A WO created at the defaultDraftwrites nothing.CONSUMEandPRODUCEstay receipt-driven (W137).
Returns
The new WO with hydrated items and recipe snapshot.
Business rules
- BOM is required for every output SKU. WOs without recipes are rejected at step 3.
materialUnitCostis computed by L3, not user-supplied. The cost rollup is the workflow's job; L2 just stores what it's given. Conversion cost is user-supplied.- Snapshot freezes identity and quantity, not cost. The
work_order_inputsrows are the consumption contract for this WO; later BOM edits do not affect them. Cost values on the snapshot are updated by W027's cost cascade as upstream prices change. - Ledger reconciliation is idempotent. Step 7 computes the desired FG
ORDER/DEMAND/ALLOCATEset from the seed status and only writes what's missing, so create and later updates (W132) share the same projector and re-runs are no-ops.CONSUME/PRODUCEstay receipt-driven (W137). - Multi-level BOMs are supported. A SKU sub-assembly input recurses through its own BOM during cost rollup. Cycle detection is out of scope for v1; the team relies on data hygiene.
Errors
NotFoundError. The location, an output SKU, or its BOM was missing.ValidationError. An output SKU has no BOM.