/* * BIM OOTB — Frictionless BIM. Two DBs. One browser. Zero install. * Copyright (c) 2025-2026 Redhuan D. Oon red1org@gmail.com * SPDX-License-Identifier: MIT /
⚠ DO NOT REMOVE — Scope guard¶
Scope: Spatial ERP OOTB — doc_engine.js + category_registry + Construction ERP POC.¶
Browser-only. No server. No iDempiere dependency.¶
Replaces prompts/iDempiereOOTB.md — same iDempiere AD thinking,¶
zero iDempiere infrastructure.¶
Requirement source: ~/Downloads/Idempiere Construction ERP.pptx (Sysnova / Kazi Farms Group)¶
Read the log after every run. Exit code is not evidence.¶
Spec-first: implement only what is described in a § section below.¶
Spatial ERP OOTB — Construction ERP POC¶
Goal¶
Prove that a Construction ERP — land lead management, FAR planning, BOQ, project financial control — runs entirely in the browser using SQLite WASM + Three.js, with zero server dependency.
The POC domain is Construction (land acquisition → development planning → BOQ → project execution) because: 1. The architect provides IFC — the 3D view already exists in BIM OOTB 2. The BOQ engine already exists (boq_charts.html, rates.js) 3. The land plot is the spatial container, the building is the BOM 4. FAR (Floor Area Ratio) is inherently spatial — building volume / plot area 5. The workflow (Lead → Screen → FAR → BOQ → Approve) maps to doc_status
Requirement source: Sysnova (Part of Kazi Farms Group) — "Construction ERP on iDempiere" presentation. Real stakeholders, real workflow, real data fields.
POC success = one device, all roles: Land team creates lead with plot location → Architect sees FAR view with IFC building on plot → Engineering sees BOQ auto-computed from IFC → Management approves/rejects → role band switches between all 6 stakeholder perspectives.
Full spec: docs/SpatialERP_OOTB.md
§0. What This Replaces¶
prompts/iDempiereOOTB.md planned an OSGi plugin embedding BIM OOTB inside
iDempiere (Iframe in ZK, postMessage bridge, C_Project tab, Java handlers).
That approach required iDempiere server (JVM + PostgreSQL + OSGi bundle).
This prompt achieves the same ERP semantics with: - SQLite WASM (sql.js) in browser - kernel_ops (already built — commit, undo, redo, replay) - Three.js + BIM OOTB viewer (already built — IFC rendering, BOQ, 5D) - Same viewer (deploy/dev/index.html), extended with doc_engine.js
Build order: core engine FIRST, then UI, then domain data.
§0b. Core Engine Architecture — Build This First¶
┌─────────────────────────────────────────────────────────────────┐
│ BROWSER (same for all roles) │
├─────────────────┬──────────────────────────┬────────────────────┤
│ Adapters │ Handlers │ Core │
│ (UI skin) │ (domain logic plugins) │ (immutable) │
├─────────────────┼──────────────────────────┼────────────────────┤
│ ThreeScene ★ │ LeadCreate (Land) │ kernel_ops ★ │
│ SwipeCardStack │ FARCalc (Architect) │ StateMachine │
│ RoleBand │ BOQGenerate (Eng/BOQ) │ JournalEngine │
│ RoleFilter │ Approve (Mgmt) │ SpatialIndex ★ │
│ QRGateway ★ │ SalesView (Sales) │ CategoryRegistry │
│ │ LegalClose (Legal) │ │
│ ★ = already │ │ ★ = already │
│ exists │ │ exists │
└─────────────────┴──────────────────────────┴────────────────────┘
│
▼
SQLite WASM (.db file — 9 tables)
Build priority:¶
| Priority | Component | File | Depends on |
|---|---|---|---|
| P0 | Table creation (6 new tables) | doc_engine.js |
Nothing |
| P0 | StateMachine (pure function) | doc_engine.js |
Tables |
| P0 | JournalEngine (auto-post) | doc_engine.js |
StateMachine |
| P0 | kernel_ops user_tag | kernel_ops.js |
Nothing |
| P1 | CategoryRegistry reader | category_loader.js |
Tables |
| P1 | Construction seed data | construction.db |
Tables + Registry |
| P2 | Construction handlers | handlers/construction.js |
Core + seed data |
| P3 | SwipeCardStack | swipe.js |
Registry + handlers |
| P3 | RoleBand | role_band.js |
project_metadata |
| P4 | Integration test | test file | All above |
P0 must work headless (no UI) with unit tests proving state transitions and journal posting before any card is rendered.
§1. Requirement — Sysnova Construction ERP¶
§1.1 The business flow (from pptx)¶
Land Lead Management lifecycle:
Lead Creation → Screening → FAR Planning → Sales Visibility
→ BOQ → Negotiation → Approval / Rejection → Closure
§1.2 Stakeholders and roles¶
| Role | Responsibility | OOTB mode | OOTB scope |
|---|---|---|---|
| LAND (Land/Business Dev) | Lead creation, negotiation, full access | full |
All leads |
| ARCH (Architect/Planning) | FAR & development comments, FAR only | operator |
FAR fields + IFC |
| ENGR (Engineering/BOQ) | Cost estimation only | operator |
BOQ tab only |
| SALE (Sales) | Planning review, read-only, NO owner info | readonly |
No landowner data |
| MGMT (Management) | Approval authority, full access | full |
All leads |
| LEGL (Legal) | Post-approval processing | operator |
Approved leads only |
§1.3 Functional requirements (from pptx)¶
Lead Creation: - Lead Code (auto-generated) - Land Type (Freehold / Leasehold) - Land Size (Katha) - Lead Source - Location details: Plot, Road, Block, Sector, Area - Facing (East/West/North/South) - Road Width
Landowner Information (Confidential — hidden from Sales): - Owner Name, Contact Person, Mobile, Email, Address - Rule: Sales users shall NOT have visibility
Activity Management: - Meeting date, Follow-up date, Activity type, Notes, Attachments
FAR Calculation & Development Planning: - FAR value - Developable area, Saleable area - Number of storeys - Units per floor, Total units - Parking, Basement
Development Sales: - Sales Price per unit - Comments
BOQ: - Cost per SF - Comments - Links to IFC-derived BOQ (existing boq_charts.html)
Financial Management: - Project cost report, WIP statement - Budget vs actual, Revenue recognition - Progress billing (RA Bills), Retention
Project Phases: Foundation, Civil Structure, Electrical, Plumbing, Finishing
§2. iDempiere Mapping — AD in SQLite¶
Every iDempiere concept from the Sysnova pptx maps to browser-side:
| iDempiere (pptx shows) | Browser equivalent |
|---|---|
| Lead Info window (custom table) | documents with doc_type='LAND_LEAD' |
| Lead Activity tab | kernel_ops with op_type='LEAD_ACTIVITY' |
| Development Plan window | documents with doc_type='DEV_PLAN', linked to lead |
| Development Sales Price tab | document_lines with price + comments |
| Development BOQ window | document_lines with cost_per_sf + IFC BOQ link |
| C_Project (ABC Construction) | containers root — the project |
| Project Phases (Foundation, Civil...) | containers children — phases as sub-containers |
| AD_Role (Land Team, Sales, etc.) | ?mode= + role band + metadata field hiding |
| DocStatus workflow | doc_engine.js StateMachine |
| Financial reports | journal queries (SUM debit, credit GROUP BY account) |
§3. Schema — The Nine Tables¶
Same five data tables + registry from SpatialERP_OOTB.md §4.
Construction-specific data lives in metadata JSON, not new columns.
§3.1 New tables (created by doc_engine.js on first load)¶
-- §TAB_CONTAINERS — spatial hierarchy: Site → Building → Phase → Floor
CREATE TABLE IF NOT EXISTS containers (
id TEXT PRIMARY KEY,
parent_id TEXT REFERENCES containers(id),
name TEXT NOT NULL,
category TEXT NOT NULL, -- SITE, BUILDING, PHASE, FLOOR, PLOT
geometry_id TEXT, -- FK to component_geometries (IFC model)
x REAL DEFAULT 0, y REAL DEFAULT 0, z REAL DEFAULT 0,
metadata TEXT DEFAULT '{}' -- JSON: land_size, facing, far_value, etc.
);
-- §TAB_ITEMS — things in containers: elements, fixtures, materials
CREATE TABLE IF NOT EXISTS items (
id TEXT PRIMARY KEY,
container_id TEXT REFERENCES containers(id),
product_ref TEXT,
name TEXT,
qty REAL DEFAULT 1,
geometry_id TEXT,
x REAL DEFAULT 0, y REAL DEFAULT 0, z REAL DEFAULT 0,
metadata TEXT DEFAULT '{}'
);
-- §TAB_DOCUMENTS — leads, dev plans, BOQs, POs, invoices
CREATE TABLE IF NOT EXISTS documents (
id TEXT PRIMARY KEY,
doc_type TEXT NOT NULL, -- LAND_LEAD, DEV_PLAN, DEV_BOQ, PURCHASE_ORDER...
doc_status TEXT DEFAULT 'DRAFT',
created TEXT NOT NULL,
completed TEXT,
description TEXT,
metadata TEXT DEFAULT '{}' -- JSON: all lead fields, landowner info, etc.
);
-- §TAB_DOC_LINES — BOQ lines, sales prices, phase costs
CREATE TABLE IF NOT EXISTS document_lines (
id TEXT PRIMARY KEY,
doc_id TEXT REFERENCES documents(id),
item_id TEXT,
container_id TEXT,
qty REAL,
unit_price REAL,
metadata TEXT DEFAULT '{}'
);
-- §TAB_JOURNAL — auto-generated on document completion
CREATE TABLE IF NOT EXISTS journal (
id TEXT PRIMARY KEY,
doc_id TEXT REFERENCES documents(id),
line_id TEXT,
account TEXT NOT NULL, -- LAND_COST, CONSTRUCTION_WIP, REVENUE, RETENTION
debit REAL DEFAULT 0,
credit REAL DEFAULT 0,
timestamp TEXT NOT NULL
);
-- §TAB_REGISTRY — AD for construction categories
CREATE TABLE IF NOT EXISTS category_registry (
category TEXT PRIMARY KEY,
domain TEXT,
json_schema TEXT,
default_geometry TEXT,
actions TEXT, -- JSON: allowed actions per category
heatmap_rule TEXT,
label_template TEXT
);
§3.2 Existing tables (unchanged)¶
kernel_ops— commit, undo, redo, replay (adduser_tagcolumn)component_geometries— IFC 3D shapesproject_metadata— domain config, roles, colours
§4. Construction Seed Data — construction.db¶
§4.1 Containers — site/plot/building/phases¶
-- §SEED_CONTAINERS
-- The site container — top level
INSERT INTO containers VALUES ('site_gulshan', NULL, 'Gulshan-1 Site', 'SITE', NULL,
0,0,0, '{"area":"Gulshan-1","city":"Dhaka"}');
-- The land plot — spatial object on the site
INSERT INTO containers VALUES ('plot_60', 'site_gulshan', 'Plot 60, Road 2, Block 4', 'PLOT', NULL,
60,2,0, '{"plot_no":60,"road_no":2,"block_no":4,"sector":3,"road_width":20,"facing":"East"}');
-- The building — this is where the IFC goes
-- geometry_id links to the architect's IFC model once imported
INSERT INTO containers VALUES ('bldg_test', 'plot_60', 'Proposed Development', 'BUILDING', NULL,
0,0,0, '{"storeys":40,"far_value":10000,"dev_area":20,"saleable_area":100,"basement_area":2,"total_units":50,"units_per_floor":4,"parking":30}');
-- Project phases (from Sysnova pptx: Foundation, Civil, Electrical, Plumbing, Finishing)
INSERT INTO containers VALUES ('phase_found', 'bldg_test', 'Foundation', 'PHASE', NULL, 0,0,0, '{"sequence":10,"std_phase":"Foundation_Construction"}');
INSERT INTO containers VALUES ('phase_civil', 'bldg_test', 'Civil Structure', 'PHASE', NULL, 0,0,0, '{"sequence":20,"std_phase":"Civil_Structure_Construction"}');
INSERT INTO containers VALUES ('phase_elec', 'bldg_test', 'Electrical', 'PHASE', NULL, 0,0,0, '{"sequence":30,"std_phase":"Electrical_Construction"}');
INSERT INTO containers VALUES ('phase_plumb', 'bldg_test', 'Plumbing', 'PHASE', NULL, 0,0,0, '{"sequence":40,"std_phase":"Plumbing_Construction"}');
INSERT INTO containers VALUES ('phase_finish', 'bldg_test', 'Finishing', 'PHASE', NULL, 0,0,0, '{"sequence":50,"std_phase":"Finishing_Construction"}');
§4.2 Documents — the land lead (main document)¶
-- §SEED_LEAD — matches pptx Lead Info screen exactly
INSERT INTO documents VALUES ('LEAD-1000000', 'LAND_LEAD', 'DRAFT', '2026-05-13', NULL,
'Test lead — Gulshan-1 Plot 60',
'{
"lead_code": "1000000",
"land_type": "Freehold",
"land_size_katha": 10.0,
"lead_source": "Others",
"plot_no": 60, "road_no": 2, "block_no": 4, "sector": 3,
"area": "Gulshan-1", "facing": "East", "road_width": 20,
"owner_name": "CONFIDENTIAL — Mr. Rahman",
"contact_person": "test contact",
"phone": "0986533223",
"email": "",
"address": "",
"user_contact": "Azmir",
"container_ref": "plot_60"
}');
-- §SEED_DEV_PLAN — linked to the lead
INSERT INTO documents VALUES ('DEV-1000000', 'DEV_PLAN', 'DRAFT', '2026-05-13', NULL,
'Development Plan for LEAD-1000000',
'{
"lead_ref": "LEAD-1000000",
"far_value": 10000.0,
"total_dev_area": 20.0,
"total_saleable_area": 100.0,
"num_storeys": 40,
"total_units": 50,
"units_per_floor": 4,
"total_parking": 30,
"total_basement_area": 2.0,
"container_ref": "bldg_test"
}');
§4.3 Document lines — sales price + BOQ¶
-- §SEED_SALES_PRICE
INSERT INTO document_lines VALUES ('SP-001', 'DEV-1000000', NULL, 'bldg_test',
1, 2000.00, '{"type":"sales_price","comment":"testttt"}');
-- §SEED_BOQ
INSERT INTO document_lines VALUES ('BOQ-001', 'DEV-1000000', NULL, 'bldg_test',
1, 2300.00, '{"type":"cost_per_sf","comment":"eofuweofowef"}');
§4.4 Category Registry (AD)¶
-- §SEED_REGISTRY
INSERT INTO category_registry VALUES ('SITE', 'CONSTRUCTION', NULL, NULL,
'["CreateLead","ViewLeads","ViewPnL"]',
'{"field":"active_leads","red_above":10}',
'{name}');
INSERT INTO category_registry VALUES ('PLOT', 'CONSTRUCTION', NULL, 'plot_3d',
'["CreateLead","EditLead","ViewFAR","LinkIFC"]',
'{"field":"lead_status","red_value":"REJECTED"}',
'Plot {metadata.plot_no} — {metadata.area}');
INSERT INTO category_registry VALUES ('BUILDING', 'CONSTRUCTION', NULL, NULL,
'["ViewIFC","ComputeBOQ","EditFAR","ApprovePlan"]',
'{"field":"doc_status","amber_value":"DRAFT","red_value":"REJECTED"}',
'{name} ({metadata.storeys}F)');
INSERT INTO category_registry VALUES ('PHASE', 'CONSTRUCTION', NULL, NULL,
'["ViewProgress","AddCost","CompleteMilestone"]',
'{"field":"pct_complete","red_below":20,"green_above":80}',
'{name} — Seq {metadata.sequence}');
§4.5 Project Metadata — roles from Sysnova pptx¶
-- §SEED_META — maps exactly to pptx Role Based Access Control slide
INSERT INTO project_metadata VALUES ('domain', 'CONSTRUCTION');
INSERT INTO project_metadata VALUES ('roles',
'["LAND","ARCH","ENGR","SALE","MGMT","LEGL"]');
INSERT INTO project_metadata VALUES ('role_labels',
'{"LAND":"Land Team","ARCH":"Architect","ENGR":"BOQ Team","SALE":"Sales","MGMT":"Management","LEGL":"Legal"}');
INSERT INTO project_metadata VALUES ('role_colours',
'{"LAND":"#1565c0","ARCH":"#7b1fa2","ENGR":"#e65100","SALE":"#2e7d32","MGMT":"#b71c1c","LEGL":"#00838f"}');
INSERT INTO project_metadata VALUES ('role_modes',
'{"LAND":"full","ARCH":"operator","ENGR":"operator","SALE":"readonly","MGMT":"full","LEGL":"operator"}');
INSERT INTO project_metadata VALUES ('role_scopes',
'{"LAND":"*","ARCH":"far","ENGR":"boq","SALE":"no_owner","MGMT":"*","LEGL":"approved_only"}');
INSERT INTO project_metadata VALUES ('accounts',
'["LAND_ACQUISITION","CONSTRUCTION_WIP","REVENUE","RETENTION","PROFESSIONAL_FEES"]');
INSERT INTO project_metadata VALUES ('confidential_fields',
'["owner_name","contact_person","phone","email","address"]');
§5. State Machine — Lead Lifecycle¶
The Sysnova workflow maps to an extended state machine. The base 5-state machine handles all doc_types. The LAND_LEAD has domain-specific status labels:
Lead Created (DRAFT)
↓ screen
Screening (IN_PROGRESS)
↓ plan_far
FAR Planning (IN_PROGRESS — sub-status: FAR)
↓ submit_approval
Management Approval (IN_PROGRESS — sub-status: APPROVAL)
↓ approve / reject
→ approve → BOQ (IN_PROGRESS — sub-status: BOQ)
↓ negotiate
Negotiation (IN_PROGRESS — sub-status: NEGOTIATION)
↓ close
Approved / Closure (COMPLETED)
→ reject → Rejected (VOIDED)
Implementation: the base StateMachine handles DRAFT → IN_PROGRESS → COMPLETED / VOIDED.
The sub-statuses (FAR, APPROVAL, BOQ, NEGOTIATION) are stored in
documents.metadata.sub_status — the state machine doesn't know about them.
Handlers advance the sub_status via commitOp.
// §STATE_LEAD_TRANSITIONS
// The base machine sees: DRAFT → IN_PROGRESS → COMPLETED or VOIDED
// The handlers manage sub_status progression within IN_PROGRESS:
//
// handler: screenLead(db, leadId)
// → sets metadata.sub_status = 'SCREENING'
// → transition(db, leadId, 'start') // DRAFT → IN_PROGRESS
// → commitOp(db, 'LEAD_SCREEN', {lead_id, screened_by})
//
// handler: planFAR(db, leadId, farData)
// → sets metadata.sub_status = 'FAR'
// → creates DEV_PLAN document linked to lead
// → commitOp(db, 'FAR_PLAN', {lead_id, far_value, dev_area, ...})
//
// handler: submitApproval(db, leadId)
// → sets metadata.sub_status = 'APPROVAL'
// → commitOp(db, 'SUBMIT_APPROVAL', {lead_id, submitted_by})
//
// handler: approve(db, leadId)
// → sets metadata.sub_status = 'BOQ'
// → commitOp(db, 'LEAD_APPROVE', {lead_id, approved_by})
//
// handler: reject(db, leadId, reason)
// → transition(db, leadId, 'void') // → VOIDED
// → commitOp(db, 'LEAD_REJECT', {lead_id, reason})
//
// handler: generateBOQ(db, leadId)
// → reads IFC-derived element data from extracted .db
// → creates document_lines with costs from rates.js
// → sets metadata.sub_status = 'NEGOTIATION'
// → commitOp(db, 'BOQ_GENERATE', {lead_id, total_cost, line_count})
//
// handler: closeLead(db, leadId)
// → transition(db, leadId, 'complete') // → COMPLETED
// → JournalEngine posts: debit LAND_ACQUISITION, credit CASH
// → commitOp(db, 'LEAD_CLOSE', {lead_id, final_price})
§6. Role-Based Field Visibility¶
The Sysnova pptx specifies: "Sales users shall NOT have visibility" on
landowner information. This is handled by ?mode= and confidential_fields:
// §FIELD_FILTER
// When rendering a document card:
// 1. Read current role from role_band
// 2. Read role_scopes from project_metadata
// 3. Read confidential_fields from project_metadata
// 4. If role scope says "no_owner":
// → strip confidential_fields from displayed metadata
// → owner_name, phone, email, address → hidden
//
// No server enforcement — this is UI filtering (same as ?mode= pattern).
// The .db contains all data. Filtered views are convenience, not security.
// For true security: generate per-role .db extracts (same as customer QR pattern).
| Role | Sees lead fields | Sees owner info | Sees FAR | Sees BOQ | Can approve |
|---|---|---|---|---|---|
| LAND | All | Yes | Yes | Yes | No |
| ARCH | Location only | No | Yes (edit) | No | No |
| ENGR | Location + FAR | No | Yes (read) | Yes (edit) | No |
| SALE | Location + FAR + Sales Price | No | Yes (read) | No | No |
| MGMT | All | Yes | Yes | Yes | Yes |
| LEGL | Approved leads only | Yes | Yes | Yes | No |
§7. The Spatial Correlation — IFC Meets ERP¶
This is where BIM OOTB + Spatial ERP fuse. The architect provides an IFC file. The existing viewer renders it. The ERP layer wraps documents around it.
§7.1 The connection¶
Architect provides: residential_project.ifc
↓
BIM OOTB viewer: imports → extracts → renders 3D scene
↓
Spatial ERP: the 3D building IS the container 'bldg_test'
↓
BOQ handler: reads elements_meta from extracted .db
→ auto-populates document_lines with quantities + rates
↓
FAR calculation: reads bbox from element_transforms
→ computes total floor area / plot area
↓
Phase mapping: IFC disciplines (ARC, STR, MEP) → project phases
→ Foundation = STR ground floor elements
→ Electrical = ELEC discipline elements
→ Plumbing = PLB discipline elements
↓
Heatmap: phases colour-coded by completion %
→ Foundation 100% (green), Civil 60% (amber), Finishing 0% (red)
§7.2 BOQ from IFC (already exists)¶
The viewer's boq_charts.html already computes:
SELECT m.discipline, m.ifc_class, m.storey, COUNT(*) as qty
FROM elements_meta m
GROUP BY m.discipline, m.ifc_class, m.storey
With rates from rates.js (CIDB Malaysia 2024, or custom template).
The BOQ handler just copies this into document_lines:
// §HANDLER_BOQ_GENERATE
// Reads the existing QTO computation from the loaded .db
// Creates document_lines under the DEV_PLAN document:
// line per (discipline, ifc_class, storey) with qty, rate, total
// Links to the building container
// This is the SAME data boq_charts.html shows — just persisted as ERP lines
§7.3 FAR from geometry (new computation)¶
// §HANDLER_FAR_CALC
// FAR = Total Floor Area / Plot Area
// Total Floor Area: SUM of slab areas per storey (from element_transforms bbox)
// Plot Area: from container metadata (land_size_katha × 720 sq ft per katha)
//
// Or: read from architect's input in DEV_PLAN metadata
// The IFC provides the truth — architect's estimate can be validated against it
§7b. Two HTML Files, Two Concerns¶
The ERP layer is document-centric, not spatial. No redundant 3D scene. The spatial experience stays in BIM OOTB. Two HTML files, clean separation:
erp.html — Universal Document Engine (NEW)¶
Standalone HTML. No Three.js. Pure document handling with TikTok swipe UX. This is the common foundation for ALL future ERP document handling — construction, F&B, WMS, back office. Any domain where you swipe through documents.
┌──────────────────────────────┐
│ [ENGR] BOQ Team [QR] [<>]│ Role band
├──────────────────────────────┤
│ │
│ ┌────────────────────────┐ │
│ │ Lead: LEAD-1000000 │ │ Swipe card — document-centric
│ │ Gulshan-1, Plot 60 │ │ No 3D thumbnail. Just data.
│ │ 10 Katha, Freehold │ │ Clean, fast, phone-first.
│ │ │ │
│ │ Status: ⬤ SCREENING │ │ Colour dot = status
│ │ FAR: 10,000 │ │
│ │ BOQ: RM 2,300/SF │ │
│ │ │ │
│ │ [ Approve ] [ Reject ]│ │ Action buttons per role
│ └────────────────────────┘ │
│ │
│ ← swipe → (next document) │
│ ↑ swipe ↑ (drill into FAR) │
│ ↓ swipe ↓ (back to list) │
│ │
│ [ Open in BIM ] ← link out │ Opens index.html with same .db
└──────────────────────────────┘
What erp.html contains:
- sql.js (loads the same .db from OCI or IndexedDB)
- doc_engine.js (StateMachine + JournalEngine)
- category_loader.js (registry reader)
- swipe.js (card stack + gestures)
- role_band.js (role switcher + QR share)
- handlers/construction.js (or fnb.js, wms.js — domain handlers)
- No scene.js. No Three.js. No WebGL.
What this means: erp.html works on ANY device, even a $50 phone with no GPU. It's a card swipe app that happens to read a SQLite database. The 3D experience is optional — open BIM OOTB when you need spatial context.
index.html — BIM OOTB Spatial Scene (EXISTING)¶
The existing viewer. IFC geometry, 3D scene, measure tool, clash detection. When the user needs to connect a document to a spatial element, the Measure tool pattern extends to become an ERP overlay panel.
The Measure Tool Pattern — ERP in BIM¶
measure.js already demonstrates the pattern:
- User activates Measure tool → panel opens over the 3D scene
- User clicks element → panel shows measurement data
- Panel overlays the scene, doesn't replace it
"Measure for ERP" follows the same pattern:
┌──────────────────────────────────────────┐
│ BIM OOTB 3D Scene │
│ │
│ IFC building rendered │
│ ┌─────┤
│ │ ERP │
│ User clicks an IfcWall ─────────►│ │
│ │Tag │
│ │Lead │
│ │Snag │
│ │BOQ │
│ │Phase│
│ │ │
│ └─────┤
│ [Measure] [X-Ray] [Clash] [ERP] │ ← new toolbar button
└──────────────────────────────────────────┘
The [ERP] toolbar button opens a side panel (same as Measure panel). Click any element → panel shows: - Which BOQ line this element belongs to - Which phase (Foundation, Civil, Electrical...) - Cost for this element type - Linked documents (lead, snag, PO) - Action: [Tag] [Link to Lead] [Add Snag] [Mark Complete]
Each action is a commitOp — logged in kernel_ops, visible in erp.html.
The bridge is the .db¶
erp.html index.html
(document swipe) (3D spatial scene)
│ │
│ SAME .db on OCI │
│ ◄─────────────────────────► │
│ Same containers │
│ Same documents │
│ Same kernel_ops │
│ │
│ Lead created in erp.html │
│ → .db updated │
│ → index.html loads same .db │
│ → plot coloured by status │
│ │
│ Element tagged in index.html │
│ → kernel_op committed │
│ → erp.html loads same .db │
│ → BOQ line shows tag │
No postMessage. No WebSocket. No API. The .db file on OCI is the
integration layer. Both HTML files read the same file. Changes by one
are visible to the other on next load (or if sharing IndexedDB, instantly).
Desktop God Mode¶
Two browser tabs side by side:
- Left: index.html?db=construction.db (3D scene, IFC, measure)
- Right: erp.html?db=construction.db (document swipe, role band, approval)
Both read the same .db. The manager sees the building AND the documents.
Mobile¶
One app at a time:
- On-site foreman: opens erp.html on phone → swipes through phases, marks completion
- Architect at desk: opens index.html on laptop → reviews IFC, uses [ERP] panel to tag elements
- Management in meeting: opens erp.html on tablet → swipes leads, approves/rejects
The [Open in BIM] button on erp.html cards links out to index.html with
the same .db and ?scope= — for when you need spatial context.
Same repo, lazy-loaded¶
All ERP files live in the BIM OOTB repo (deploy/dev/). Nothing is separate.
Users see the full product — BIM + ERP — in one place.
Lazy-load pattern (same as measure.js):
- measure.js loads only when user clicks [Measure]
- erp_panel.js loads only when user clicks [ERP]
- erp.html is a standalone entry point that loads the same modules
deploy/dev/
index.html ← BIM viewer. [ERP] button lazy-loads erp_panel.js
erp.html ← Standalone document swipe. Loads same modules.
doc_engine.js ← Core: StateMachine + JournalEngine
category_loader.js ← Registry reader
erp_panel.js ← The panel UI (swipe cards + role band)
Loaded by BOTH index.html and erp.html
swipe.js ← Card stack + gestures (used by erp_panel.js)
role_band.js ← Role switcher + QR (used by erp_panel.js)
handlers/
construction.js ← Lead lifecycle + BOQ + FAR handlers
fnb.js ← (future) restaurant handlers
wms.js ← (future) warehouse handlers
measure.js ← Already exists. Same lazy-load pattern.
boq_charts.html ← Already exists. Same standalone pattern.
Two entry points, same modules:
| Entry | When | What loads |
|---|---|---|
erp.html |
Mobile user, document-only, no 3D needed | sql.js + doc_engine + erp_panel + swipe + role_band + handler |
index.html → [ERP] button |
Desktop user, wants 3D + documents | Same modules, lazy-loaded into panel overlay |
Why this works: The user sees one product with everything in it.
The foreman opens erp.html on their phone. The architect opens index.html
and clicks [ERP] when they need document context. Same data, same modules,
same role band — two ways in.
Why this separation wins¶
| Concern | erp.html (standalone) | index.html + [ERP] panel |
|---|---|---|
| Primary UX | Document swipe (TikTok) | 3D spatial + document overlay |
| Three.js | None — pure DOM + CSS | Full WebGL scene |
| Device requirement | Any phone, any browser | GPU-capable device |
| Domain-agnostic | Yes — same cards for any domain | Construction/BIM spatial |
| Foundation for | All future ERP document handling | Spatial tagging, element linking |
| File size | Tiny (~50KB + sql.js WASM) | Large (Three.js + IFC geometry) |
| Same modules? | Yes — doc_engine, swipe, role_band | Yes — lazy-loaded on [ERP] tap |
§8. POC Demo Flow — All Roles, One Device¶
1. Open construction.db in viewer
Role band: [LAND] (blue) — "Land Team"
3D view: site plan. Plot 60 shown as a rectangle on the map.
Plot is grey (no lead yet... wait, seed data has DRAFT lead).
Plot card shows: "Plot 60 — 10 Katha — Freehold — Gulshan-1"
↓
2. Tap Plot 60 → Lead card shows all fields (owner info visible to LAND).
Tap [Screen]. Lead transitions DRAFT → IN_PROGRESS (Screening).
commitOp: LEAD_SCREEN. Plot goes amber.
↓
3. Tap [<>] → switch to ARCH (purple) — "Architect"
Scope filters to FAR fields only.
Tap Plot 60 → Card shows land info + FAR section.
Owner info HIDDEN (Architect = no owner visibility).
Tap [LinkIFC] → drop architect's IFC file → building renders on plot.
Tap [EditFAR] → FAR value auto-computed from IFC geometry.
commitOp: FAR_PLAN. Sub-status → FAR.
↓
4. Tap [<>] → switch to ENGR (orange) — "BOQ Team"
Scope filters to BOQ tab only.
Tap building → Card shows IFC element summary.
Tap [ComputeBOQ] → handler reads elements_meta + rates.js
→ document_lines auto-populated: IfcWall RM 285/M2 × 245 = RM 69,825...
commitOp: BOQ_GENERATE. Sub-status → NEGOTIATION.
Card shows total cost: RM 2,300/SF × total SF.
↓
5. Tap [<>] → switch to SALE (green) — "Sales"
Owner info HIDDEN. FAR + BOQ visible (read-only).
Sales sees: Plot location, FAR, total cost, sales price per unit.
Cannot edit. Cannot approve. Can review.
↓
6. Tap [<>] → switch to MGMT (red) — "Management"
Full access. ALL fields visible including owner.
Reviews: lead info, FAR, BOQ, sales price.
Tap [Approve] or [Reject].
If approve → commitOp: LEAD_APPROVE. Sub-status → BOQ.
If reject → transition to VOIDED. Lead card goes red.
↓
7. Tap [<>] → switch to LEGL (teal) — "Legal"
Scope: approved leads only. Sees the approved lead.
Processes post-approval paperwork.
Tap [Close] → transition to COMPLETED.
Journal auto-posts: debit LAND_ACQUISITION, credit CASH.
↓
8. Tap [<>] → back to LAND (blue)
Plot 60 goes green (lead completed).
Building rendered on plot with phases colour-coded.
Project financial view: journal entries visible.
kernel_ops: full audit trail of every step by every role.
↓
9. Tap [QR] → generates link for ARCH role.
Hand to architect friend. They see FAR view + IFC.
Same viewer. Same .db. Different perspective.
↓
POC COMPLETE.
§9. Implementation Order¶
| Step | Section | Deliverable | Verify |
|---|---|---|---|
| 1 | §3.1 | doc_engine.js — CREATE TABLE + StateMachine + JournalEngine |
Unit test: transitions + journal |
| 2 | §4 | construction.db — seed data (containers, documents, registry) |
Load in viewer → site renders |
| 3 | §5 | Lead lifecycle handlers (screen, planFAR, approve, reject, close) | §-log: full lead cycle works |
| 4 | §7.2 | BOQ handler — reads IFC elements, creates document_lines | §-log: BOQ lines match boq_charts |
| 5 | §7.3 | FAR handler — computes from IFC geometry or manual input | §-log: FAR value computed |
| 6 | §6 | Role-based field filtering (confidential_fields hidden for SALE) | §-log: owner info hidden per role |
| 7 | §8 | SwipeCardStack + RoleBand — 6 roles cycling | §-log: role switch changes scope |
| 8 | §8 | End-to-end demo flow | All 9 steps complete on one device |
| 9 | — | Upload construction.db to OCI dev bucket | Accessible via viewer URL |
§10. Files — What Gets Created / Modified¶
New files¶
| File | Lines (est.) | What |
|---|---|---|
deploy/dev/doc_engine.js |
~100 | Core: StateMachine + JournalEngine + table creation |
deploy/dev/category_loader.js |
~30 | Registry reader |
deploy/dev/erp_panel.js |
~150 | ERP panel UI: card renderer + status display. Lazy-loaded by [ERP] button in toolbar, same as measure.js pattern. |
deploy/dev/swipe.js |
~150 | Card stack + touch gestures (used by erp_panel.js) |
deploy/dev/role_band.js |
~80 | Role switcher + QR share (used by erp_panel.js) |
deploy/dev/handlers/construction.js |
~200 | Lead lifecycle + BOQ + FAR handlers |
deploy/dev/erp.html |
~80 | Standalone document-only entry. No Three.js. Loads same modules. For mobile/document-only users. |
deploy/dev/buildings/construction.db |
seed data | The POC database |
deploy/dev/tests/test_doc_engine.js |
~80 | Unit tests for core engine |
Modified files¶
| File | Change |
|---|---|
deploy/dev/main.js |
Load doc_engine.js, category_loader.js. Init tables on DB load. |
deploy/dev/kernel_ops.js |
Add user_tag column (idempotent ALTER). |
deploy/dev/index.html |
Add [ERP] icon to toolbar (lazy-loads erp_panel.js on tap). Include doc_engine.js. |
deploy/dev/tools.js |
Register [ERP] toolbar button alongside Measure, X-Ray, Clash. |
NOT modified¶
deploy/live/*— Production — NEVER touchdeploy/dev/scene.js— Three.js scene untoucheddeploy/dev/measure.js— Measure tool untouched (pattern to follow, not modify)deploy/dev/boq_charts.html— BOQ engine reused as-is by handlersdeploy/dev/rates.js— Rate templates reused as-isdeploy/dev/share.js— QR/share reused by role_band.js
§11. Boundaries and Constraints¶
- No server. No iDempiere. No PostgreSQL. Browser only.
- No redundant 3D scene in erp.html. Document-centric only. Spatial = BIM OOTB.
- [ERP] is just another toolbar icon. Same pattern as [Measure]. Lazy-loaded.
- erp.html is standalone. For mobile/document-only users who don't need 3D.
- IFC = the building. Architect provides IFC → viewer imports → geometry links to container.
- BOQ reuses existing engine.
boq_charts.html+rates.jsalready compute costs. - kernel_ops is primary. Every mutation goes through commitOp.
- Confidential fields filtered in UI, not enforced server-side.
- Handlers are stateless. Read → compute → commitOp.
- Mobile-first. pointerup not click. Touch targets ≥ 44px.
- Never touch deploy/live/.
§12. What This Unlocks (Post-POC)¶
| Phase | What | Same core? |
|---|---|---|
| F&B domain | New seed .db: restaurant tables. New handlers. | Yes — same doc_engine.js |
| WMS domain | New seed .db: racks, bins, SKUs. | Yes — new handler only |
| Multi-project | Multiple leads/plots on one site. Portfolio view. | Yes — more containers |
| Cloud relay | Upload .db to OCI. Role-based QR links. | Yes — no code change |
| Full P2P | Lead → approve → PO → GR → Invoice → Payment | Yes — P2P handlers (§10b in spec) |
| iDempiere graduation | Export to C_Project, C_ProjectLine, M_Product | Data migration, not rewrite |
§13. Reference¶
docs/SpatialERP_OOTB.md— full spec (schema, UX, architecture, P2P, market)~/Downloads/Idempiere Construction ERP.pptx— Sysnova source requirementdocs/BOMBasedCompilation.md§1 — iDempiere entity mappingdeploy/dev/kernel_ops.js— existing OpLogdeploy/dev/boq_charts.html— existing BOQ/QTO enginedeploy/dev/rates.js— existing rate templatesdeploy/dev/share.js— existing share mechanismprompts/iDempiereOOTB.md— the old approach (OSGi). Superseded.
Copyright (c) 2025-2026 Redhuan D. Oon. MIT Licensed.