The bom module in src/cluster_erp/modules/bom/ holds the bill of materials (BOM) of each manufactured product breakdown. A BOM card names the product, model, and version it builds; it has numbered versions that move from draft to active to superseded, and each version has a tree of items. The module explodes a version into effective quantities for costing, production, and planning, and it serves the BOM tree that the routing scaffold of the production module reads to derive the operation BOM (eBOM).
At a glance¶
| Item | Value | Source |
|---|---|---|
| Package | src/cluster_erp/modules/bom/ |
folder |
| Module class | Module |
src/cluster_erp/modules/bom/module.py:19 |
name / version |
bom / 0.1.0 |
src/cluster_erp/modules/bom/module.py:20-26 |
| API prefix | /api/v1/bom (ModuleBase.api_prefix) |
src/cluster_erp/core/module_base.py:37-38 |
| Sub-modules | 4: boms, bom_versions, bom_items, bom_templates |
folder listing |
| Tables | 5: bom_boms, bom_bom_versions, bom_bom_items, bom_bom_templates, bom_bom_template_items |
__tablename__ in the four models.py files |
| Entities in metadata | 5: bom, bom_version, bom_item, bom_template, bom_template_item |
src/cluster_erp/modules/bom/module.py:898-904 |
| Routes | 39 route decorators in the package | grep for @router. |
| Web app presence | Catalogue app bom, with the custom widget bom_explorer from a gated bundle |
src/cluster_erp/modules/app_store/catalog.json (entry bom), gated_apps/bom/app.js |
| Desktop presence | src/cluster_erp/desktop/modules/bom/ (bom_explorer.py, bom_builder.py) |
folder |
What the module does¶
- Keeps one BOM card per product breakdown with a base quantity, a usage type, and a status (
src/cluster_erp/modules/bom/boms/models.py:17-79). - Versions the structure: only drafts are editable, and at most one version per BOM is active (
src/cluster_erp/modules/bom/bom_versions/models.py:19-42). - Stores items as a tree inside a version, with find number, quantity, unit, scrap factor, cut sizes, make-or-buy choice, stock material, effectivity dates, and per-unit labour and cost figures (
src/cluster_erp/modules/bom/bom_items/models.py:18-142). - Copies reusable templates into a draft version (
src/cluster_erp/modules/bom/bom_versions/services.py:544-651). - Explodes a version into effective quantities and a rollup per breakdown (
src/cluster_erp/modules/bom/bom_versions/services.py:722-820). - Serves the explorer tree, where-used data, and a purchasing coverage snapshot (
src/cluster_erp/modules/bom/bom_versions/services.py:822-836,src/cluster_erp/modules/bom/bom_versions/services.py:1141-1151,src/cluster_erp/modules/bom/bom_versions/services.py:1519-1520). - Creates a BOM card with a first draft version when a design with a part is approved (
src/cluster_erp/modules/bom/module.py:667-775).
Sub-modules¶
| Sub-module | Purpose | Main entities | Router | Source |
|---|---|---|---|---|
boms |
BOM cards | Bom |
/api/v1/bom/boms |
src/cluster_erp/modules/bom/boms/routes.py:31 |
bom_versions |
Versions, lifecycle, explosion, tree, coverage, where-used | BomVersion |
/api/v1/bom/bom-versions |
src/cluster_erp/modules/bom/bom_versions/routes.py:26 |
bom_items |
Items of a version | BomItem |
/api/v1/bom/bom-items |
src/cluster_erp/modules/bom/bom_items/routes.py:31 |
bom_templates |
Templates and their items | BomTemplate, BomTemplateItem |
/api/v1/bom/bom-templates |
src/cluster_erp/modules/bom/bom_templates/routes.py:29 |
Data model¶
| Entity | Table | Key fields | Notes | Source |
|---|---|---|---|---|
Bom |
bom_boms |
bom_code, name, product_id, product_model_id, product_version_id, owner_design_id, base_quantity, usage_type, status, extra |
the three breakdown ids are required | src/cluster_erp/modules/bom/boms/models.py:17-79 |
BomVersion |
bom_bom_versions |
bom_id, version_no, status, created_from_template_id, total_weight_kg, total_labor_min, total_material_cost, total_labor_cost, approved_by, approved_at, valid_from |
one live active version per BOM; version_no unique per BOM |
src/cluster_erp/modules/bom/bom_versions/models.py:19-98 |
BomItem |
bom_bom_items |
bom_version_id, parent_item_id, part_id, part_model_id, part_version_id, find_number, qty, uom, level, effective_from, effective_to, cut_length_mm, cut_width_mm, sourcing, stock_material_id triple, scrap_factor, is_phantom, cost and labour per unit, source |
part_id is a product id |
src/cluster_erp/modules/bom/bom_items/models.py:18-142 |
BomTemplate |
bom_bom_templates |
template_code, name, family, description |
src/cluster_erp/modules/bom/bom_templates/models.py:13-31 |
|
BomTemplateItem |
bom_bom_template_items |
template_id, parent_item_id, part_id, find_number, qty, uom, cut_length_mm, is_phantom |
no model or version column | src/cluster_erp/modules/bom/bom_templates/models.py:34-81 |
Quantities and money are Numeric: item quantity Numeric(18, 4), costs Numeric(18, 4), scrap factor Numeric(6, 4) (src/cluster_erp/modules/bom/bom_items/models.py:62-128). The product ids are plain UUID columns; the bom module checks them through the EventBus, not through database foreign keys (src/cluster_erp/modules/bom/boms/services.py:203-213).
Screens and metadata¶
get_metadata declares a sidebar group BOM Management (Turkish Ürün Ağacı Yönetimi) with four sections: BOM Explorer (the custom widget bom_explorer), Bills of Materials, BOM Versions, and BOM Templates (src/cluster_erp/modules/bom/module.py:838-905). The explorer tabs are labelled Product Tree · BOM (webapp/i18n/en.json key bom_explorer.tab.cs03) and Operation BOM · eBOM (webapp/i18n/en.json key bom_explorer.tab.ca03). The product card shows a read-only Bill of Materials tab for manufacturable products (src/cluster_erp/modules/products_and_services/products/metadata.py:276-285). Gated bundles are described on gated app bundles; the desktop explorer on desktop module screens.
Status and actions¶
A BOM card is active or archived; its usage type is manufacturing, engineering, or costing (src/cluster_erp/modules/bom/boms/schemas.py:10-11). A version moves from draft to active to superseded (src/cluster_erp/modules/bom/bom_versions/models.py:20-24).
| Action | Label | Condition | Endpoint | Permission | Source |
|---|---|---|---|---|---|
activate |
Activate | status is draft |
POST /bom-versions/{id}/activate |
bom.activate |
src/cluster_erp/modules/bom/bom_versions/metadata.py:48-58, src/cluster_erp/modules/bom/bom_versions/routes.py:145-154 |
copy_from_template |
Copy from Template | status is draft |
POST /bom-versions/{id}/copy-from-template |
bom.create |
src/cluster_erp/modules/bom/bom_versions/metadata.py:59-85, src/cluster_erp/modules/bom/bom_versions/routes.py:187-201 |
supersede |
Supersede | status is active |
POST /bom-versions/{id}/supersede |
bom.activate |
src/cluster_erp/modules/bom/bom_versions/metadata.py:86-116, src/cluster_erp/modules/bom/bom_versions/routes.py:170-184 |
The diagram shows a version's lifecycle.
stateDiagram-v2 [*] --> draft: create, clone, or auto-BOM draft --> active: activate active --> superseded: supersede with a draft successor draft --> [*]: delete superseded --> [*]
In prose: a version is created as draft (src/cluster_erp/modules/bom/bom_versions/services.py:231-274). activate needs a draft with at least one live item and no other active version on the BOM (src/cluster_erp/modules/bom/bom_versions/services.py:417-474). supersede turns the active version into superseded and a draft of the same BOM into active in one call (src/cluster_erp/modules/bom/bom_versions/services.py:476-542). Only a draft can be deleted, and its items go with it (src/cluster_erp/modules/bom/bom_versions/services.py:305-333).
Business rules¶
- Creating or changing a BOM card checks that the product exists and that model and version belong to it (
src/cluster_erp/modules/bom/boms/services.py:80-108,src/cluster_erp/modules/bom/boms/services.py:130-139). - An archived BOM can only be set back to active (
src/cluster_erp/modules/bom/boms/services.py:120-124). - A BOM with an active or superseded version cannot be deleted; deleting a BOM soft-deletes its versions and their items (
src/cluster_erp/modules/bom/boms/services.py:159-176,src/cluster_erp/modules/bom/boms/services.py:215-267). - Items can be added or changed only in a draft version (
src/cluster_erp/modules/bom/bom_items/services.py:135-137). - A zero quantity is refused; a negative quantity is a co-product (
src/cluster_erp/modules/bom/bom_items/services.py:139-146). - Each item's breakdown is validated; a stock material without model and version takes the product's default ones (
src/cluster_erp/modules/bom/bom_items/services.py:91-128,src/cluster_erp/modules/bom/bom_items/services.py:158-169). - The item level comes from the parent chain; the chain may not loop or go deeper than
MAX_BOM_DEPTH, which is 10 (src/cluster_erp/modules/bom/bom_items/services.py:32,src/cluster_erp/modules/bom/bom_items/services.py:430-470). - A missing scrap factor becomes
DEFAULT_SCRAP_FACTOR, 0.0300 (src/cluster_erp/modules/bom/bom_items/services.py:33,src/cluster_erp/modules/bom/bom_items/services.py:180-181). clonecopies any version into a new draft with the next version number and all items (src/cluster_erp/modules/bom/bom_versions/services.py:335-415).- Version totals: labour minutes and labour cost are per-unit figures times quantity; material cost is per-unit cost times quantity divided by one minus the scrap factor; weight sums the net weight of leaf items from the product master (
src/cluster_erp/modules/bom/bom_versions/services.py:671-718). bom_codecomes from the yearly sequencebom, andtemplate_codefrombom_template(src/cluster_erp/modules/bom/boms/services.py:89,src/cluster_erp/modules/bom/bom_templates/services.py:101).
Explosion and the operation BOM¶
The diagram shows how a BOM version becomes the explosion that other modules read, and how the routing scaffold reads the tree.
flowchart TD V["BomVersion and BomItem rows"] --> F["_filter_effective(as_of)"] F --> Q["effective_qty = qty / (1 - scrap), times each parent qty / (1 - parent scrap)"] Q --> A["operation_type and work_center_id from production allocations"] A --> R["rollup per part, model, version"] R --> X["GetBomExplosion answer"] X --> C["costing"] X --> P["production and MRP"] V --> S["GetBomScaffoldTree answer"] S --> W["production routing scaffold"] W --> I["CreateScaffoldIntermediate to products_and_services"]
In prose: explosion loads the live items of a version and drops items whose effectivity window does not contain the given date, with their whole subtree (src/cluster_erp/modules/bom/bom_versions/services.py:136-159, src/cluster_erp/modules/bom/bom_versions/services.py:734-741). For each item the effective quantity is the quantity divided by one minus its scrap factor, then multiplied by each parent's quantity divided by one minus that parent's scrap factor, rounded to four places with ROUND_HALF_EVEN; a negative co-product quantity takes no scrap (src/cluster_erp/modules/bom/bom_versions/services.py:128-133, src/cluster_erp/modules/bom/bom_versions/services.py:748-767). The operation type and work centre come from the production module's component allocations (src/cluster_erp/modules/bom/bom_versions/services.py:790-796). The rollup sums effective quantities per part, model, and version (src/cluster_erp/modules/bom/bom_versions/services.py:798-820). Other modules ask for it with GetBomExplosion (src/cluster_erp/modules/bom/module.py:217-228). The explosion follows parent links inside one version; the explorer tree additionally expands items that have their own active BOM (src/cluster_erp/modules/bom/bom_versions/services.py:1141-1151).
For the operation BOM, the production module's routing scaffold asks GetBomScaffoldTree with a full product breakdown. The bom module answers with the active version of exactly that breakdown, or a named version, and all its live items with resolved make-or-buy; it does not fall back to a default breakdown (src/cluster_erp/modules/bom/module.py:363-420, src/cluster_erp/modules/production/routings/scaffold.py:185-192). The scaffold turns non-phantom items into inputs of a final joining operation and lifts the children of phantom items; this is described in its module docstring (src/cluster_erp/modules/production/routings/scaffold.py:1-43). When an operation of a routing has an output, the scaffold asks products_and_services to create or reuse an intermediate product with CreateScaffoldIntermediate (src/cluster_erp/modules/production/routings/scaffold.py:2221-2239). The routing side is documented on routings and operation types.
Events and queries¶
The module consumes 15 contracts and produces 14, counted as on the parties module page. The full list is on the contracts reference.
| Direction | Contract class | Handler or emitter | Source |
|---|---|---|---|
| emits | BomDraftCreated |
version create, clone, auto-BOM | src/cluster_erp/modules/bom/bom_versions/services.py:256-264, src/cluster_erp/modules/bom/bom_versions/services.py:406-414, src/cluster_erp/modules/bom/module.py:760-768 |
| emits | BomActivated, BomVersionActivated |
activate, supersede |
src/cluster_erp/modules/bom/bom_versions/services.py:455-473, src/cluster_erp/modules/bom/bom_versions/services.py:523-541 |
| emits | BomSuperseded |
supersede |
src/cluster_erp/modules/bom/bom_versions/services.py:515-522 |
| emits | BomItemChanged |
item create, update, and move | src/cluster_erp/modules/bom/bom_items/services.py:227, src/cluster_erp/modules/bom/bom_items/services.py:324, src/cluster_erp/modules/bom/bom_items/services.py:516 |
| queries | GetProductById, GetProductDefaultBreakdown, GetBulkProductDefaultBreakdowns, GetDesignById, and planning reads |
services and handlers | src/cluster_erp/modules/bom/boms/services.py:204-206, src/cluster_erp/modules/bom/bom_versions/services.py:598-608, src/cluster_erp/modules/bom/module.py:686-688 |
| answers query | GetBomById, CreateBomForProduct, GetPartUsageCounts, IsBomActive, GetBomExplosion, GetActiveBomVersionForProduct, GetBomVersionsUpdatedAt, GetActiveBomItemsForProduct, GetBomScaffoldTree, GetBomItemDisplay, GetBomMaterials, GetBomVersionDisplay |
handlers in module.py |
src/cluster_erp/modules/bom/module.py:85-123 |
| subscribes | DesignApproved |
_handle_design_approved |
src/cluster_erp/modules/bom/module.py:124, src/cluster_erp/modules/bom/module.py:667-775 |
| subscribes | ProductDeleted |
_handle_product_deleted |
src/cluster_erp/modules/bom/module.py:125, src/cluster_erp/modules/bom/module.py:777-816 |
| subscribes | DescribeFieldConsumers |
_describe_field_consumers |
src/cluster_erp/modules/bom/module.py:65, src/cluster_erp/modules/bom/module.py:907-937 |
Rules carried by the subscriptions:
- On
DesignApproved, the module reads the design's part, takes that product's default model and version, and creates anactiveBOM card named after the design with a draft version 1. It skips a design without a part, a design that already owns a BOM, and a product without a default breakdown (src/cluster_erp/modules/bom/module.py:667-775). - On
ProductDeleted, the module soft-deletes the deleted product's items in draft versions only; active and superseded versions keep their rows (src/cluster_erp/modules/bom/module.py:777-816).
Permissions¶
| Permission key | Used by | Source |
|---|---|---|
bom.read |
every read route | src/cluster_erp/modules/bom/boms/routes.py:51-99, src/cluster_erp/modules/bom/bom_versions/routes.py:35-295, src/cluster_erp/modules/bom/bom_items/routes.py:61-219, src/cluster_erp/modules/bom/bom_templates/routes.py:38-146 |
bom.create |
create, update, delete, bulk delete, clone, copy from template, recalculate, item move | src/cluster_erp/modules/bom/boms/routes.py:131-170, src/cluster_erp/modules/bom/bom_versions/routes.py:109-207, src/cluster_erp/modules/bom/bom_items/routes.py:233-293, src/cluster_erp/modules/bom/bom_templates/routes.py:107-199 |
bom.activate |
activate and supersede | src/cluster_erp/modules/bom/bom_versions/routes.py:148, src/cluster_erp/modules/bom/bom_versions/routes.py:174 |
Privacy, retention, and history¶
The module stores no personal data and has no privacy_handlers.py. Deletion is soft throughout, and version and item deletions cascade with the same actor (src/cluster_erp/modules/bom/boms/services.py:215-249). Changes are captured by change history.
Analytics facts¶
This module has no analytics_facts.py.
Settings and reference data¶
The module has no settings section. The stock-unit arithmetic for make-from-stock items reads the kerf, sheet gap, and edge-margin settings through core/stock_conversion.py (src/cluster_erp/modules/bom/bom_versions/services.py:95-125); see product breakdown and stock conversion.
Tests¶
Found by a grep for modules.bom in tests/ and by file name in web-client/.
| Kind | File | Covers |
|---|---|---|
| pytest | tests/test_bom_bugfix_batch_2026_10_08.py |
unit pickers, item unit, version totals formula, metadata shape (10 tests) |
| pytest | tests/test_record_links.py |
record links in BOM metadata (3 tests) |
| e2e | web-client/_e2e_bom_app.mjs |
the BOM app: explorer and registers |
| e2e | web-client/_e2e_bom_me2m.mjs, web-client/_e2e_bom_me2m_matrix.mjs |
the purchasing coverage tab and its arithmetic |
| e2e | web-client/_e2e_bom_open_in_explorer.mjs |
opening a BOM from the product card in the explorer |
| e2e | web-client/_e2e_ebom_scaffold.mjs, web-client/_e2e_ebom_wizard.mjs, web-client/_e2e_ebom_operation_staging.mjs |
the routing scaffold that reads the BOM tree |
| e2e | web-client/_e2e_bom_bugfix_batch.mjs |
the BOM forms in a browser |
Discrepancies¶
Terms introduced¶
| Term | Code identifier | Definition | Turkish term | Source |
|---|---|---|---|---|
| bill of materials | Bom |
The card that names the product breakdown a structure builds. | Ürün ağacı | src/cluster_erp/modules/bom/boms/models.py:17 |
| BOM version | BomVersion |
A numbered snapshot of a BOM structure with a lifecycle. | Ürün ağacı versiyonu | src/cluster_erp/modules/bom/bom_versions/models.py:19 |
| BOM item | BomItem |
One position of a BOM version. | Ürün ağacı kalemi | src/cluster_erp/modules/bom/bom_items/models.py:18 |
| explosion | explosion |
The flat item list with effective quantities and a rollup per breakdown. | Patlatma | src/cluster_erp/modules/bom/bom_versions/services.py:722 |
| effective quantity | effective_qty |
Quantity per finished unit after scrap and parent multiplication. | Etkin miktar | src/cluster_erp/modules/bom/bom_versions/services.py:748-767 |
| co-product | negative qty |
A BOM item with negative quantity, an expected output. | Ko-ürün | src/cluster_erp/modules/bom/bom_items/services.py:139-146 |
| phantom item | is_phantom |
An item that coverage skips because its children carry the multiplied demand. | Hayalet kalem | src/cluster_erp/modules/bom/bom_versions/services.py:830-831 |
Related pages¶
- Modules index
- Products and services module
- Design engineering module
- Costing module
- Production module
- Product breakdown and stock conversion
- API reference for this module
Verified against commit 92f8051 on 2026-11-13.