ERP World View · Doctrine

Cross-ERP Rosetta Stone

Every ERP's vocabulary, mapped to the one canonical model the engine folds into. Read a row across: the same business fact, named five ways, retained faithfully in one AD shape.

The doctrine (defined ahead of the folding engineers). There is one canonical model — iDempiere's Application Dictionary (AD), surfaced in the Fold Engine as the kernel_ops log + its projection. Every other ERP is not a second system — it is data re-keyed into that one model. account.account (Odoo), SKA1 (SAP) and GL_CODE_COMBINATIONS (Oracle) all land as one thing: C_ElementValue. Once folded there is no "Odoo logic" or "SAP logic" resident — only the canonical fold + the closed verb set. This table is the dictionary that lets an iDempiere veteran read an Odoo (or SAP) chart and confidently fold a counterpart into the same shape, because they recognise every term in the Canonical column.
◆ CANONICAL the fold target — iDempiere/AD is the model ✅ FOLDED witnessed to the cent (maxDiff=0c) ○ REFERENCE public data-model mapping; fold not yet witnessed

Provenance by source column: iDempiere ◆ canonical · Odoo ✅ folded (the O2C/P2P rows shipped in gen_ad_odoo.js, oracle-diffed) · SAP S/4 · Oracle EBS · Dynamics BC ○ reference (their standard table/term names are public and accurate; our fold of them is master-mapping only so far — see the honesty panel). A cell marked “—” means the concept has no direct named home in that product (often it is imperative code there, not a table).

1 · Tenancy, organisation & security

ConceptCanonical (AD / Fold) ◆iDempiere ◆Odoo ✅SAP S/4 ○Oracle EBS ○Dynamics BC ○
Tenant / companyAD_ClientAD_Clientres.companyT001 (BUKRS)GL Ledger / OUCompany
Organisation / unitAD_OrgAD_Org(branch / analytic)WERKS · VKORGInventory Org / OULocation / Dimension
UserAD_UserAD_Userres.usersUSR02 (SU01)FND_USERUser
Role / access grantAD_Role + *_AccessAD_Roleres.groups · ir.model.accessPFCG role / auth objResponsibilityPermission Set
Accounting schemaC_AcctSchemaC_AcctSchema(company CoA + currency)Ledger 0L / Ctrl AreaSet of Books / LedgerG/L Setup

2 · Master data (the WHAT)

ConceptCanonical (AD / Fold) ◆iDempiere ◆Odoo ✅SAP S/4 ○Oracle EBS ○Dynamics BC ○
Business partnerC_BPartnerC_BPartnerres.partnerBUT000 · KNA1 / LFA1HZ_PARTIES / AP_SUPPLIERSCustomer / Vendor
Product / itemM_ProductM_Productproduct.productMARA (+MARC/MBEW)MTL_SYSTEM_ITEMS_BItem
Product categoryM_Product_CategoryM_Product_Categoryproduct.categoryMATKL (material group)Item CategoryItem Category
Price listM_PriceList(_Version)M_PriceListproduct.pricelistA004 / VK11 conditionsQP price listSales Price
TaxC_TaxC_Taxaccount.taxMWSKZ (FTXP)eBTaxVAT Posting Setup
G/L account (CoA)C_ElementValueC_ElementValueaccount.accountSKA1 / SKB1GL_CODE_COMBINATIONS (seg)G/L Account
Account combinationC_ValidCombinationC_ValidCombination(account + analytic)ACDOCA assignment fieldsGL_CODE_COMBINATIONSDimension combination

3 · Documents — Order to Cash (sell-side)

ConceptCanonical (AD / Fold) ◆iDempiere ◆Odoo ✅SAP S/4 ○Oracle EBS ○Dynamics BC ○
Sales orderC_Order (SO)C_Ordersale.order(.line)VBAK / VBAPOE_ORDER_HEADERS_ALLSales Header / Line
Delivery / shipmentM_InOutM_InOutstock.picking (out)LIKP / LIPSWSH_DELIVERYPosted Sales Shipment
Sales invoiceC_Invoice (ARI)C_Invoiceaccount.move (out_invoice)VBRK / VBRPRA_CUSTOMER_TRXSales Invoice Header
Customer receiptC_PaymentC_Paymentaccount.paymentBSEG (incoming) / DZAR_CASH_RECEIPTS_ALLCash Receipt

