Work Centers & BOM (Mfg)
Define the machines and stations where production happens, and the multi-level recipes (Bills of Materials) that say exactly what each product is made of and how it is built — the engineering master data that feeds every Work Order downstream.
production/WorkCenters and production/Bom, are gated by the
manufacturing_* permissions, and store their data in dedicated
manufacturing_* tables. They are separate from the lightweight Base
Production screens (production/Work_centers and production/Boms,
described in Work Centers and
Bill of Materials). In the sidebar they read
"BOM (Mfg)" vs. "Bill of Materials" — don't confuse the two entries.
How the Mfg versions differ from the base versions
The advanced Work Center and BOM screens add the structure a real shop floor needs. Compared to the base equivalents, they give you:
manufacturing_* tablesWork centers store to manufacturing_work_centers; BOM headers,
items and routing store to manufacturing_bom, manufacturing_bom_items
and manufacturing_bom_operations. These are distinct from the base Production
tables, so the two areas never share records.
Every BOM header carries a free-text Version, a status (Draft → Pending Approval → Approved → Obsolete), an effective-date window, and Active / Default flags — so the right revision is chosen when orders are raised.
A BOM's component lines (Items) and routing steps (Operations) are added and removed through inline modals launched from the list — you never leave the BOM list to build the recipe.
A BOM Item can be typed as a Sub Assembly — a part that is itself
manufactured. The manufacturing_bom_items table stores a
parent_bom_item_id link, so recipes can nest into a true multi-level BOM.
Product name and each Item Name exactly. The underlying
product_id / item_id default to 0 and are not set from the
UI, so a Mfg BOM is not truly joined to the Inventory items master. Typos create
disconnected recipes. (The base Bill of Materials screen behaves
differently — check which one you are on.)
Work Center — fields
A Work Center is any production resource where work is performed: a machine, a manual labor station, an assembly cell, an inspection point, or a packaging station. Each one carries a capacity per hour, an hourly cost and a default setup time. Open Manufacturing → Work Centers and click Add Work Center to create one.
| Field | Meaning | Required | Notes |
|---|---|---|---|
code — Code |
Short unique identifier for the work center (e.g. WC-001). | Required | Free text. Placeholder "e.g. WC-001". The database enforces uniqueness of
code among non-deleted rows — a duplicate save fails with a generic error, not a
friendly "code exists" message. |
name — Name |
Human-readable work center name. | Required | Free text. The list is sorted alphabetically by Name. |
work_center_type — Type |
Category of the resource. | Optional | Select2 dropdown: Machine, Labor, Assembly, Inspection, Packaging. Defaults to Machine on new records (so the form is never blank). Validated as required on save; rendered as a coloured label in the list. |
description — Description |
Free-text notes about the work center. | Optional | Textarea, 3 rows. |
capacity_per_hour — Capacity/Hr |
How many units this work center can process per hour. | Optional | Number, step 0.01, defaults to 0. Stored as NULL if left empty. Shown in the list with its
capacity UOM symbol, or - if zero/empty. |
capacity_uom_id — Capacity UOM |
Unit of measure for the capacity figure. | Optional | Dropdown of active manufacturing UOMs (name + symbol) with a blank - option.
Stored NULL if blank. |
cost_per_hour — Cost/Hr |
Hourly operating cost of this work center (used for costing operations). | Optional | Number, step 0.01, defaults to 0. Shown formatted to 2 decimals. |
setup_time_minutes — Setup Time (min) |
Default minutes to set up this work center before a run. | Optional | Integer, min 0, step 1, defaults to 0. Stored on the work center — but each operation captures its own setup time separately and does not auto-copy from here. |
location — Location |
Physical location / area of the work center. | Optional | Free text. Shown as - if empty. |
is_active — Active |
Whether the work center is available for use. | Optional | Checkbox, checked by default on new records. Only active work centers appear in the BOM Operations work-center dropdown. Inactive rows show a grey "Inactive" label. |
Work Center Type & status legend
The Work Centers list
The work-centers-table DataTable shows one row per work center with these columns:
| Column | Shows | Notes |
|---|---|---|
| Code | The unique code. | — |
| Name | The work center name. | List is sorted alphabetically by this column. |
| Type | Machine / Labor / Assembly / Inspection / Packaging. | Rendered as a coloured label. |
| Capacity / Hr | Capacity figure + UOM symbol. | Shows - if zero or empty. |
| Cost / Hr | Hourly cost, 2 decimals. | — |
| Location | Physical area. | Shows - if empty. |
| Status | Active (green) or Inactive (grey). | — |
| Actions | Per-row cog (gear) menu: Edit, Delete. | Not exported. |
work_center_type filter is supported by the controller, but the list view does not
render a filter dropdown for it, so it is effectively unused in the UI.
BOM anatomy — Header, Items, Operations
A Bill of Materials (BOM) is the recipe for a product. It has three parts, each edited on its own screen and each with its own field table below:
BOM Header — fields
The header is the recipe's top-level definition: what product it makes, how many units it
yields, its version, status and validity window. Open Manufacturing → BOM and click
Add BOM (the production/Bom/modal_form screen).
| Field | Meaning | Required | Notes |
|---|---|---|---|
bom_code — BOM Code |
Unique code identifying this BOM. | Required | On a new BOM the field is pre-filled with the next suggested code
(prefix BOM + 6-digit zero-padded sequence, e.g. BOM-000001).
The DB enforces uniqueness among non-deleted rows. |
product_name — Product |
Name of the finished product this BOM produces. | Required | Free text — there is no product picker; type the name exactly. A hidden
product_id (defaults 0) exists but is not exposed on the form. |
bom_type — Type |
Nature of the BOM. | Optional | Dropdown: Manufacturing, Assembly, Kit, Engineering. Defaults to Manufacturing. Rendered as a coloured label in the list. |
version — Version |
Revision label of the BOM. | Optional | Free text, defaults to 1.0. Used to distinguish recipe revisions of the
same product. |
quantity — Quantity |
How many units of the product this BOM yields (the base/batch quantity that item quantities are relative to). | Required | Numeric, step 0.0001, defaults to 1. Shown to 4 decimals on the view page. |
uom_id — UOM (next to Quantity) |
Unit of measure for the produced quantity. | Required | Dropdown of active UOMs. Uses validate-hidden so the required rule still
fires even though Select2 hides the native control. |
status — Status |
Lifecycle stage of the BOM. | Optional | Dropdown: Draft, Pending Approval, Approved, Obsolete. Defaults to Draft. Freely selectable — there is no enforced workflow guard. |
effective_from — Effective From |
Date this BOM version becomes valid. | Optional | HTML date input. Stored NULL if blank; shows N/A on the view page when empty. |
effective_to — Effective To |
Date this BOM version stops being valid. | Optional | HTML date input. Stored NULL if blank; shows N/A on the view page when empty. |
notes — Notes |
Free-text remarks about the BOM. | Optional | Textarea, 3 rows. Shown on the view page (HTML-escaped, line breaks preserved). |
is_active — Active |
Whether this BOM is currently usable. | Optional | Checkbox, checked by default on new BOMs. Independent of the status field. |
is_default — Default BOM |
Marks this BOM as the primary recipe for its product. | Optional | Checkbox, unchecked by default. No uniqueness is enforced — nothing stops you flagging several BOMs for the same product as Default. |
status enum. The separate
approval_status, approved_by and approved_at columns
are never populated by these screens — there is no distinct "approve" action, just the
Status dropdown.
BOM Item — fields
Each BOM Item is one material line — a raw material, component, sub-assembly or consumable that
the product consumes. Add items via the BOM Items modal
(production/Bom/items_modal/{bom_id}), reached from the cog menu on the BOM row.
There is an inline add-row form; the form resets after each successful add.
| Field | Meaning | Required | Notes |
|---|---|---|---|
item_name — Item Name |
Name of the material/component consumed. | Required | Free text — no item picker. Placeholder "Item Name *". A hidden
item_id (defaults 0) exists but is not exposed. |
item_type — Item Type |
Classification of the component. | Required | Dropdown: Raw Material, Component, Sub Assembly, Consumable. No blank default is preselected. Use Sub Assembly for parts that are themselves manufactured — this is what makes the BOM multi-level. Rendered as a coloured label. |
quantity — Qty |
How much of this material is needed (per the BOM's produced quantity). | Required | Numeric, step 0.0001. Shown to 4 decimals with UOM symbol. |
uom_id — UOM |
Unit of measure for the item quantity. | Required | Dropdown of active UOMs. The add-form control is not marked required in the HTML, but
the controller validates uom_id as required|numeric on save. |
scrap_percentage — Scrap% |
Expected waste percentage for this material. | Optional | Numeric, step 0.01, defaults 0. Shown as e.g. 2.00%. |
is_optional — Optional |
Whether the item is optional rather than mandatory. | Optional | Not present in the inline add form — always saved as 0 (Required)
when added here. The list/view still displays Optional vs. Required. |
notes — Notes |
Free-text remarks for the item. | Optional | Not present in the inline add form; stored blank when added here. Displayed in
the items table (- if empty). |
bom-items-table columns are: Item Name, Type, Quantity, Scrap %, Optional,
Notes, and a red X to delete the line. Because the add form omits
is_optional and notes, every item added here shows as
Required with an empty Notes cell.
BOM Operation — fields
Each BOM Operation is one routing step (e.g. Cutting, Welding), numbered in sequence and
performed at a Work Center. Add operations via the Operations modal
(production/Bom/operations_modal/{bom_id}), reached from the cog menu on the BOM
row. There is an inline add-row form; the form resets after each successful add.
| Field | Meaning | Required | Notes |
|---|---|---|---|
operation_number — # |
Sequence number of the routing step; operations sort ascending by this. | Optional | Integer, defaults to 10 (convention: 10, 20, 30… to leave gaps for insertion). Falls back to 10 server-side if left blank. |
operation_name — Operation |
Name of the routing step (e.g. Cutting, Welding). | Required | Free text. Placeholder "Operation *". |
work_center_id — Work Center |
The work center where this operation is performed. | Required | Dropdown showing code - name of active work centers only.
Validated required|numeric. If no active work centers exist you cannot add an operation. |
setup_time_minutes — Setup |
Minutes to set up before running this operation. | Optional | Integer, defaults 0. Shown as "N min". Captured per-operation — does not auto-copy from the work center's own setup time. |
run_time_per_unit_minutes — Run/Unit |
Minutes to process one unit at this operation. | Required | Numeric, step 0.01. Shown to 2 decimals + " min". |
labor_cost_per_hour — Labor/Hr |
Hourly labor cost for this operation. | Optional | Numeric, step 0.01, defaults 0. Shown to 2 decimals. |
machine_cost_per_hour — Mach/Hr |
Hourly machine cost for this operation. | Optional | Numeric, step 0.01, defaults 0. Shown to 2 decimals. |
overhead_percentage — Overhead % |
Overhead percentage applied to this operation's cost. | Optional | Stored in the DB (defaults 0) and accepted by the server, but has no input in the add form and is not shown in the operations table. |
description — Description |
Free-text description of the operation. | Optional | Accepted by the server but has no input in the add form. |
is_outsourced — Outsourced |
Whether the operation is done by an external supplier. | Optional | Accepted (defaults 0) but has no input in the add form, so operations added here are always in-house (Outsourced = No). Displayed as Yes (warning) / No. |
notes — Notes |
Free-text remarks for the operation. | Optional | Accepted by the server but has no input in the add form. |
description,
overhead_percentage, is_outsourced and notes take
server defaults, so every operation added here is in-house (Outsourced = No), 0 overhead, and
has no description/notes — even though the bom-operations-table shows an
Outsourced column (columns: #, Operation, Work Center, Setup, Run/Unit, Labor/Hr,
Machine/Hr, Outsourced, delete X).
The BOM list & full-page view
Bill of Materials list
Open Manufacturing → BOM (production/Bom) for the master list. It carries a
Status filter dropdown (Draft / Pending Approval / Approved / Obsolete / blank = all) and
the bom-table DataTable:
| Column | Shows | Notes |
|---|---|---|
| BOM Code | The unique bom_code. | — |
| Product | The free-text product name. | — |
| Version | Revision label. | — |
| Type | Manufacturing / Assembly / Kit / Engineering. | Coloured label. |
| Quantity | Produced quantity. | — |
| Status | Draft / Pending / Approved / Obsolete. | Coloured status label. |
| Created By | User who created the BOM. | — |
| Created | Creation date. | — |
| Actions | Cog menu: View, BOM Items, Operations, (divider), Edit, Delete. | Not exported; Excel export covers columns 0–7. |
BOM full-page view
The cog menu's View opens a read-only page (production/Bom/view/{id}) headed
"{BOM Code} - {Product Name}" with a Back to List button. It shows everything in one
place — no editing happens here:
- Header panel
- BOM Code, Version, Type, Status badge, Quantity (4 decimals), Effective From, Effective To (each N/A if empty), Created, and Notes.
- BOM Items table
- Item Name, Type, Quantity, Scrap %, Optional, Notes. Reads "No items added yet" when empty.
- Operations table
- #, Operation, Work Center, Setup Time, Run Time/Unit, Labor Cost/Hr, Machine Cost/Hr, Outsourced. Reads "No operations defined" when empty.
id returns a
404 (Not Found) page.
BOM status lifecycle
A BOM header moves through four statuses. The intended flow is left to right, but note the warning below — the code does not enforce it.
manufacturing_bom
permission can set a BOM directly from Draft to Approved to Obsolete — or backward — by editing it
and picking a new value in the Status dropdown. There is no separate "approve" step and no guard
that prevents reactivating an obsolete BOM. Treat the pipeline as an intended convention,
not a locked process.
The Active and Default flags
Two checkbox flags on the header are independent of the status field:
is_active — the BOM is usable. Toggled on edit, independent of status.is_default — the primary recipe for its product. Uniqueness is NOT enforced.BOM Type & Item Type legends
Actions & permissions
Both screens require module access to Manufacturing plus one screen-level permission — the
granular *_create/_update/_delete keys exist in the permissions config but are
not individually enforced here; the base key gates every action on the screen.
| Action | What it does | Permission | Confirmation |
|---|---|---|---|
| Add / Edit Work Center | Opens the modal (empty for add, pre-filled for edit); Save inserts/updates a
manufacturing_work_centers row (UUID, created_by, timestamps). New
rows default is_active = 1. |
manufacturing_work_center |
None. Success shows the "record_saved" flash and injects/updates the row. |
| Delete Work Center | Soft-deletes the work center (deleted = 1). |
manufacturing_work_center |
Standard delete-confirmation dialog. |
| Add / Edit BOM | Inserts/updates a manufacturing_bom header (UUID, created_by,
timestamps). Server defaults: version 1.0, type manufacturing,
status draft, product_id 0 if none; new BOMs default
is_active = 1. |
manufacturing_bom |
None. Success flash "record_saved". |
| View BOM | Opens the read-only full-page view (header + all items + all operations). | manufacturing_bom |
None. |
| Delete BOM | Soft-deletes the header (deleted = 1). |
manufacturing_bom |
Standard delete-confirmation dialog. |
| Add / Delete BOM Item | Inserts a manufacturing_bom_items row (is_optional forced 0,
notes/item_id blank), or soft-deletes a line. |
manufacturing_bom |
Add: none (row injected, form resets). Delete: confirmation dialog. |
| Add / Delete Operation | Inserts a manufacturing_bom_operations row (op-number defaults 10; outsourced/
overhead/description/notes take server defaults), or soft-deletes a line. |
manufacturing_bom |
Add: none (row injected, form resets). Delete: confirmation dialog. |
| Filter BOMs by Status | Reloads the list filtered to the chosen status, or all when blank. | manufacturing_bom |
None. |
| Close (modal) / Back to List | Dismisses a modal (items/operations already added are already saved) or returns to the BOM list. | n/a | None. |
ON DELETE CASCADE foreign keys
from items/operations to the BOM, but because the delete only sets deleted = 1, that
cascade never fires — child item/operation rows are simply hidden with the parent, not physically
removed.
Validation & result messages
Missing required fields raise the field's client-side "field_required" message. The server validates:
- Work Center:
code,name,work_center_typerequired;idnumeric. - BOM header:
bom_code,product_namerequired;quantityanduom_idrequired|numeric;idnumeric. - BOM item:
bom_idrequired|numeric,item_namerequired,item_typerequired,quantityanduom_idrequired|numeric. - BOM operation:
bom_idrequired|numeric,operation_namerequired,work_center_idrequired|numeric,run_time_per_unit_minutesrequired|numeric.
record_saved); delete success shows "Record deleted successfully"
(record_deleted). A failed save shows the generic error_occurred message; a
failed delete shows "record could not be deleted"
(record_cannot_be_deleted). Opening an Items/Operations modal with no
bom_id returns {success:false, message: error_occurred}. Because
duplicate codes are caught at the DB level, a duplicate code/bom_code
fails with that generic error, not a friendly "code already exists" message.
SOP — Onboard a Work Center
manufacturing_work_center permission and the
Manufacturing module is enabled.
Open the list
Go to Manufacturing → Work Centers and click Add Work Center.
Identify it
Assign a unique Code (e.g.
WC-CNC-01) and a clear Name (e.g. "CNC Milling Machine 1").Pick the type
Select the correct Type — Machine, Labor, Assembly, Inspection or Packaging.
Enter capacity & cost
Set Capacity/Hr with the matching Capacity UOM, and Cost/Hr (used for later costing). Add the default Setup Time (min) and Location if known.
Keep it Active
Leave Active checked so the center is selectable when you build BOM routing.
Save
Click Save and confirm the row appears with a green Active status.
SOP — Create a multi-level BOM
manufacturing_bom permission.
-
Create the BOM header
Manufacturing → BOM → Add BOM. Accept the pre-filled BOM Code (or set your own), type the Product name exactly, choose the Type, set the Version, Quantity and UOM. Keep Status = Draft while building, then Save.
-
Add the components (Items)
From the BOM row's cog menu, click BOM Items. In the inline row, type the Item Name, pick the Item Type, enter Qty + UOM, and optionally Scrap%. Click Add — the line appears and the form resets. Repeat for every component.
-
Nest sub-assemblies
For any part that is itself manufactured, set its Item Type = Sub Assembly. This is what makes the BOM multi-level — a sub-assembly should have its own BOM built the same way. (The
parent_bom_item_idlink underneath supports the nesting even though the list shows items flat.) -
Define the routing (Operations)
From the cog menu, click Operations. In the inline row, set the operation number (10, 20, 30…), type the Operation name, pick the Work Center (only active ones appear), and enter Setup and Run/Unit times (plus Labor/Hr and Mach/Hr if you are costing). Click Add; steps sort by number. Repeat in sequence.
-
Review the whole BOM
Use the cog menu's View to check the header, every item (correct qty/UOM/scrap), and the full routing in one read-only page before you release it.
-
Release it
Edit the BOM and change Status to Pending Approval, then (after review) to Approved. Ensure Active is checked, and tick Default BOM if this is the primary recipe. Set Effective From/To to control the validity window.
Where this data goes next
Nothing is produced on these two screens — they are pure master data. Once your Work Centers and an Approved BOM exist, they become the source for the shop floor:
manufacturing_uom (active
only — seeded units include Piece, Dozen, Kilogram, Gram, Liter, Milliliter, Meter, Centimeter,
Hour, Minute). Work-center cost_per_hour and operation labor/machine costs feed
production costing (manufacturing_standard_costs /
manufacturing_actual_costs). And changes to a BOM are meant to be governed by
Engineering Change Orders (manufacturing_eco, which reference bom_id) —
managed on a separate screen, not editable here.
BOM. The setting seeded as bom_code_prefix is not read by the
code generator (which looks for a bom_prefix key), so editing that setting will not
change the suggested prefix.