Data Models — Reading Guide
Each module in the Orders v2 stack has a Data Model page: a field-complete reference of what that module stores, what a read returns, and how it links to its neighbors. This page is the reading guide — it explains the two shapes every module page uses, describes the envelope every table and DTO shares so the module pages don’t repeat it, and maps out the six pages plus three things worth knowing before you read them.
These pages describe the system as built at the Phase-2 stack tip, for engineers who already know the domain and don’t need a manufacturing or logistics primer. They are a “traditional data model” view — columns, DTOs, endpoints — not a design narrative; for the why behind a module’s design, see that module’s own overview page (linked from each data-model page).
How to read these pages
Section titled “How to read these pages”Every module page splits its state into two shapes, because they are genuinely different things:
- Stored shape — the database tables, field by field. This is what a mutation writes: a new row, never an edit to an old one. Every accepted or refused action against an entity appends a row rather than updating one in place.
- Projected shape — the DTOs a read returns. Some fields are a direct copy of a stored column; others are computed — worked out from one or more stored fields at read time rather than kept as their own column. Orders’
CONFIRMED/RECEIVINGreading and Kanban’sstatusfield are both computed this way; each module page’s Projected shape table marks every computed field and says how it’s derived.
They differ because state in this system isn’t a single mutable record — it’s computed from history. A correction is a new row layered on top of the old ones, not an edit to them; nothing already committed is ever overwritten. That’s also why most tables carry a previous column chaining each row to the version before it, and why a table’s full change history is available through its .../log endpoint, not just its current state.
The shared record envelope
Section titled “The shared record envelope”Every module’s tables and DTOs are built from the same handful of shapes. They’re described once here; the module pages reference this section instead of repeating the field lists.
Bitemporal and bookkeeping columns
Section titled “Bitemporal and bookkeeping columns”Nearly every table in every module carries this column group, regardless of what else it stores:
| Column | Meaning |
|---|---|
id | the specific version row’s own primary key — the rId on the wire |
eid | the entity’s stable identity, unchanged across every version of it — the eId on the wire |
tenant_id | the owning tenant |
effective_as_of | when the change is claimed to hold, in business time (the caller’s claim) |
recorded_as_of | when the database learned of it (DB-owned, defaults to now()) |
previous | the prior version row’s id — a soft self-reference forming the version chain; not a DB foreign key in most modules |
retired | tombstone flag |
bts_author, bts_author_sub, bts_created_by_sub | who made the change — display name, verified OIDC subject, and the OIDC subject of whoever created the entity |
bts_note | the caller’s free-text note for this specific mutation |
created_by, created_at_effective, created_at_recorded | genesis-row bookkeeping, carried forward unchanged on every later version |
Several modules renamed their occurrence-envelope columns (kind→occ_kind, outcome→occ_outcome, reason→occ_reason) to conform to common-module 16.0.0’s OccurrenceColumns trait; each module page notes whether it did. The Kotlin property names (kind/outcome/reason) are unaffected either way.
The occurrence envelope
Section titled “The occurrence envelope”Most tables (all but Item and Business Affiliate, which predate the pattern — see module map below) also carry:
| Column | Meaning |
|---|---|
kind (or occ_kind) | which action produced this row — the module’s *OccurrenceKind enum |
outcome (or occ_outcome) | COMMITTED or REFUSED |
reason (or occ_reason) | the refusal code, set only when REFUSED |
A row is the pairing of one occurrence (what was attempted, and how it turned out) with the resulting state (the payload columns as they stood immediately after). That’s why a .../log endpoint can return every row — committed and refused — most recent first: each row already carries its own history entry.
The floating-reference cluster
Section titled “The floating-reference cluster”Several modules point at an entity in another module using the same recurring column group (with a per-reference prefix, e.g. s_supplier_*, item_reference_*, manufacturer_ref_*):
| Sub-column | Meaning |
|---|---|
<prefix>_entity_id | the referenced entity’s eId — re-resolved by eId on every read while the reference is floating |
<prefix>_record_id | a specific version’s rId — a version pin, set once the reference is frozen (or, in some modules, only once the referenced record retires) |
<prefix>_name (or another value-snapshot field) | a denormalized copy of the referenced record’s display fields, kept in sync by an observer-driven cascade while the reference floats |
<prefix>_retired | denormalized copy of the referenced record’s own tombstone flag |
<prefix>_provenance_updated_by, <prefix>_provenance_updated_at | the referenced record’s own last-update provenance (the ProvenanceComponent shape — this exact suffix pair recurs on every reference cluster) |
Each module page’s Cross-module references section says, per reference, whether it’s currently floating, version-pinned, or a value-snapshot only — and where the pin freezes, if it does.
On the wire: EntityRecord and OccurrenceDto
Section titled “On the wire: EntityRecord and OccurrenceDto”Two shapes wrap every module’s payload, defined once in common-module and reused everywhere:
EntityRecord<Payload, Metadata> — what every GET .../{eId} and every mutation-response wraps its record in:
| Field | Meaning |
|---|---|
rId | the version row’s id |
asOf | the bitemporal coordinate (effective + recorded) |
payload | the module’s own payload type |
metadata | the module’s own metadata type (usually just tenantId, sometimes a parent link) |
author, oidcSub | who made the change |
createdBy, createdBySub, createdAt | genesis bookkeeping |
previous | the prior version’s rId |
retired | tombstone flag |
note | the caller’s note for this mutation |
OccurrenceDto — the shape of every entry in a .../log response (a list, most recent first):
| Field | Meaning |
|---|---|
rId | the row’s id |
kind, outcome, reason | the occurrence envelope |
effectiveAsOf, recordedAsOf | both bitemporal coordinates, as epoch millis |
author | who made the change |
note | the caller’s note, if any |
Module pages’ Projected shape tables list only what’s specific to that module’s own DTOs; they don’t repeat these two shapes.
Module map
Section titled “Module map”| Module | Page | Stored shape | Notes |
|---|---|---|---|
| Orders | Orders: Data Model | order, order_line | |
| Demand | Demand: Data Model | demand_item, production_delivery | |
| Kanban Cards | Kanban Cards: Data Model | kanban_card | predates the occurrence pattern — see below |
| Inventory Pools | Inventory Pools — Data Model | inventory_pool | pools only — see below |
| Item | Item: Data Model | item, item_supply (+ supporting tables) | predates the occurrence pattern |
| Business Affiliate | Business Affiliate: Data Model | business_affiliate, business_role | predates the occurrence pattern |
Three things are worth knowing before you read the module pages, because each is a place where the stored shape doesn’t match what the name might suggest:
Staff isn’t a runtime entity at Phase 2. There is no staff table, no staff DTO, and no staff module anywhere in operations. An order’s assignee is a plain-text column — not a reference of any kind — by a recorded ruling (DT-023 cut-7c, ruling R4): the model already carries a version-pin slot for staff, but it stays inactive until staff becomes a runtime entity. See Staff for the module as it’s designed to work once that lands.
Inventory is pools only. “Inventory items” isn’t a stored concept — inventory_pool has exactly one state column, content, holding a canonical unit→amount map. There’s no per-item row, no holder reference, and no locator; see Inventory Pools — Data Model for what the module actually tracks and where item-level detail lives instead.
Kanban predates the occurrence pattern. It has a single table and two flat event-type enums (KanbanCardEvent, KanbanCardPrintEvent) storing only the most recent event, not an occurrence log with a *OccurrenceKind enum like its sibling modules. Converting kanban onto the same occurrence pattern the rest of the stack uses is planned follow-on work, not yet built at the Phase-2 stack tip covered here.
Copyright: © Arda Systems 2025-2026, All rights reserved