4 · Documents — Procure to Pay (buy-side)

ConceptCanonical (AD / Fold) ◆iDempiere ◆Odoo ✅SAP S/4 ○Oracle EBS ○Dynamics BC ○
Purchase orderC_Order (PO)C_Orderpurchase.order(.line)EKKO / EKPOPO_HEADERS_ALLPurchase Header / Line
Goods receiptM_InOut (vendor)M_InOutstock.picking (in)MKPF / MSEG (MIGO)RCV_TRANSACTIONSPosted Purchase Receipt
Vendor invoiceC_Invoice (API)C_Invoiceaccount.move (in_invoice)RBKP / RSEG (MIRO)AP_INVOICES_ALLPurchase Invoice
3-way matchM_MatchInvM_MatchInv(reconcile / valuation)GR/IR clearing (WRX)PO–receipt–invoice matchItem Application

5 · Accounting & posting (the books)

ConceptCanonical (AD / Fold) ◆iDempiere ◆Odoo ✅SAP S/4 ○Oracle EBS ○Dynamics BC ○
Posted journal (the books)fact_acct / journalFact_Acctaccount.move.lineACDOCA (BSEG+BKPF)GL_JE_LINES / XLA_AE_LINESG/L Entry
Document flow / lineagekernel_ops chain(doc origin fields)(invoice_origin)VBFA (document flow)XLA linksNavigate / applied entries
Trial balance / statementfoldStatement / foldTrialBalancePA_Report(financial report)FAGLB03 / Report PainterFSG / FRxAccount Schedule

6 · Process, rules & workflow

ConceptCanonical (AD / Fold) ◆iDempiere ◆Odoo ✅SAP S/4 ○Oracle EBS ○Dynamics BC ○
Document statusDocStatus + C_DocType FSMC_DocType / DocumentEnginestate field (Python)Status mgmt (BSVZ)Workflow statusDocument Status enum
Workflow engine (WfMC)AD_WorkflowAD_Workflowautomated actions wkf removed v8SAP Business WorkflowOracle Workflow BuilderPower Automate / approvals
Validation ruleAdModelVal · AD_Val_RuleModelValidator@api.constrains (code)BAdI / GGB0 (code)business event (code)OnValidate trigger (code)
Field derive / calloutAdCallout · AD_Column.CalloutCalloutonchange (code)field exit / PBO (code)defaulting ruleOnAfterValidate (code)
Change log / provenancekernel_ops (signed op-log)AD_ChangeLogmail.tracking.valueCDHDR / CDPOSFND audit trailChange Log Entry
How to read the rules rows (§6). Where a cell says “(code)”, that ERP keeps the rule in an imperative method body, not a table. We do not import that code — we fold its op-log effect (DERIVE / VALIDATE / ACT) into the canonical column, and route genuinely-custom logic to a plugin. So the left two columns are always data; the right three are sometimes data (declarative config) and sometimes code (which becomes an effect or a plugin). That asymmetry — declarative on the left, mixed on the right — is the whole migration thesis in one table.

7 · Cost of change — the elementary case

The Rosetta rows above show where a fact lives. This section shows what it costs to change one. Start with the most boring change in any ERP: a clerk keeps overwriting an auto-filled field, so the business says “lock the Sales Order Description field — make it read-only.” Read the two columns down.

StepiDempiere (Java monolith) ◆Fold Engine (AD-declarative) ✅
What you editAD_Field.IsReadOnly = 'Y' — one dictionary rowthe same AD_Field.IsReadOnly = 'Y'
Through whatrunning Java server + Postgres + admin login, via the AD windowedit the row → signed op-log append (kernel_ops)
Recompile?No — it is AD-drivenNore-fold = re-read, not recompile
Takes effectreset cache / re-loginon the spot — the form repaints, no reload
Reaches other sitescentral DB onlyop-log replays to every node, signature verified
Server neededyesnone

