Work Orders
A work order drives a manufacturing run from scheduling through completion — tracking the Product being built, planned and produced quantities, materials consumed, operations performed, and quality checks recorded along the way.
A work order is the unit of production in the Manufacturing module. You create one when you need to build a quantity of a composite Product — the order tracks everything from the initial plan through the last quality check: who is working on it, which materials are required, how many units were actually produced, and what each step cost. Work orders consume inventory units as materials and produce finished units that land in your chosen output location.
Work orders can only target composite Products — Products with a bill of materials (BOM). If a Product is not marked composite, it will not appear in the Output Product picker. See Products for details on the BOM flag.
Creating a work order #
New work orders are created from Manufacturing → Work Orders → New Work Order. The form asks for:
- Output Product — the composite Product you are producing. Required. Only Products marked as composite (Has Bill of Materials) appear in the list.
- Planned Quantity — how many units you intend to produce. Must be greater
than zero. Defaults to
1. - Status — Draft or Scheduled. A new work order can only open at the start line; every later status is reached by working the order (issuing materials, starting production, recording a quality check). The record refuses a work order created as Completed or Cancelled whichever door it comes through, because such an order has no legal transition out of itself.
- Due Date — optional target completion timestamp.
- Scheduled Start — optional timestamp for when production is planned to begin.
- Output Location — the inventory location where finished units will land when the order is completed. Optional; if omitted the units must be placed manually.
- Assigned To — a user responsible for the order. Optional.
- Notes — free-form text for any additional context.
When a work order is saved, it is automatically assigned a sequential
number prefixed WO- (e.g. WO-0042).
Work order statuses #
A work order moves through a defined lifecycle. The status badge is visible in the list and on the order's page:
- Draft — created but not yet scheduled. Can move to Scheduled, In Progress, or Cancelled.
- Scheduled — planned and queued for production. Can move to In Progress or Cancelled.
- In Progress — actively being produced. Can move to Quality Check, Completed, or Cancelled.
- Quality Check — production is done; output is awaiting inspection. Can move back to In Progress, forward to Completed, or Cancelled.
- Completed — finished. The quantity produced is recorded. Terminal — cannot be changed further.
- Cancelled — stopped before completion. All non-terminal operations are automatically skipped. Terminal.
The edit form enforces this. Its Status field offers only the statuses the order can reach from where it is now — the rest are greyed out — and it says in so many words which moves are open to it. A move that is not allowed is refused with a message naming the legal next steps, never silently dropped.
The API enforces the same lifecycle. The rule belongs to the work order, not
to the screen, so a status jump the edit form refuses is refused just the same
through the API — with a 422 carrying that same
message. Previously the API accepted any status, so which door a change came
through decided whether it was allowed. Sending the status an order already has
is not a move and is still accepted.
When you cancel a work order from the UI, all pending and in-progress operations are skipped automatically so the order closes cleanly.
The work order list #
Manufacturing → Work Orders shows each order's number, output Product, planned and produced quantities, status badge, assigned user, and due date. Filter by status (multi-select) or by Product, and sort by any column. Trashed orders can be restored via the Trashed filter.
Materials #
The Materials tab on a work order lists the components required to build it. Each material line records:
- Material Product or Material Category — either a specific Product or any unit from a category qualifies. Category-based lines are useful when any equivalent substitution is acceptable.
- Planned Qty and Unit of Measure — how much is needed per the BOM, and what that figure is measured in. This is the point the whole tab turns on: a BOM line asking for 100 lb per finished unit on an order for ten units needs 1,000 lb, not 1,000 of anything. If the material is stocked in 50 lb bags, that is twenty bags.
- In Stock — how much is on the shelf at the work order's output location, in the same unit of measure, colour-coded green (covered), amber (some but not enough), or red (none). Stock at your other sites is not counted, because it is not stock this job can use.
- Consumed Qty — how much actually left inventory, derived from the stock movements rather than from the plan. The two can differ: a part-used bag is taken off the shelf whole but only what the job used is charged to it.
- Planned Cost and Actual Cost — the quantity converted into stocked
units and priced at the material's unit cost in cents (stored in the database
as an integer, e.g.
1500= $15.00). 1,000 lb of a $7.98 50 lb bag costs $159.60 — twenty bags — not $7,980. - Status — Pending, Issued (pulled from inventory and on the production floor), or Consumed.
Materials can be added and edited manually. You can add a line for any Product or category in your catalog. When a work order is completed the consumed-qty entries serve as the record of what left inventory.
Issuing materials #
Issue Materials on the work order page takes every pending line off the shelf at once. Before it moves anything it checks that each line can be covered — if any is short the whole issue is refused, nothing is consumed, and the message names the material, what the order needs, what is in stock and where. Fix the shortage (move stock in, or reduce the planned quantity) and issue again.
What comes off the shelf is decided in the material's unit of measure and then converted into whole units: 1,000 lb of a product stocked in 50 lb bags takes twenty bags, oldest first, from the work order's own location. The last bag opened is taken off the shelf whole — you cannot re-shelve half a bag of mix — while the job is charged only the quantity it asked for. Each bag records its own movement, so the audit trail names every unit that went into the run.
Category-based lines have no product to take off a shelf, so they are marked issued and left for a person to fulfil.
Operations #
The Operations tab lists the production steps for the order, in sequence order. Each operation records:
- Step # — the sequence position (1, 2, 3…).
- Name — a description of the step (e.g. Cut, Assemble, Paint).
- Work Center — the work center where the step is performed. Drives labor cost calculation via the work center's cost per hour.
- Status — Pending, In Progress, Completed, or Skipped.
- Est. Minutes — planned duration, used for planned labor cost.
- Assigned To — the user responsible for this step.
- Started At / Completed At — timestamps set when an operation is started or completed. Actual minutes (and therefore actual labor cost) are computed from the difference.
From the operations table you can Start, Complete, or Skip an individual step without leaving the work order page. Steps run in sequence order: while an earlier step is still pending or in progress, Start and Complete are greyed out and say which step is holding this one up. Finish that step, or Skip it — skipping is deliberately always available, because a step somebody decides not to perform moves the routing forward honestly, unlike completing one that never happened. Steps sharing a sequence number do not block each other.
Operations sourced from a routing template (see Routings & Operations) are pre-populated when the order is created from a BOM that references a routing; you can still add ad-hoc steps at any time.
Cost roll-up: planned total cost = sum of planned material costs + sum of planned labor costs. Actual total cost uses consumed quantities and actual operation minutes, plus any unaccounted material (see below). Both figures are visible on the work order's detail page.
Unaccounted material #
The materials issued to a work order tell you how far it could physically get: a run with concrete mix for ten units and epoxy for two makes two, because the shortest material is the one that decides. Producing more units than the materials account for is allowed — a batch planned for ten genuinely does sometimes yield eleven — but you are asked where the extra came from, and the work order carries the shortfall from then on.
The Cost Summary then shows an Unaccounted Material figure in red: the material those units should have taken, priced at the work order's own material cost per unit. Actual Material Cost stays honest about what left the shelf; Actual Total includes the shortfall, so a run that produced more than it consumed can no longer report "Actual = Planned" to the penny. Produced Qty is marked in red too, saying what the materials cover and by how much the run is over.
An explanation covers the figure it was given for, and no more. Going above that figure asks again — a sentence written about ten units is not an account of twelve — while re-saving the same figure, or correcting one downward, never asks. Lowering an over-recorded number must never need a justification, or people stop lowering them.
This is enforced on the work order itself, not on the form, so every door meets
it: the QC modal, Complete Work Order, and PATCH /api/v1/manufacturing/work-orders/{id} writing quantity_produced directly.
The API refuses an unexplained overrun with a 422 under overrun_reason and
leaves the column where it was; send overrun_reason alongside
quantity_produced to record a genuine yield gain, and read the shortfall back
from unaccounted_quantity and unaccounted_material_cost. How much the current
explanation covers comes back as overrun_reason_quantity.
A product with no bill of materials has no material lines and so nothing to check its output against. The work order says so on its own page rather than showing a row of zeroes that reads as "no material used" — add components to the product to have material cost and shortfalls tracked.
Quality checks #
The Quality Checks tab is where inspection results are logged. A quality check records:
- Result — Pass, Rework, or Reject.
- Checked At — the timestamp of the inspection (defaults to now).
- Qty Inspected and Qty Passed — the difference implies the rejection count. Qty Passed cannot exceed Qty Inspected: on a Pass it becomes the work order's produced quantity and creates that many units, so a typo here would mint stock that was never made. And on a Pass, Qty Passed above what the issued materials account for asks you where the extra units came from before it will record anything — see Unaccounted material. A Rework or Reject creates no stock and is never held up by this.
- Inspector — the user who performed the check.
- Defect Notes — description of what was wrong.
- Corrective Action — what was done to address the defect.
Each inspection is stored as a separate row with its own result; you can edit or delete individual check records after the fact. When a work order is in Quality Check status you can record results here and then advance the order to Completed once it passes.
Relationship to inventory #
Completed work orders produce units: when you record quantity_produced on
completion, the finished units appear in the output location as new
inventory units, in that location's receiving
bin — or, failing that, wherever the product was last put away there. This is
the same rule receiving follows, and it is why an in-stock unit always has a bin
somebody can walk to. On the material side, consumed units are marked used and
their movement history records the work order as the source. This keeps the full
audit trail intact across both the work order and the inventory unit.
For the Product's BOM see Products; for the location the output lands in see Locations & Bins.
Doing it from the API #
# Create a work order
curl -X POST "https://your-domain.com/api/v1/manufacturing/work-orders" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"product_id": "<product-id>",
"quantity_planned": 10,
"output_location_id": "<location-id>",
"due_at": "2026-07-01T08:00:00Z"
}'
# List work orders filtered by status
curl "https://your-domain.com/api/v1/manufacturing/work-orders?status=in_progress" \
-H "Authorization: Bearer $TOKEN"
# Advance a work order one stage. Only the moves listed under
# "Work order statuses" above are accepted — anything else comes back 422:
# {"message": "A draft work order cannot be moved straight to that status.
# From Draft this work order can move to: Scheduled, In Progress
# or Cancelled.",
# "errors": {"status": ["A draft work order cannot be moved straight ..."]}}
# Nothing else in a refused request is saved, so retrying with a legal status
# is safe.
curl -X PATCH "https://your-domain.com/api/v1/manufacturing/work-orders/<work-order-id>" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"status": "in_progress"}'
# Record a quality check result
curl -X POST "https://your-domain.com/api/v1/manufacturing/quality-checks" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"work_order_id": "<work-order-id>",
"result": "pass",
"quantity_inspected": 10,
"quantity_passed": 10,
"inspector_id": "<user-id>"
}'
Material and operation records are also available at
/api/v1/manufacturing/work-order-materials and
/api/v1/manufacturing/work-order-operations — filter both by work_order_id
to scope them to a specific order. See the full
API reference for all available
filters and fields.