Skip to content

Kanban Cards: Data Model

The Kanban Cards module stores one table, kanban_card: a physical replenishment card, its serial number, the item it’s tied to, its quantity and physical location, and its lifecycle and print-lifecycle state. This page is the field-complete reference for that table and its DTOs; see Data Models — Reading Guide for the shared envelope shapes referenced throughout, and for why this module predates the occurrence pattern the rest of the stack uses.

kanban_card is bitemporal like every other module’s tables, but has no *OccurrenceKind enum — state transitions run through two flat event-type enums instead (see Schema notes). The card’s own eid doubles as CardCycleId, the phantom-typed reference Demand uses for card membership; there is no separate card-cycle table.

PlantUML diagram

The bitemporal-coordinate and bookkeeping columns come from a shared base class rather than being declared directly in this module’s table object; their names and types are confirmed from the SQL migrations.

ColumnTypeNullRole
idUUIDNOT NULL (PK)identity
tenant_idUUIDNOT NULLidentity
effective_as_oftimestampNOT NULLidentity
recorded_as_oftimestampNOT NULL, default now() (since V004)identity
previousUUIDNULL, FK → kanban_card(id)identity
eidUUIDNOT NULLidentity
retiredbooleanNOT NULL, default falsebookkeeping
bts_author (was author)varchar(255)NOT NULLbookkeeping
kanban_card_snvarchar(255)NOT NULLpayload — natural identifier; see Schema notes
item_reference_entity_idUUIDNULLpayload
item_reference_item_namevarchar(255)NULLbookkeeping
item_reference_record_idUUIDNULLbookkeeping — version-pin, used on tombstone resolution
card_quantity_amountdouble precisionNULLpayload
card_quantity_unitvarchar(255)NULLpayload
physical_locator_facilityvarchar(255)NULLpayload
physical_locator_departmentvarchar(255)NULLpayload
physical_locator_locationvarchar(255)NULLpayload
physical_locator_sub_locationvarchar(255)NULLpayload
last_event_at_time_effectivebigintNULLpayload
last_event_at_time_recordedbigintNULLpayload
last_event_kanban_card_event_typevarchar(255)NULLpayload
last_event_from_where_facility/department/location/sub_locationvarchar(255)NULLpayload
last_event_to_where_facility/department/location/sub_locationvarchar(255)NULLpayload
last_event_authorvarchar(255)NULLpayload
statusvarchar(255)NULLpayload — computed projection, see Projected shape
last_print_event_at_time_effectivebigintNULLpayload
last_print_event_at_time_recordedbigintNULLpayload
last_print_event_kanban_card_event_typevarchar(255)NULLpayload
last_print_event_authorvarchar(255)NULLpayload
print_statusvarchar(255)NULLpayload — computed projection
created_byvarchar(255)NOT NULLbookkeeping
created_at_effectivetimestampNOT NULLbookkeeping
created_at_recordedtimestampNOT NULLbookkeeping
notesvarchar(8192)NULLpayload — seeded from the item’s cardNotesDefault, then independently editable
bts_author_sub (was oidc_sub)varchar(255)NULLbookkeeping
bts_note (was note)varchar(512)NULLpayload
bts_created_by_subvarchar(255)NULLbookkeeping
item_reference_retiredbooleanNOT NULL, default falsebookkeeping — denormalized item-deleted flag (PDEV-808)
item_reference_provenance_updated_byvarchar(255)NULLbookkeeping
item_reference_provenance_updated_atbigintNULLbookkeeping

Primary key: id. Unique constraints: none — see Schema notes. Foreign keys: fk_kanban_card_previous__id (previous → kanban_card(id), RESTRICT).

Other bookkeeping object: kanban_card_serial_number, a Postgres SEQUENCE (not a column) that mints kanban_card_sn values.

KanbanCard (persisted payload / API record body)

Section titled “KanbanCard (persisted payload / API record body)”
FieldTypeStored or computedNotes
eIdEntityIdStored
serialNumberStringStored
itemItemReferenceStoredfloating ref + denormalized retired/provenance
cardQuantityQuantity.Value?Stored
locatorPhysicalLocator.Value?Stored
lastEventKanbanCardEvent?Stored
statusKanbanCardStatus?Computedthe lifecycle transition table, from current status + the incoming event type; also seeded directly at creation
lastPrintEventKanbanCardPrintEvent?Stored
printStatusKanbanCardPrintStatus?Computedsame pattern, print-lifecycle transition table
notesString?Storedreconciled on item change

KanbanCardDetails (read model, details/print endpoints)