Witnessed live: W-AD-SELFEDIT — a signed AD_Field edit re-folds the open form on the spot (field count 26→25→26), no reload, no recompile. The honest point: even iDempiere makes this change declarative — that is exactly why the engine borrows its dictionary rather than re-authoring it. The Fold Engine does not win on “is it config”; it wins on no server, no cache reset, and self-propagation by op-log.

Now the gradient. Cost stays near-zero while a change is declarative, and rises only when the logic is genuinely imperative — and even then it is bounded to a single plugin.

Change requestNatureiDempiere cost ◆Fold Engine cost ✅
Lock a field (read-only / mandatory)declarativeedit AD row, reset cacheedit AD row → op-log → re-fold, no server
Default payment term from the partnerdeclarative bindingset AD_Column.Calloutset AD_Column.CalloutAdCallout dispatch
Block an order over credit limitdeclarative ruleAD_Val_Rule configAD_Val_RuleAdModelVal fold
Bespoke rebate arithmeticimperative (custom)new Java class, recompile, restartone plugin (.foldbundle), engine untouched
Change how an order posts to GLimperative (core)edit MOrder.completeIt(), rebuild + redeploy the monolithfold the op-log effect (config + post_resolver), else a plugin
Verdict. A declarative change is near-zero and self-propagating; an imperative change is bounded to one plugin — never a monolith rebuild. Cost scales with how imperative the change is, not with the size of the system. This is the same thesis as the cost equation below, seen from the change side instead of the build side.

§7 in numbers — one real change, counted. Not a stopwatch (noisy) and not the whole-codebase LOC ratio — the effort surface of one identical change done both ways: “block completing a Sales Order when it would breach a custom CreditCeiling on the customer” (a real customisation that genuinely needs a Java ModelValidator on the iDempiere side, not config).

Effort axis (one real change)iDempiere — OSGi ◆Here — fold ✅
Lines to touch *~50 across 4 files~16 across 2 files
Handling checks (steps that can fail)9 — import-package · register · AD sync · build · resolve · restart · log-clean · 2× acceptance4 — re-fold reads · handler pinned · 2× acceptance
Data / DB artifacts5 AD rows + DDL, carried by a 2Pack/SQL migration re-applied & verified on every target instance (×N)1 declarative slice edit — the slice is the schema; 0 migrations: one signed op-log append self-replays to all N nodes
Forced build + restart2 — compile + server restart0

* Lines is a forecast estimate (each line enumerated in the witness card), pending an actual wc -l on a built artifact each side. The other three axes — checks, DB artifacts, build/restart — are structural facts, exact: they are fixed by OSGi (which forces a build→resolve→restart loop and a per-instance migration) versus the fold (which forces neither). The decisive saving is the bottom two rows: build+restart 2 → 0, and a per-instance migration ×N collapsing to one signed op-log append.

8 · The cost equation — what actually scales

A common first reading is that this architecture just moves cost: you save on UI and DB binding, then pay it back managing a cryptographic op-log. That is the wrong equation — it confuses a one-time substrate with a recurring tax. The honest accounting:

