Project Order Blueprint¶
Foundation: MANIFESTO · BBC · DISC_VALIDATION_DB_SRS
iDempiere handles orders, costing, document flow, supplier/customer relationships, cost margins, WIP, materials on site, and large-scale projects with one-to-many orders — all proven over 20 years. This document maps those ERP patterns to construction, showing what each iDempiere entity already solves.
What this document covers:
| Section | iDempiere pattern | Construction application |
|---|---|---|
| §1 | C_OrderLine exceptions | Order carries only deviations from base BOM. 200 houses in 1–6 lines each |
| §2 | C_Project | Site as BOM — multi-building developments, costing, doc flow, WIP |
| §3 | M_Product_Category tree | Domain by taxonomy, not code — construction, infrastructure, marine |
| §4 | DocAction=Approve | BOM mining — users promote reusable recipes from compiled buildings |
| §5 | Product master columns | nD dimensions — 4D schedule, 5D cost, 6D sustainability, 7D facility, 8D ERP |
| §6 | Ref_Order_ID (parent FK) | Order inheritance — composable variant overlays |
| §7 | Callout + ModelValidator | DiffVerb + Callout — reactive spatial editing with cascading rules |
| §8 | AD_ChangeLog | Full provenance and audit trail |
| §9 | AD_Org + AD_Val_Rule | Discipline as OrderLine — iDempiere processing order |
The core thesis. See MANIFESTO.md.
1. Exception-Based Ordering — Configure-to-Order for Buildings¶
Status: PARTIALLY IMPLEMENTED. Exception-based ordering works (Sessions D+E). Replace, Remove, Compress, Add all implemented. Reference class and indexed exceptions are future.
The full BOM explosion (BBC §3.4 BOM Drop) produces hundreds or thousands of C_OrderLines. For a developer building 200 houses from the same base design, this is redundant — the differences between units are a handful of product swaps, not a different building.
Principle: A C_OrderLine records only deviations from the standard BOM. The order says "build me a Duplex, but swap the bedroom door to sliding." Two lines, not two hundred.
C_Order: "Build me a Duplex" ← 2 lines total
├── C_OrderLine #1: family_ref='BUILDING_DX_STD' ← entire building, ONE line
│ (no exception — take the standard BOM as-is)
│
└── C_OrderLine #2: product swap exception ← the deviation
locator_ref = 'Rm_Bedroom_1.Door_1'
original = DOOR_PANEL_900
replacement = DOOR_SLIDING_900
(same M_Product_Category — category-constrained)
Compile-time behaviour: bomDrop() explodes the parent BOM fully in the
backend. When the explosion reaches the locator_ref named in an exception
line, it applies the swap. The rest of the tree passes through unchanged.
Output is deterministic — the same order always produces the same building.
Why this works:
| Concern | How it's handled |
|---|---|
| Validation | Swap must be within same M_Product_Category (same shelf). MValRuleException records acknowledged deviations |
| Determinism | Base BOM is immutable. Exceptions are ordered. Same Order → same output |
| Scalability | 200 houses = 200 orders of 1–6 lines each, not 200 × full explosion |
| Versioning | C_Order carries full audit trail. output.db is a reproducible materialised view |
| ASI overrides | M_AttributeSetInstance on the exception C_OrderLine carries per-instance parameters (e.g. colour, material finish) — same as customer options in manufacturing ERP |
1.1 Exception Algebra — Four Complete Mutations¶
Any building variant expressible as a finite diff against a base BOM can be described with exactly four operations:
| Mutation | Exception | Example |
|---|---|---|
| Replace | swap product at locator_ref | Bedroom door → sliding door |
| Compress | qty=N, don't explode (§1.2 Reference Class) | 100 floors as one line |
| Remove | qty=0 at locator_ref | Standard duplex WITHOUT garage |
| Add | new C_OrderLine with locator_ref | Add solar panels (addDiscipline) |
This is a complete set. No other mutation type is needed. Every variant is a combination of these four primitives applied to a base BOM.
The qty=0 removal is the minimalist complement to product swap. The order says "everything in the standard BOM, except delete the node at this locator_ref and its entire subtree." The compiler skips that branch during explosion. The parent AABB recalculates (the removed child's space becomes available buffer or the parent shrinks).
1.2 Reference Class Pattern — Qty Without Explosion¶
A skyscraper BOM has three children: Roof, Body, Slab. Body has child
FLOOR_STD with BOMQty=100. Full explosion produces 100 identical
C_OrderLines. But every floor is the same — the order should say so in
one line.
Reference class: A C_OrderLine with qty > 1 that is NOT exploded
into individual lines. It declares a type and a count. The compiler
instantiates at compile time, computing spatial placement (dz per floor)
from the BOM recipe's offset rules.
C_Order: "Build me a 100-storey Tower" ← 3 lines total
├── C_OrderLine #1: family_ref='TOWER_100F_STD' ← the grandparent
│ (contains Roof + Body + Slab — ONE line)
│
├── C_OrderLine #2: reference class override
│ locator_ref = 'Body.FLOOR'
│ family_ref = 'FLOOR_STD'
│ qty = 100 ← NOT exploded
│ (compiler instantiates 100 floors at regular dz)
│
└── C_OrderLine #3: indexed exception
locator_ref = 'Body.FLOOR[47]' ← floor index
swap: LOBBY_STD → LOBBY_EXECUTIVE
(only floor 47 gets the executive lobby)
Three lines for a 100-storey skyscraper with one custom floor.
Three ordering modes — increasing compression:
| Mode | Order says | Compiler does | Lines for 100 floors |
|---|---|---|---|
| Full explosion | 100 lines, one per floor | Places each | 100 |
| Product swap (§1) | 1 grandparent + swap exceptions | Explodes, swaps | 2–10 |
| Reference class | 1 line × qty + indexed exceptions | Instantiates, swaps by index | 1–3 |
ERP precedent: This is the manufacturing distinction between
discrete and repetitive production. Discrete: one work order per
unit (100 work orders). Repetitive: one work order with qty=100, produced
on a line. iDempiere supports both via C_OrderLine.QtyOrdered. The
reference class pattern applies the same principle to BOM explosion.
Indexed exceptions use locator_ref with an array index suffix
([47]). The index is the ordinal position in the parent's child sequence.
The compiler resolves the index to a specific spatial position during
instantiation. Multiple indexed exceptions on the same reference class
are supported — each targets a different instance.
Why this matters for the abstract tree (§3): The reference class pattern proves the tree is truly abstract. A BOM node is not "a floor" — it is "a typed slot that can be instantiated N times." The category says what kind of thing fills the slot. The qty says how many. The indexed exception says which instances deviate. This is the same pattern whether the repeated element is a floor, a bridge span, a ship frame, or a parking bay.
2. C_Project — Site as BOM¶
Status: Future — design specification only.
Friction: 200 houses on a development site, each a separate C_Order. The site has its own constraints — road layout, utility routing, setback distances. Managing 200 independent orders with no parent is unmanageable.
C_Project groups orders. M_Locator places them. C_Project is the iDempiere parent for multi-order engagements — project accounting, milestone billing, subcontractor management. M_Locator (Aisle/Lot/Bin) provides the site grid — each plot is a locator, each building is placed into a locator. No BOM at site level — the grid IS the address.
C_Project: "Taman Melati Phase 2" ← the development
│ M_Warehouse → site locator grid (Street/Lot/Terrace)
│
├── C_Order[1..180]: BUILDING_SH_STD → M_Locator[1..180] ← 180 plots
├── C_Order[181..195]: BUILDING_DX_STD → M_Locator[181..195] ← 15 plots
├── C_Order[196]: BUILDING_SH_CORNER → M_Locator[12] ← corner lot
└── C_Order[197..200]: BUILDING_SH_PREMIUM → M_Locator[197..200]
iDempiere mapping:
| BIM Compiler | iDempiere | What it does |
|---|---|---|
| Development site | C_Project + M_Warehouse | Groups orders, provides locator grid |
| Plot | M_Locator (Street/Lot/Terrace) | ABL address for each building position |
| Building order | C_Order (FK → C_Project, FK → M_Locator) | Exception-based order placed on a plot |
| Plot availability | M_StorageOnHand | 0 = empty plot, 1 = occupied |
| Project budget | C_Project.PlannedAmt | SUM of all C_Order costs |
What C_Project + M_Locator triggers in an ERP person's mind: project accounting, warehouse management, put-away strategy, milestone billing, subcontractor management, progress reporting. All solved problems in iDempiere — without new code.
Test architecture for C_Project: see TestArchitecture.md.
2.2 Site Layout as Warehouse Put-Away (S67)¶
Principle: A housing development site IS a warehouse. Plots are locators. Buildings are inventory. The put-away strategy assigns buildings to plots. Terrain topology defines the locator grid (ABL = Street/Lot/Terrace).
iDempiere mapping:
| Warehouse | Site | Entity |
|---|---|---|
| M_Warehouse | C_Project | Site development |
| M_Locator (Aisle/Bin/Level) | Plot (Street/Lot/Terrace) | ABL addressing |
| M_LocatorType | PlotType (STANDARD/CORNER/PREMIUM/INFRA/GREEN) | Plot classification |
| M_Locator.capacity | Plot frontage × depth - setbacks | Buildable area |
| M_PutAwayStrategy | SitePlacementStrategy | Which building goes where |
| M_InOutLine | C_Order FK → Plot | One building placed on one plot |
| M_InOutLineMA | Plot attribution | Variant, exceptions, terrain Z |
Terrain as Locator ABL. The terrain survey (PDFTerrain: 689 points, 294m × 229m,
Z range 28-48m) defines the physical warehouse floor. Natural terrace bands become
Levels. Roads cut across contours to define Aisles. Lots are sequential along each
road. Each plot's Z is interpolated from the survey via AlignmentContext.elevationAt(x, y).
Terrace 2 (Z ≈ 44-48m) ═══ Street 3 ═══ [P][P][P][P][S][P][P]
PREMIUM lots (hilltop view)
Terrace 1 (Z ≈ 38-44m) ═══ Street 2 ═══ [C][S][S][S][S][S][S][C]
STANDARD + CORNER lots
Terrace 0 (Z ≈ 28-38m) ═══ Street 1 ═══ [S][S][S][S][S][S][S][S][S]
STANDARD lots (river valley)
Put-away rules (ad_site_placement_rule):
| Plot Type | Building Variant | Orientation | Priority |
|---|---|---|---|
| CORNER | SH_CORNER | FACE_STREET | 10 |
| PREMIUM | SH_PREMIUM | FACE_VIEW | 20 |
| STANDARD | SH_STD | FACE_STREET | 99 |
| INFRA | SITE_INFRA_STD | ALONG_STREET | 1 |
| GREEN | NULL (no building) | — | 1 |
Validation constraints: - Building AABB fits within plot (frontage, depth, setback) - Adjacent buildings: gap ≥ side_setback × 2 - Building height ≤ zoning maximum - Aggregate footprint ≤ site_aabb - Infrastructure easements respected
Test case: 689-point survey site, 24 houses on 3 terraces: - Terrace 0: 9 SH_STD (river level, Z ≈ 30m) - Terrace 1: 8 SH_STD + 2 SH_CORNER (mid-slope, Z ≈ 40m) - Terrace 2: 4 SH_PREMIUM + 1 GREEN (hilltop, Z ≈ 46m) - Infrastructure: 3 road segments connecting terraces
Source data: IfcOpenShell/.../pdf_terrain/samples/survey_highres_extracted.json
Existing infrastructure (all proven, all tested):
- PDFTerrain: 689-point survey extraction (W-CONTEXT-TERRAIN-1)
- AlignmentContext: elevationAt(x, y) interpolation
- TerrainSnap: ON_SURFACE/ABOVE/BELOW snap modes
- CutFillCalculator: earthwork volumes (cut/fill/net m³)
- GradingStrategy: CONTOUR/STRAIGHT/BLEND modes
Two separate concerns:
| Concern | Mechanism | iDempiere parallel |
|---|---|---|
| Site grid | M_Locator (Street/Lot/Terrace) | Warehouse ALB addressing |
| Building content | BOM (root → children → leaf) | Manufacturing BOM |
The locator gives each plot its XYZ position on the site. The building's own BOM handles everything inside the building. C_Order.M_Locator_ID links a building to its plot — same as M_InOutLine linking inventory to a bin.
M_StorageOnHand = 0 means the plot is empty (available). Put-away strategy assigns building variants to plot types. This is standard WMS — no new infrastructure, just iDempiere's existing warehouse module applied to land.
3. Abstract Category Tree — Domain by Taxonomy, Not by Code¶
Status: Architectural observation — already emergent in the current model.
The BOM tree has no hard-coded domain concepts. There is no "Building" class, no "Floor" class, no "Room" class. There are only BOMs classified by M_Product_Category. The hierarchy is a cascade of categories:
BOM(cat=RE) → BOM(cat=L1) → BOM(cat=LI) → BOM(cat=FR) ← Construction
BOM(cat=BRIDGE) → BOM(cat=SPAN) → BOM(cat=DECK) → BOM(cat=GIRDER) ← Infrastructure
BOM(cat=VESSEL) → BOM(cat=HULL) → BOM(cat=SECTION) → BOM(cat=FRAME) ← Marine
The engine — bomDrop, Selection Cascade (BBC §3.5), verb execution, gate verification — operates on abstract BOM nodes. It never asks "is this a building?" It asks "does this BOM have children? does the child fit the parent AABB? what category constrains the swap?"
Users paint branches onto the tree by defining a category taxonomy:
- Create M_Product_Category rows (e.g., BRIDGE, SPAN, DECK, GIRDER)
- Create M_Product entries in each category with AABB dimensions
- Create M_BOM recipes linking products into parent-child hierarchies
- Import component_library geometry for the leaf products
No code changes. No new verbs (unless the domain needs domain-specific operations — a marine WELD verb, an infrastructure GRADE verb). The compilation engine, the Bonsai viewport, the gate verification, the exception-based ordering (§1) — all work unchanged.
Category cascade structure: The taxonomy itself is a tree. RE contains L1, L2. L1 contains LI, KT, BT. This mirrors how M_Product_Category works in iDempiere — categories have parent categories. The BOM Selection Cascade (BBC §3.5) already walks this structure. A new domain is a new category subtree, not a new codebase.
Concrete proof: ShipYard.md details a marine vertical — hull plates as TILE verb output, station offsets as curved surface provider, exception ordering for ice-class reinforcement. Phase 1 (flat-tile hull, 300 elements) compiles through the existing pipeline with zero code changes.
4. BOM Mining — Approve as Promote¶
Status: Future — design specification only.
Friction: 34 buildings compiled from IFC extraction. Each required manually authoring M_BOM recipes. But if Building A and Building B share 80% of their BOM tree, that shared subtree was authored twice.
DocAction = Approve promotes a validated order into a reusable BOM artifact. This is the iDempiere Approve action repurposed: instead of approving a purchase order for payment, you approve a compiled building order for promotion into the BOM library.
The workflow:
- Import two IFC buildings → extract BOMs → compile → gates pass
- Diff the two compiled BOM trees (same infrastructure as Rosetta Stone gates, run sideways — not "compiled vs reference" but "Building A vs Building B")
- Select the common subtree in the Bonsai viewport or BOM Tree tab — lasso a set of nodes, like Photoshop selection
- Approve → the selected subtree is promoted to a new M_BOM in {PREFIX}_BOM.db with a name, category, and AABB
- Both buildings now reference the shared recipe. Deviations become exception-based orders (§1)
This is crafting — like Photoshop Actions or Blender node groups. The user discovers reusable patterns through work, selects them, and promotes them to first-class library artifacts. The BOM library writes itself through accumulated practice.
Why DocAction = Approve: The promotion is deliberate and audited. It's not automatic extraction — the user curates what becomes reusable. The Approve action in iDempiere already carries audit trail, user ID, timestamp, and can require multi-level approval for quality control. A promoted BOM is a vetted artifact, not a raw extraction.
Aggregation of effort: Over time, the BOM library converges toward a minimal set of reusable recipes. 34 buildings might share 12 common floor plans, 8 room layouts, 5 roof types. Each Approve cycle reduces redundancy. The library becomes a curated encyclopedia of construction patterns — contributed by everyone who compiles a building.
5. nD Dimensions — Every D Is a Column¶
Every "D" above 3D is a column on iDempiere entities, not a separate system:
| Dimension | iDempiere entity | What it is |
|---|---|---|
| 4D Schedule | BOM tree topological sort | Depth-first walk = construction sequence. Critical path = deepest branch |
| 5D Cost | M_Product.unit_cost_rm × C_OrderLine.qty | Cost delta of an exception = one query |
| 6D Sustainability | M_Product columns (embodied carbon, EPD) | SUM = building carbon footprint |
| 7D Facility Mgmt | M_Product columns (maintenance, warranty) | Compiled order = asset register |
| 8D ERP/BI | C_Order, C_Project, C_BPartner | Procurement, WIP, cash flow — native iDempiere |
Populating these is data entry on the product master, not development. See MANIFESTO.md §Why This Matters.
6. Order Inheritance — Composable Variants¶
Status: IMPLEMENTED (Session E, S68e).
InheritanceResolver,Ref_Order_ID. OrderInheritanceTest 6/6.
An order declares a parent order via C_Order.Ref_Order_ID:
DX_BASE ← 1 line (standard duplex)
└─ DX_SOLAR (parent=DX_BASE) ← +2 lines (solar panels + switchboard)
└─ DX_SOLAR_PREMIUM (parent=DX_SOLAR) ← +3 lines (premium finishes)
DX_SOLAR_PREMIUM carries 3 lines, not 6. Each overlay is 2-3 lines — like CSS layers stacked on a base. The library of overlays is the product catalog of upgrades.
Key design rules:
- Chain walking:
InheritanceResolverwalksRef_Order_IDroot-first, collecting exception C_OrderLines at each level. Algorithm:Map<locator_ref, ExceptionLine>, root-to-leaf overwrite. O(N). - Last descendant wins: When two orders in the chain target the same
locator_ref, the deeper order (closer to leaf) takes effect. - Single-parent only (GAP-SC-5 resolved):
Ref_Order_IDis a scalar FK — diamond inheritance is structurally impossible. To combine siblings, author a new order with both exception sets explicitly. - Within-order ordering: Multiple exception lines in one order apply in
C_OrderLine.Lineorder. Higher line number wins at samelocator_ref. - Cycle detection: Track visited IDs during chain walk; cycle →
IllegalStateException. - Duplicate warning: Two lines in the same order targeting the same
locator_reftriggers a warning (likely user error), does not block.
FOSS ecosystem model and strategic positioning: see StrategicIndustryPositioning.md.
7. Reactive Editing — DiffVerb + Callout¶
iDempiere's Callout mechanism applied to spatial editing. Three layers:
| Layer | iDempiere parallel | Construction use |
|---|---|---|
| ASI | M_AttributeSetInstance | Static overrides: colour, material, finish |
| DiffVerb | W_Verb_Node (replayable delta) | Spatial mutations: "move fireplace 300mm left" |
| Callout | AD_Rule (cascading consequence) | Flue follows, hearth resizes, clearance re-validates |
Every edit is recorded, replayable, composable. The edit session is a sequence of DiffVerbs — like iDempiere's AD_ChangeLog applied to spatial operations. Full provenance: who changed what, when, from-what, to-what.
8. AD_ChangeLog — Provenance¶
iDempiere's AD_ChangeLog records every field change on every record. Applied to construction: every promoted BOM (§4), every DiffVerb (§7), every exception mutation — all recorded with attribution, lineage, and verification state.
9. Discipline as OrderLine — The Add Mutation¶
Each discipline (FP, ELEC, ACMV, CW, SP, LPG) is an Add mutation on the order (§1.1). One C_OrderLine per discipline, with: - Product → the top BOM of that discipline (e.g. FP_SYSTEM) - Category → the entry point tier (e.g. FP_MAIN_ROOM) - AD_Org → the discipline identity, blanket-triggers validation
Processing follows standard iDempiere document order (see DISC_VALIDATION_DB_SRS.md §10.4):
| Stage | iDempiere parallel | What it does |
|---|---|---|
| 1st: DocEvent per Org | ModelValidator.docValidate() | Discipline blanket rules, top-down walk |
| 2nd: AttributeSet | M_AttributeSetInstance | Per-product/per-instance resolution |
| 3rd: AD_Val_Rule | AD_Val_Rule | Government standards, post-hoc compliance |
9.1 Discipline States on the Order¶
| State | What happens |
|---|---|
| Absent | No discipline OrderLine. Validation fires warnings if jurisdiction requires it |
| Proposed | System suggests OrderLine via docValidate(TIMING_BEFORE_COMPLETE). Architect reviews |
| Accepted | OrderLine active. Compilation explodes the discipline BOM cascade |
iDempiere parallel: ModelValidator.docValidate() proposing lines before
order completion — same pattern, applied to construction disciplines.
9.2 Compliance Rule Packs¶
AD_Val_Rule rows are jurisdiction-specific, importable, versioned:
| Pack | Jurisdiction | Authority |
|---|---|---|
| UBBL-2024 | Malaysia | KPKT |
| NFPA-13 | USA/intl | NFPA |
| BCA-2019 | Singapore | BCA |
| IBC-2021 | USA (model) | ICC |
Same BOM, same Org practices, different rule pack = different jurisdiction. The engine doesn't change — only the post-hoc validation rules.
Implementation history (Sessions 0-F, all DONE) archived in git history.