Section titled “KanbanCardDetails (read model, details/print endpoints)”

Same base fields as KanbanCard, plus:

FieldTypeStored or computedNotes
rIdUUID?Storedthe bitemporal version row id
lookupUrlIdString?Computedbase64/gzip-encoded UrlLookup(cardId, tenantId, rId)
itemItemReferenceComputedoverrides the card’s cached reference with the resolved Item record’s current state
itemDetailsItem.EntityComputedresolved via an asOf or tombstone read

KanbanCardSummary (aggregate DTO, summary/* endpoints)

Section titled “KanbanCardSummary (aggregate DTO, summary/* endpoints)”
FieldTypeStored or computedNotes
eIdUUIDComputedsynthetic — a summary row is a group, not a card
itemItemReferenceComputedGROUP BY classifier
quantityList<Quantity>ComputedSUM(card_quantity_amount) grouped by (status, item, unit)
statusKanbanCardStatusStoredGROUP BY classifier

KanbanCardStateChange / KanbanCardPrintStateChange (lifecycle-event response envelopes)

Section titled “KanbanCardStateChange / KanbanCardPrintStateChange (lifecycle-event response envelopes)”
FieldTypeNotes
eventKanbanCardEvent / KanbanCardPrintEventbuilt for this call
previousEntityRecord<KanbanCard, KanbanCardMetadata>pre-mutation record
currentEntityRecord<KanbanCardDetails, KanbanCardMetadata>post-mutation, composed
ColumnTargetKindNotes
item_reference_entity_idItemfloatingprimary pointer, resolved via itemService.getAsOf
item_reference_record_idItem version rowversion-pinused only when item_reference_retired = true, to resolve the frozen tombstone version
item_reference_retired, item_reference_provenance_*Itemvalue-snapshotrefreshed on item update/delete notifications (PDEV-808)
physical_locator_*noneplain value objectno separate Facility/SubLocation entity exists — free-text columns
—Inventory Poolsnot referencedsearched, no references found
card’s own eid (as CardCycleId)Demand — membershipfloating, consumer-sidethe card’s eid is the cycle id; no cycle table exists in this module

previous is the bitemporal version chain (self-reference, same entity), not a cross-module link.

Mounted as the kanban-card resource. Standard CRUD (create/readByEid/readByRId/update/delete/query/history/bulk) is framework-generated; only the custom routes are listed here.

Kanban does not follow the shared call-shape convention — it is the documented exception to it. Card events are POST .../{eId}/event/{event} with no body and a single optional effectiveasof query parameter — no recordedasof; system time is the server’s on this route — and an event response returns {event, previous}: the card’s status before the event, not after. See The Orders v2 Call Shape, which records why — this module predates the convention.

MethodPathPurpose
POSTsummary/generateCards grouped by item/status
POSTsummary/requested/generate, summary/in-process/generateStatus-restricted summaries
GETdetails/{eId}Extended details of one card
POSTdetails/generate, details/by-status/{status}Query cards, return details
GETdetails/by-page/{page-id}Cursor-paged details continuation
GETfor-item/{itemEId}Cards linked to one item
POSTcards-by-items/by-itemsCards for several items, grouped
POSTprint-cards/print-cardPrint (Documint render) a set of cards
PUT{eId}/annotate/notesUpdate a card’s notes
POST{eId}/event/{request|accept|shelve|start-processing|complete-processing|fulfill|receive|use|deplete|withdraw|production-failure}Card lifecycle events
POST{eId}/event/unmarkOnly print-lifecycle event exposed as its own route

Listens for item new/update/delete notifications — creates a default card on a new item, refreshes linked cards on update, stamps item_reference_retired on delete (PDEV-808). Emits NewEntity<KanbanCard,...> on card creation. No UpdateEntity/DeleteEntity emission was found for KanbanCard itself — card mutations go through the common-module base update(), whose own notification behavior couldn’t be confirmed from this module’s code.

  • No database unique constraint on kanban_card_sn, despite it acting as the card’s natural identifier — uniqueness, if enforced at all, isn’t a DB-level guarantee here.
  • This module predates the occurrence-kind pattern. There is no KanbanCardOccurrenceKind enum; state transitions run through two flat event-type enums (KanbanCardEvent, KanbanCardPrintEvent) that store only the single most-recent event in last_event_*/last_print_event_* columns, not a full occurrence log. Compare this to demand, procurement.order, and resources.inventory, each of which has its own *OccurrenceKind enum — kanban is the one sibling module that hasn’t adopted the pattern. Bringing it onto the same shape as its siblings is planned follow-on work.