Cost componentScales with…Honest size
Op-log / integrity substrate (“git-for-data”)nothing — written oncefixed. Orthogonal to rule count; it secures whatever the fold produces across nodes. Not a per-rule tax.
Business-rule complexity (O2C, P2P, GL, FX)dictionary coveragecheap because the dictionary is borrowed and already proven by 20 years of iDempiere production — you pay to interpret, not to author.
UI / dictionary stitchsurfaces exposedthe real ongoing engineering — surfacing the engine correctly per window.
Oracle-diffing disciplineclaims of equivalencethe proof the interpretation is faithful — diff to real iDempiere (idempiere_test) to the cent.
Why it is compression, not relocation. The whole thesis rests on the dictionary being borrowed and trusted, with the oracle as the proof it was read correctly (measured: ~51× fewer code lines built so far, ~21× at conservative full parity, zero server — see §10). Had we authored our own dictionary, the original “immense cost of custom arithmetic to fold to the cent” would genuinely apply — and the op-log-as-paradox reading would hold. We didn’t: we interpret a proven dictionary and diff to an oracle. So rule cost ∝ dictionary coverage (cheap), the op-log stays a fixed one-time substrate, and the real ongoing cost is the stitch + oracle-diff — exactly the elementary change matrix in §7, read as an equation.
What the industry actually spends — and why the saving lands here. Public benchmarks agree the recurring cost dwarfs the build: maintenance is 70–90% of an on-prem ERP's TCO (50–80% across software broadly; the IEEE Computer Society puts it at 60–80% of lifecycle cost), and vendor support/maintenance alone runs 15–25% of license — every year. “Development happens once; maintenance continues for the system's life.” This architecture removes the two biggest recurring drivers on the read/fold path — server & license renewal (no server of record) and the recompile-redeploy cost of every change (§7: declarative edits re-fold, no rebuild). So the saving concentrates on the largest TCO bucket — the yearly maintenance line — not the one-time build. We publish no single “% saved” figure — TCO varies per org and inventing one would betray the non-invent rule; the honest claim is where the saving lands, each clause above traceable to a citation or a witnessed fact (~21–51× less code, zero-server, W-AD-SELFEDIT — §10).

Sources: maintenance share of TCO — ScienceSoft · Galorath (IEEE 60–80%); ERP vendor support % of license — Panorama Consulting (18–25%) · Panorama — Cloud ERP Cost; build-once vs maintain-forever — Idealink. Peer-reviewed primary sources: Lientz & Swanson, CACM (1978) — the foundational 487-organisation survey · Glass, Facts and Fallacies of Software Engineering (2002) — the canonical 40–80% of lifecycle cost range · Erlikh, IEEE IT Professional 2(3):17–23 (2000) — legacy upkeep ≈80% of TCO; categories standardised in ISO/IEC 14764. Not a universal law: the range is wide by design — long-lived, regulated, heavily-customised systems (ERP is the textbook case) sit high (70–90%); short-lived or throwaway software sits low (20–40%). So this is strongest exactly where ERP lives, not a claim about all software. Figures are third-party research; our contribution is only the architecture that moves the saving onto that bucket.

9 · For the iDempiere OSGi plugin maintainer

If you have shipped ModelValidators, Callouts and custom Doc_* posting as OSGi bundles, most of what you maintain is not business logic — it is OSGi plumbing and upgrade-breakage. That is exactly the recurring cost the table below removes. Read it idiom for idiom: the OSGi tax disappears; the business logic does not.

What you maintain in iDempiere / OSGi todayHere (Fold Engine) ✅
MANIFEST.MF + Import-Package/Export-Package version ranges, bndnone — a plugin is a JS .foldbundle module, no manifest wiring
Tycho/Maven build, target platform, p2 repositorynone — no compile step, no target platform
Declarative Services components, Bundle Activator, IModelValidationEngine.addModelValidatorregister a handler in the registry — no DS lifecycle
Drop bundle in plugins/, restart the OSGi runtime, resolve wiringship the module — re-fold = re-read, no restart
ClassNotFoundException / unresolved-bundle / classloader-leak debugginggone — no classloader graph to resolve
Every iDempiere upgrade can break your plugin (Java API drift, package bumps)you fold the dictionary, not compile against a moving Java API — the AD is the stable contract
What stays the same — the discipline ports. You still author the genuinely-custom logic (a bespoke rebate, a non-standard posting rule) — it lands as a registered handler / .foldbundle plugin instead of an OSGi DS component (doctrine: foreign imperative code is never auto-imported — it becomes a plugin). Money discipline is identical: your BigDecimal · HALF_UP · integer-cents habits carry 1:1 — bigdecimal.js is proven bit-equal to Java BigDecimal. And you recognise every dictionary point: a ModelValidator hook → AdModelVal/AD_Val_Rule; a CalloutAD_Column.CalloutAdCallout; custom Doc_* posting → post_resolver (config) or a plugin.
What you often delete. A large share of real iDempiere plugins are ModelValidators/Callouts expressing what the AD can hold declaratively — mandatory, read-only, default, validation. Those stop being a bundle at all: they become an AD_Field/AD_Val_Rule edit that re-folds live (witnessed: W-AD-SELFEDIT). The plugin you'd have built and maintained forever no longer exists.
What is new to learn — there is a curve. The op-log model (“git-for-data”): plugin effects are signed ops, and you think in DERIVE / VALIDATE / ACT effects rather than method overrides; provenance is the signed kernel_ops chain, not AD_ChangeLog. Registration is explicit, not OSGi service auto-wiring (the host handler set is pinned). Plugins are JS, not Java — idiom translation for a model/callout/validator dev; a bigger shift if your work was deep ZK UI. Honest caveats: the JS plugin SDK is younger than 20 years of OSGi ecosystem (fewer examples, more edges), and deep ZK customisation does not map 1:1 — that is the “stitch” cost §8 names.

