Skip to content

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).

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/RECEIVING reading and Kanban’s status field 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.

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.

Nearly every table in every module carries this column group, regardless of what else it stores:

ColumnMeaning
idthe specific version row’s own primary key — the rId on the wire
eidthe entity’s stable identity, unchanged across every version of it — the eId on the wire
tenant_idthe owning tenant
effective_as_ofwhen the change is claimed to hold, in business time (the caller’s claim)
recorded_as_ofwhen the database learned of it (DB-owned, defaults to now())
previousthe prior version row’s id — a soft self-reference forming the version chain; not a DB foreign key in most modules
retiredtombstone flag
bts_author, bts_author_sub, bts_created_by_subwho made the change — display name, verified OIDC subject, and the OIDC subject of whoever created the entity
bts_notethe caller’s free-text note for this specific mutation
created_by, created_at_effective, created_at_recordedgenesis-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.

Most tables (all but Item and Business Affiliate, which predate the pattern — see module map below) also carry:

ColumnMeaning
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.

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-columnMeaning
<prefix>_entity_idthe referenced entity’s eId — re-resolved by eId on every read while the reference is floating
<prefix>_record_ida 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>_retireddenormalized copy of the referenced record’s own tombstone flag
<prefix>_provenance_updated_by, <prefix>_provenance_updated_atthe 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:

FieldMeaning
rIdthe version row’s id
asOfthe bitemporal coordinate (effective + recorded)
payloadthe module’s own payload type
metadatathe module’s own metadata type (usually just tenantId, sometimes a parent link)
author, oidcSubwho made the change
createdBy, createdBySub, createdAtgenesis bookkeeping
previousthe prior version’s rId
retiredtombstone flag
notethe caller’s note for this mutation

OccurrenceDto — the shape of every entry in a .../log response (a list, most recent first):

FieldMeaning
rIdthe row’s id
kind, outcome, reasonthe occurrence envelope
effectiveAsOf, recordedAsOfboth bitemporal coordinates, as epoch millis
authorwho made the change
notethe 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.

ModulePageStored shapeNotes
OrdersOrders: Data Modelorder, order_line
DemandDemand: Data Modeldemand_item, production_delivery
Kanban CardsKanban Cards: Data Modelkanban_cardpredates the occurrence pattern — see below
Inventory PoolsInventory Pools — Data Modelinventory_poolpools only — see below
ItemItem: Data Modelitem, item_supply (+ supporting tables)predates the occurrence pattern
Business AffiliateBusiness Affiliate: Data Modelbusiness_affiliate, business_rolepredates 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.