Key takeaways
A manufactured part is defined by two structures, maintained separately and used together. The bill of materials answers what goes in, and the routing answers how and where it is made.
For that difference in plain language, read our guide to BOM vs routing. This page is the tables, the fields and the keys.
| Object | What it answers | One row per |
|---|---|---|
| Item master | What is this thing | Part number |
| BOM header | Which recipe version | Parent part and revision |
| BOM line | What goes in, how much | Component on that BOM |
| Routing | Which method version | Part and revision |
| Operation | How, where, how long | Step in the routing |
| Work centre | The capacity it runs on | Machine, cell or bench |
The fifth object, the work centre, deserves a real table with its own key, because the routing points at it and so does every machine record on the floor.
A BOM line and a routing operation never reference each other by name. They meet through one number, the operation sequence.
One row per part number, whatever the part is. A purchased washer, a made sub-assembly and a phantom kit share one table, which lets a BOM line point at anything without special cases.
| Field | What it holds |
|---|---|
| part_ | The key. Never reused, never edited |
| description | Short noun phrase, not a sentence |
| item_ | Make, Buy or Phantom |
| uom | Base unit: pcs, kg, m, L |
| unit_ | Net weight in the stated unit |
| revision | Drawing or engineering level |
| lifecycle_ | Design, Active, Phase out, Obsolete |
| lot_ | Yes forces a lot number on every move |
| serial_ | Yes forces a serial per unit |
| shelf_ | Sets expiry at receipt or production |
| default_ | Preferred source for Buy items |
item_type decides which other structures may exist. A Make item is allowed a BOM and a routing, a Buy item neither, and a Phantom a BOM but no routing.
Enforce that in the database, because a Buy item with a routing is how a purchased part starts consuming capacity in your plan.
The two types need different minimums, and blurring that fills the master with placeholders.
| Field | Purchased | Manufactured |
|---|---|---|
| part_ | Yes | Yes |
| item_ | Buy | Make |
| default_ | Yes | No |
| revision | Only if drawn | Yes |
| BOM and routing | Must be empty | Yes, one active each |
| unit_ | If shipped by weight | Derive from the BOM |
| shelf_ | If perishable | If perishable |
| lot_ | Per material risk | Per customer or law |
Lot control is a decision about recall exposure, modelled in our lot traceability data model. Derive a made part's weight from its BOM rather than typing it.
One row per parent part, per revision, per site, holding no components. It says which version of the recipe applies, and when.
| Field | What it holds |
|---|---|
| bom_ | The key, for example B-4471-C |
| parent_ | The item this BOM builds |
| revision | BOM level, tracked apart from the drawing |
| effective_ | First date it may be used |
| effective_ | Last date, or open |
| quantity_ | 1, 100 or 1,000. Read the next section |
| bom_ | Draft, Released, Superseded |
| alternative | Which of several valid recipes |
| site | Plant, when recipes differ by plant |
Keep the BOM revision separate from the drawing revision, because a supplier swap changes the BOM without touching the drawing.
The natural key is parent_part_no plus revision plus site, and it must be unique, or two people build two BOMs for the same part and both look correct.
A BOM line's qty_per is meaningless on its own. It is a quantity per quantity_basis parent units, and the basis lives on the header.
A basis above 1 exists so that adhesive is written as 3.5 litres per 1,000 units, a number people do not mistype.
| Basis | qty_per | Per one parent |
|---|---|---|
| 1 | 2 | 2 |
| 100 | 2 | 0.02 |
| 100 | 200 | 2 |
| 1,000 | 3.5 | 0.0035 |
A line written as 200 per 100 parents means 2 each, and read as 200 each it is a hundredfold error. No alarm fires, because the number is still a valid quantity.
Store the basis on the header and let no line override it, show the derived per-unit figure next to qty_per, and make a tenfold jump in that figure ask for confirmation.
One row per component, with its own sequence number so two lines can carry the same part.
| Field | What it holds |
|---|---|
| bom_ | The key, 10, 20, 30 with gaps |
| component_ | What goes in |
| qty_ | Quantity per quantity_basis parents |
| uom | Component unit, may differ from parent |
| scrap_ | Extra to issue, as a share |
| operation_ | Which operation consumes it |
| is_ | Blow through instead of stocking |
| substitute_ | Lines that may replace each other |
| reference_ | Where it sits: R12, R13, bolt hole 4 |
| effective_ | Line dates, inside the header window |
| issue_ | Manual issue or backflush |
Reference designator is the field electronics plants cannot live without and mechanical plants usually skip. If a BOM says four capacitors and the board has positions C4, C7, C11 and C19, the designators are what lets an operator and a placement program agree, so keep their count equal to qty_per.
The uom on the line is the component's unit, not the parent's.
The routing header mirrors the BOM header almost field for field. Both are versioned definitions of the same part, and both need the same effectivity rules.
| Field | What it holds |
|---|---|
| routing_ | The key, for example R-4471-C |
| part_ | The item this method makes |
| revision | Process level, tracked apart from the BOM |
| effective_ | Window in which it may be used |
| routing_ | Draft, Released, Superseded |
| alternative | Second method, for example a manual cell |
| lot_ | Lot the times were measured on |
Times measured on a lot of 500 do not describe a lot of 20, which is what lot_size_basis records. Where those times come from is our time study data model.
| Operation field | What it holds |
|---|---|
| operation_ | 010, 020, 030 with gaps for inserts |
| work_ | Where it runs |
| description | Verb first: Weld frame, Deburr bore |
| setup_ | Minutes per order, not per part |
| run_ | Time for one unit at rated speed |
| run_ | min/pc, pc/h or h/100. State it |
| queue_ | Expected wait before the step |
| move_ | Transport to the next step |
| overlap_ | Send ahead quantity or percentage |
| tool_ | Die, fixture or program needed |
| yield_ | Share that survives this step |
| is_ | Nothing passes without a result |
| crew_ | People for setup and for run |
Setup is paid once per order and does not move with quantity. Run time is paid once per unit, and this is the field pair most often collapsed into one number.
The routing below carries 155 minutes of setup across four operations. On an order of 500 units the planned total is 3,351.2 minutes, so setup is 4.6% of it, while on 50 units the total is 478.4 minutes and the same setup is 32.4%.
Fold setup into run time by dividing 155 by 500, and the 500 unit order is still right while the 50 unit order is planned at 338.9 against a real 478.4.
That routing overruns by roughly 40% on every small order, and the report calls it a performance problem at the work centre. It is a data model problem.
queue_time and move_time are lead time, not work, so they belong in the schedule and never in the cost of the operation. overlap_rule lets the next operation start on a send ahead quantity, and without it the schedule moves the whole lot at once and quotes far longer than the floor needs.
The routing points at a work centre and the work centre points at the physical assets. That chain is what lets machine data be scored against a routing.
| Work centre field | What it holds |
|---|---|
| work_ | The key, for example WC-WELD-02 |
| description | Weld cell 2 |
| asset_ | The machines inside it |
| capacity | Machines and people available |
| calendar | Shifts, breaks, planned shutdowns |
| cost_ | Machine rate and labour rate |
| efficiency_ | Planning derate, kept visible |
One work centre holds several assets, and an asset belongs to one work centre, as in our asset hierarchy data model.
Keep the efficiency_factor as its own field rather than baking it into run times, because a visible derate can be corrected while a padded run time reads as a slow process.
The two structures are joined by operation_seq on the BOM line. That number decides when material is needed, where it is issued, what the work in progress is worth, and how much of a component the order needs.
| Method | What it costs you |
|---|---|
| Manual issue | One transaction per pick. Accurate, and it catches a wrong part at the bench |
| Backflush | No picking transaction. Stock is only as right as the reported quantity and the qty_per |
Backflush deducts qty_per multiplied by the quantity reported at the consuming operation, and it is the default in most plants for fasteners, adhesives and paint.
It goes wrong in one way: a part scrapped after the material was already in it still consumed that material, so backflush against the operation's input quantity, not its good output. High value and lot controlled components should stay on manual issue whatever the transaction cost.
Many systems default operation_seq to the first operation when the field is left empty. It is the most expensive default in the model, and four things break at once.
Material requirements are dated to the start of the job, so parts arrive early and sit. Work in progress value jumps to its full amount at the first step, so a job that is 10% done looks 100% funded.
Backflush deducts paint when the first part is cut, so stock is wrong all run, and the shortage list is useless because everything is short on day one.
Set the field on every line. A component is consumed at the first operation that physically needs it in the hand.
Effectivity answers which version applies to a given order.
| Approach | Key field | Use when |
|---|---|---|
| Date | effective_ | Change applies from a date |
| Unit | from_ | Change applies from a build number |
| Lot | from_ | Process change tied to a batch |
Date effectivity is the common case and the easier one. It fits a supplier change or a material substitution that applies to whatever is built from Monday.
Unit effectivity is what regulated and long-lived products need, and it costs more to run. When a customer asks what was in serial 00412, a date is not an answer, because the build spanned a revision change.
An edit in place has no time dimension. It changes what the part was made of last March as well as what it will be made of tomorrow.
Costs then recalculate against a recipe never used, a trace returns today's design rather than the components actually fitted, and a job on the floor changes under the operator's hands.
The correct move is always the same. Close the old line with an effective_to date, open a new line with an effective_from date, and raise the BOM revision.
Treat Released as immutable, accepting only the effectivity dates that retire it.
At release the order takes a snapshot. What it copies and what it merely references is the difference between defensible history and guesswork.
| Copied into the order | Kept as a reference |
|---|---|
| bom_id and revision | part_no |
| Each line: component, qty_per, scrap_factor, operation_seq | Item master description and uom |
| routing_id and revision | work_ |
| Each operation: setup, run, yield, tool_no | Work centre calendar and rates |
| Computed required quantities | Supplier and stock records |
Anything that defines what this order was told to do is copied, and anything that only names an object is referenced. A shift calendar can change without rewriting history, but a qty_per cannot.
The full field set on the order itself is our production work order data model.
Each of these three solves a real problem, and each has a failure mode that is easy to reach.
| Device | What it is for | Failure mode |
|---|---|---|
| Phantom | Group parts without stocking them | A real stocked part flagged phantom vanishes from stock |
| Substitute | Allow an approved alternative | Silent swap, so the unit's real content is unknown |
| Scrap factor | Issue extra for known loss | Set at the wrong level, or stacked on a yield |
A phantom exists in the structure but never in stock, and the explosion blows straight through it to its children. Use it for a kit of fasteners, or a sub-assembly built and consumed in the same operation without being put away.
Flag a genuinely stocked part as phantom and its on hand quantity is ignored, so the children are ordered again and the shelf fills up.
A substitute_group marks lines that may stand in for each other, usually with a priority and a date window. The failure mode is allowing the swap without recording it.
If the order does not store which approved part was fitted, that unit's genealogy is a guess. Book the substitution on the order line with part, quantity and approval.
A scrap_factor issues extra material for loss that is known and accepted, such as offcuts or a purge at the start of a run.
Two conventions exist and systems disagree. Additive multiplies qty_per by one plus the factor, while yield style divides qty_per by one minus the factor.
At 5% on 1,000 pieces the first gives 1,050 and the second 1,053, so write down which your system uses. Set at the wrong level it buys extra of the wrong part, and stacked on an operation yield it orders the same loss twice.
A scrap factor is an allowance, not a measurement. Track real losses through scrap records with reasons, as in our scrap reason code taxonomy, and review the factors against them.
Order MO‑4471‑08 asks for 500 good units of PART‑4471 revision C. Every number below, including both error cases, comes from the two tables that follow.
| BOM B-4471-C (basis 1) | qty_per | At op |
|---|---|---|
| SUB-2210 sub-assembly | 2 pcs, scrap 3% | 010 |
| HW-0801 bolt | 8 pcs, scrap 1% | 030 |
| PNT-100 paint | 0.12 L, scrap 6% | 040 |
| Routing R-4471-C | Setup + run | Yield |
|---|---|---|
| 010 Fit subs, WC-ASSY-01 | 30 min + 1.80 min/pc | 100% |
| 020 Weld, WC-WELD-02 | 45 min + 2.40 min/pc | 98% |
| 030 Bolt and torque, WC-ASSY-02 | 20 min + 1.20 min/pc | 100% |
| 040 Paint and cure, WC-PAINT-01 | 60 min + 0.90 min/pc | 100% |
Yield runs from the last operation back to the first, because the 500 is a requirement for good output, not for input.
Operations 040 and 030 pass everything, so both need 500 in. Operation 020 passes 98%, so it needs 500 divided by 0.98, that is 510.20, rounded up to 511.
Operation 010 has no loss, so the order releases 511 units, 11 of them expected to be lost at welding.
Each component is sized on the input quantity of the operation that consumes it, then increased by its scrap factor, rounding discrete parts up.
| Component | Calculation | Required |
|---|---|---|
| SUB-2210 at op 010 | 511 x 2 x 1.03 = 1,052.66 | 1,053 pcs |
| HW-0801 at op 030 | 500 x 8 x 1.01 = 4,040 | 4,040 pcs |
| PNT-100 at op 040 | 500 x 0.12 x 1.06 = 63.6 | 63.6 L |
The sub-assembly is sized on 511 because it is fitted before the weld loss, the bolts on 500 because they go on after it. Get that wrong on the bolt line and you order 4,088.
SUB‑2210 has its own BOM, B‑2210‑A, on a quantity_basis of 100. The 1,053 sub-assemblies are its parent quantity.
| B-2210-A (basis 100) | Calculation | Required |
|---|---|---|
| RAW-5590, qty_per 100 | 1,053 x 100 / 100 | 1,053 blanks |
| CLIP-77, qty_per 200, scrap 2% | 1,053 x 2 x 1.02 = 2,148.12 | 2,149 pcs |
Read qty_per 100 as a per-unit figure and the steel requirement becomes 105,300 blanks instead of 1,053. The only thing between that and a purchase order is somebody noticing the number is large.
Planned minutes are setup plus run time multiplied by the quantity that passes through the operation, and that quantity changes at operation 020.
| Operation | Calculation | Minutes |
|---|---|---|
| 010 | 30 + 511 x 1.80 | 949.8 |
| 020 | 45 + 511 x 2.40 | 1,271.4 |
| 030 | 20 + 500 x 1.20 | 620.0 |
| 040 | 60 + 500 x 0.90 | 510.0 |
| Total | 155 setup + 3,196.2 run | 3,351.2 |
That is 55.9 hours of planned work, 155 minutes of it setup, and welding is the constraint. Check it two ways: the four lines add to 3,351.2, and 155 setup plus 3,196.2 run give the same.
Now recompute with operation 020 at 100% yield, which is the default. The release quantity becomes 500, and every operation is planned on 500 units.
| Figure | Correct | Yield omitted |
|---|---|---|
| Released at op 010 | 511 | 500 |
| Good units out | 500 | 490 |
| Planned minutes | 3,351.2 | 3,305.0 |
| SUB-2210 required | 1,053 | 1,030 |
The order finishes 10 units short, because 500 released at 98% yield returns 490 good. Planned time is understated by 46.2 minutes, and the sub-assembly requirement drops to 1,030.
Both halves reconcile. The 11 missing units explain 11 times 1.80 plus 11 times 2.40, which is 46.2 minutes, and 11 times 2 times 1.03 rounds to the 23 missing sub-assemblies.
The 3% belongs on the SUB‑2210 line of the parent BOM, because subs are damaged during fitting at operation 010. Suppose it goes on the RAW‑5590 line one level down instead.
| Figure | Correct | Factor moved down |
|---|---|---|
| SUB-2210 planned | 1,053 | 1,022 |
| RAW-5590 blanks | 1,053 | 1,053 |
| Usable subs after fitting | 1,022 | 991 |
| Good units out | 500 | 485 |
The steel requirement is identical at 1,053 blanks, which is exactly why this error survives review. The number everyone checks does not move.
What moves is the sub-assembly build quantity: 1,022 instead of 1,053. At 0.70 minutes per sub on the press, those 31 missing subs are 21.7 minutes cut from the sub-assembly order.
The fitting loss then hits the smaller pool, and 3% of 1,022 is 30.66, so 31 are damaged and 991 usable subs remain.
At two per unit those fit 495 units, and 495 through a 98% weld yield gives 485 good. The order finishes 15 units short, with 31 blanks stranded as work in progress.
Both errors share a signature: consistent arithmetic, no validation, and a shortage that appears at the end of the job.
The order leaves the office with a frozen copy of both structures, and comes back with what actually happened, operation by operation.
The reporting loop needs four values per operation: the input quantity, the good quantity, the scrap quantity with a reason code, and the actual setup and run minutes.
Good divided by input is the operation's measured yield, and against the stored yield_percent it says whether the model or the process has drifted.
Actual minutes against setup_time plus run_time_per_unit is the same test for the times. If welding takes 1,400 minutes rather than 1,271.4, either the standard is optimistic or the work centre is losing time to stops.
Separating those two explanations is what OEE is for: availability for time lost to stops, performance for speed against the routing's rate, quality for what survived.
That makes run_time_per_unit the ideal cycle time performance is scored against, and our guide to cycle time covers how to set it. The part number must travel with the machine record, or the two systems disagree about which part was running.
The ERP owns the item master, the BOMs, the routings and the order header, while the floor systems own the events. Our pages on the manufacturing execution system and the CMMS data model cover that boundary.
One rule keeps it coherent: every field has exactly one owning system, and the others read it.
Ask each one against your own part, in a live system. Sample data only proves the sample data was built to work.
Fabrico does not maintain BOMs or routings, and it does not do MRP, production planning or scheduling. Those structures live in your ERP or MES, and Fabrico reads them rather than owning them.
What Fabrico owns is the machine side. It measures OEE, downtime, short stops and cycle times through PLC connections, IoT sensors and AI cameras.
The link to this model is deliberately narrow. The part number travels with the machine data, so a stop or a slow run is attributed to the part that was running, and run_time_per_unit can be compared with what the machine actually achieved.
Whether the operation and the work centre come across as well depends on what your ERP or MES sends over the integration. Ask for that mapping in writing before you assume a downtime report can be split by operation.
On the maintenance side, Fabrico is a full CMMS: your team creates maintenance work orders, runs preventive plans and tracks spare parts consumed per work order, from web or mobile apps. That inventory covers maintenance spares, not production components.
The AI assistant answers questions about a machine's losses and history in plain language.
Want to see whether your routing times match what your machines really do? Book a 30 minute demo with a Fabrico consultant, no commitment, or contact us with your questions.
The header needs bom_id, parent_part_no, revision, effective_from and effective_to, quantity_basis and status. Each line needs component_part_no, qty_per, uom, scrap_factor and operation_seq, plus is_phantom, substitute_group and reference_designator where they apply.
The BOM is a parent item with component lines and quantities, so it answers what goes in. The routing is a parent item with operation lines, work centres and times, so it answers how and where it is made.
It is the number of parent units that qty_per refers to, usually 1, 100 or 1,000. A line of 200 on a basis of 100 means 2 per unit, and reading it as 200 per unit multiplies the requirement by a hundred.
No, because setup is paid once per order and run time is paid per unit. Combining them plans every order size wrongly except the one the standard was built on.
Because a later revision would otherwise rewrite orders already running or already closed. A copy keeps costs, traceability and the pick list consistent with what was built.