10 · Measured — both sides counted, on disk and in code

The sections above argue the shape of the cost. This one is just a tape measure. Every figure below is a real du/wc/sqlite count of iDempiere itself against this engine — same demo tenant (GardenWorld), measured 2026-06-06 / -12, no estimates in the left three columns.

Layer (measured)iDempiere — Java / Postgres ◆This — JS / SQLite ✅Reduction
Definition — the AD seedAdempiere_pg.dmp = 45.2 MBad_seed.db = 26.1 MB (full-width, no slice-holes)~1.7×
A live tenant on diskPostgres 143 MB (41 heap + 39 idx + 6 TOAST + ~57 bloat)SQLite 43 MB (925 tables · 187,133 rows) → 8.5 MB zstd backup3.3× (16.8× compressed)
Runtime code1,427,147 Java LOC · 4,465 files · 294 .zul · 60 plugins28,184 JS LOC · 132 files (static + SQLite-WASM)~51× lines, built so far
Stack to run itJVM + Postgres + ~3.7 GB build, server-boundstatic files, offline-capable, no server of recordserver → none
The honest caveat — read this before quoting any ratio. These are delivery / definition reductions, not feature parity. The 28K JS renders the dictionary and folds the paths built so far (O2C, the posted journal, signed rule-edit) — it does not re-implement the full transactional Java server. So far only ~0.2% of the M-class transactional logic is folded (M*.java ≈ 105K code-LOC). The code ratio falls as real coverage grows (it was 89× → 76× → 51× as more was folded) — so the honest headline is the conservative ~21× projected at full parity (≈68K JS), not the eye-catching early number. What is genuinely removable is the server/build stack and the generic AD-interpretation engine (leaner because the dictionary is self-describing); what is irreducible is each transactional verb, folded deterministically — that is the whole thesis, stated against its own measuring tape.

Source: real measurement, not estimate — internal/BLOAT_MEASUREMENT.md (live docker Postgres → migrate_pg_to_sqlite.js, 2026-06-06) + LOC re-counted 2026-06-12; the model-layer denominator is docs/ERP_MODEL_ARCHETYPE.md (the MOrder archetype + ~25 completeIt deltas). Full coverage accounting: Coverage Matrix.

Companions: Benchmark Comparison — the engine vs iDempiere live in-browser; both sides measured from real source (51× LOC, 26× by size), re-counted each deploy · Fold Engine Black Book — the deep dev manual (op-log programming, how to extend the engine) · ERP Rosetta Stone (iDempiere-Java → Fold Engine) — the developer idiom map · ACDOCA Fold Plan — the SAP headline (the Universal-Journal fold) · Coverage Matrix — which surfaces are proven equivalent · Migrate & Compare — the thesis & honesty panel.  ·  Reference table/term names are public product documentation; their fold into the canonical column is proven only for iDempiere (◆) and Odoo (✅) today — SAP/Oracle/Dynamics (○) are the campaign ahead.