BOM & CPQ — turning a configuration into a parts list
Audience: developers & AI agents · Scope: the BOM & CPQ System as a whole; a map to its spoke docs · Last reviewed: 2026-07-25
TL;DR — A BOM (Bill of Materials) is the flat list of parts that make up one configured luminaire.
The site never calculates a BOM itself — Infor does. The site's job is to turn a configuration into
something Infor recognises, and there are exactly two ways it does that: the slash method (find an
existing Infor item whose name matches the configuration code, and serve that item's pre-exploded BOM from a
local cache) and CPQ (ask Infor's rule engine to configure the product from scratch and hand back
manufacturing data). Every quote line records which method answered it in bom_fetch_method.
This is a System: it owns a business capability, spans a page, cron jobs, admin tools and an outbound API, and shares one vocabulary across them. It builds on the Infor integration layer — that layer is a dependency, not a spoke.
The parts (map)
a configured quote line
(product + selections → code)
│
▼
┌──────────────────────────────────────┐
│ updatePositionMaterials() │ position-boms.md
│ the resolution ladder │
└───┬──────────────────────────────┬───┘
│ │
"slash method" "cpq"
match the code to an Infor item no match → ask the rule engine
│ │
▼ ▼
┌───────────────────┐ ┌──────────────────────┐
│ local BOM cache │ │ Infor CPQ (SOAP) │ cpq.md
│ bom-cache.md │ │ Configure → │
│ │ │ LoadMfgData │
│ infor_items │ └──────────┬───────────┘
│ infor_item_ │ │ part numbers
│ materials │◄────────────────────┘ + quantities
└─────────┬─────────┘ (exploded via the same MSSQL query)
│
▼
#__configbox_external_user_materials ← the answer, per quote line
│
├──► BOM status screen (agents/admins)
├──► costing / margin analysis, kitting, schedules
└──► outbound BOM REST API rest-api.md
| Spoke | Doc | Role |
|---|---|---|
| Position BOMs | position-boms.md | quote line → parts list: the resolution ladder, the bom_status states, the deferred post-response calculation, the failure register, and bulk recalculation |
| The BOM cache | bom-cache.md | the local mirror of Infor's exploded item BOMs — how items are picked for refresh, the in-progress flags, the nightly job, and the admin tools |
| Infor CPQ | cpq.md | the rule-engine path — the SOAP contract, how questions map to CPQ option lists, value overrides, and what the two failure keywords mean |
| BOM REST API | rest-api.md | the API the site exposes so other systems can ask for a BOM — OAuth2 client-credentials, the two endpoints, the response envelope |
End-to-end flow
- An agent adds or edits a line on a quote (
bcmyquotes→addPosition/editPosition). - The JSON response is sent, then
fastcgi_finish_request()closes the connection and BOM calculation runs after the user's request has returned — see position-boms.md. updatePositionMaterials()walks the resolution ladder: custom line → exact item-name match → exact order-line-code match → CPQ (if the product has a ruleset) → best partial match over 50% →no_bom.- Whichever branch wins, the resulting materials are written to
#__configbox_external_user_materialsfor that position, andbom_*columns on the position record how the answer was reached. - A CPQ failure additionally files a row in the fail register (
admincpqbomfails), which a weekday cron emails about. - Overnight,
cb_bom_cache_update.phprefreshes the cache for Infor items that are new or changed, so tomorrow's slash-method lookups are current.
Shared concepts
These are the terms every spoke uses. Defined once, here.
| Term | Meaning |
|---|---|
| Item | A part number in Infor (item_mst.item). A finished luminaire is an item; so is a screw. The site treats item names as opaque strings. |
| Material | An item consumed by another item's job. The BOM of item X is the set of materials under X. "Material" is always relative to a parent. |
| Root item | The top of the tree for one lookup — the finished luminaire whose BOM was requested. Stored as rootItem / the item column in the cache. |
| Flat BOM | The tree flattened to one row per material, with quantities already multiplied down the levels. Everything here deals in flat BOMs, never nested ones. |
| Configuration code | The slash-separated product code the configurator produces (e.g. ABC/12/WH/…). This is the join key between the website and Infor — see quotes. |
| The slash method | Resolving a BOM by matching the configuration code to an existing Infor item name (or to a known order-line code), then reading that item's cached BOM. Named for the slashes in the code. bom_fetch_method = 'slash_method'. |
| CPQ | Configure-Price-Quote — Infor's rule engine. Given a ruleset and a set of option-list values it configures a part and returns its manufacturing data. bom_fetch_method = 'cpq'. |
| PMT code | Infor's "product material type" code (item_mst.p_m_t_code), carried through on every material row. Used downstream for costing and grouping. |
path | A JSON array of item names from the root down to the material, e.g. ["LUM-1","SUB-2","SCREW"]. Doubles as the sort key and, hashed, as the de-duplication signature. |
| Nesting level | How deep the explosion goes. Capped at 5 (ConfigboxModelCsimaterials::maxNestingLevel); raising it means changing both the SQL and Infor's own load method. |
Quantity arithmetic — a material's quantity is the product of the quantities at every level from the
root down to it, times the root item's requested quantity. A screw used 4× in a sub-assembly used 2× in a
luminaire ordered 3× is 24. This multiplication happens when the flat BOM is built
(ConfigboxModelCsimaterials::getMaterialFromRow()), not when it is read.
Dependencies
- Infor / ERP — the integration layer. This System uses all three of its access paths: the direct MSSQL connection (BOM explosion and item lookups), the IDO REST API, and CPQ's own SOAP endpoint, which has its own settings and is not part of the IDO REST client.
- Quotes — positions are quote lines; the BOM columns live on the position record.
- Scheduled jobs — the cache refresh, bulk recalculation and fail-report emails.
- Monitoring — the
BOM-CacheandTest-BOM-CalculationCloudWatch namespaces. (The latter carries production metrics despite the name — seedocs/_known-issues.md.)
Cross-cutting ops
- Admin tools — three interactive BOM calculators plus the fail register; see bom-cache.md and cpq.md.
- Permission — reading BOM data on a quote line is gated on
com_configbox.core.manage(BcHelper::canSeeBomData()), so ordinary agents do not see it. - Settings — the CPQ group is documented in cpq.md; the Infor REST/DB groups in the Infor hub. There are no settings for the cache itself.
- Logs —
custom_bom_cache_caching,custom_bom_cache_loading,custom_position_bom_calculation,custom_infor_cpq. - Operator-facing how-tos live in the Admin Guide under Pricing, BOM & ERP
(
admin-guide/pricing-bom-erp/README.md).