Skip to content

Item: Data Model

The Item module stores a material type’s full descriptor — classification, physical attributes, print sizing, primary/secondary supply, manufacturer, identifiers — in item, with each of an item’s possible supply sources broken out into child rows in item_supply. Four supporting tables (batch_job, item_draft, tenant_settings, user_settings) round out the module but aren’t part of the Item entity itself. This page is the field-complete reference for all six; see Data Models — Reading Guide for the shared envelope shapes referenced throughout, and for why this module predates the occurrence pattern.

item_supply is a same-module child table (parent_eid, no FK — cross-Universe rule). item’s own primary_supply_*/secondary_supply_* clusters are a re-derived projection of the authoritative item_supply rows, refreshed on every write rather than being client-authoritative. Both item and item_supply hold floating references into Business Affiliate — manufacturer and supplier respectively.

PlantUML diagram

ColumnTypeNullRole
idUUIDNOT NULL (PK)identity
effective_as_oftimestampNOT NULLidentity
recorded_as_oftimestampNOT NULL, default now()identity
bts_authorvarchar(244)NOT NULLbookkeeping
eidUUIDNOT NULLidentity
previousUUIDNULL, FK → item.ididentity
retiredbooleanNOT NULL, default falsebookkeeping
tenant_idUUIDNOT NULLidentity
bts_author_sub, bts_note, bts_created_by_subvarcharNULLbookkeeping
created_by, created_at_effective, created_at_recordedvarchar / timestampNOT NULLbookkeeping
item_namevarchar(255)NOT NULLpayload
descriptionvarchar(8192)NULLpayload
image_urlvarchar(8192)NULLpayload
classification_type, classification_sub_typevarchar(255)NULLpayload
use_casevarchar(255)NULLpayload
gl_codevarchar(255)NULLpayload
physical_locator_facility/department/location/sub_locationvarchar(255)NULLpayload
internal_skuvarchar(255)NULLpayload
min_quantity_amount / min_quantity_unitdouble / varchar(255)NULLpayload
notesvarchar(8192)NULLpayload
card_notes_defaultvarchar(8192)NULLpayload
taxablebooleanNULLpayload
primary_supply_supply_eidUUIDNULLpayload — pointer into item_supply
primary_supply_supplier_ref_entity_id / _affiliate_eidUUIDNULLpayload
primary_supply_supplier_ref_namevarchar(255)NULLbookkeeping — value-snapshot
primary_supply_supplier_ref_record_idUUIDNULLbookkeeping — version-pin
primary_supply_supplier_ref_retiredbooleanNOT NULL, default falsebookkeeping
primary_supply_supplier_ref_provenance_updated_by/_atvarchar / bigintNULLbookkeeping
primary_supply_name, primary_supply_sku, primary_supply_order_method, primary_supply_urlvarcharNULLbookkeeping — re-derived from item_supply every write
primary_supply_order_quantity_amount/_unit, primary_supply_unit_cost_value/_currency, primary_supply_average_lead_time_length/_time_unitmixedNULLbookkeeping — re-derived
secondary_supply_* (same sub-column set as primary_supply_*)mixedNULLsame roles as the primary cluster
default_supplyvarchar(255)NULLpayload
default_supply_eidUUIDNULLpayload
card_size, label_size, breadcrumb_size, item_colorvarchar(255) (enums)NULLpayload
manufacturer_ref_entity_id / _affiliate_eidUUIDNULLpayload
manufacturer_ref_namevarchar(255)NULLbookkeeping
manufacturer_ref_record_idUUIDNULLbookkeeping — version-pin, set only on retirement
manufacturer_ref_retired, manufacturer_ref_provenance_updated_by/_atmixedNULL/NOT NULLbookkeeping
total_inventory_count_amount/_unitdouble / varchar(255)NULLpayload
last_count_date_timestamp/_time_zonetimestamp / varchar(255)NULLpayload
identifiers_upc/_ean/_gtin/_isbn/_asinvarchar(255)NULLpayload
additional_qrs, labelsJSONBNOT NULL, default []payload
search_texttext, GENERATED STORED—bookkeeping — DB-generated search index, excluded from application writes

Constraints: PK id; FK fk_item_previous__id. No supplier/manufacturer FK anywhere — cross-Universe rule.

ColumnTypeNullRole
idUUIDNOT NULL (PK)identity
effective_as_oftimestampNOT NULLidentity
recorded_as_oftimestampNOT NULL, default now()identity
bts_authorvarchar(244)NOT NULLbookkeeping
eidUUIDNOT NULLidentity
previousUUIDNULL, no FKidentity
retiredbooleanNOT NULL, default falsebookkeeping
bts_author_sub, bts_note, bts_created_by_subvarcharNULLbookkeeping
created_by, created_at_effective, created_at_recordedvarchar / timestamp(3)NOT NULLbookkeeping
parent_eidUUIDNOT NULLidentity — floats to Item’s eId, not a pinned rId
supplier_ref_entity_id / _affiliate_eidUUIDNULLpayload
supplier_ref_namevarchar(255)NOT NULLbookkeeping — value-snapshot, preserved from the earlier supplier column
supplier_ref_record_idUUIDNULLbookkeeping — version-pin
supplier_ref_retired, supplier_ref_provenance_updated_by/_atmixedNOT NULL / NULLbookkeeping
supply_name, sku, order_method, urlvarcharNULLpayload
order_quantity_amount/_unit, unit_cost_value/_currency, average_lead_time_length/_time_unitmixedNULLpayload

Constraints: PK id; no FK on previous or parent_eid (cross-Universe rule). Application-level uniqueness only: one supply name per parent, enforced by ItemSupplyValidator, not a DB constraint.

TablePurposeNotable columns
batch_jobBulk-operation job tracking (shared with system.batch, not Item-specific)job_status, last_event (JSON), success_count, error_count, total, errors (JSON) — plus the standard bitemporal/bookkeeping columns
item_draftDraft store keyed by entity, not versionedentity_id (PK, UNIQUE), tenant_id, draft_value (JSON), draft_metadata (JSON), author
tenant_settingsTenant-scoped printing-settings override document (PDEV-1077)overrides (JSONB), plus the standard bitemporal/bookkeeping columns
user_settingsSame shape as tenant_settings but unscoped by tenant (a user’s overrides follow them)subject (varchar, replaces tenant_id), overrides (JSONB)

Item.Entity (the payload of EntityRecord<Item, ItemMetadata>)

Section titled “Item.Entity (the payload of EntityRecord<Item, ItemMetadata>)”
FieldTypeStored or computedNotes
eId, name, description, imageUrl, classification, useCase, glCode, locator, internalSKU, minQuantity, notes, cardNotesDefault, taxable, defaultSupply, defaultSupplyEId, cardSize, labelSize, breadcrumbSize, itemColor, manufacturer, totalInventoryCount, lastCountDate, identifiers, additionalQrs, labels1:1 with item columnsStored
primarySupply, secondarySupplyItemSupplyReference.Value?Stored, but re-derived every writereconcileItemProjection re-projects the slot from the authoritative ItemSupply before persisting
preferredSupplyItemSupplyReference?ComputeddefaultSupply name match against primary/secondary, else primary-or-secondary
nonPreferredSupplyItemSupplyReference?Computedthe slot not selected as preferred
FieldTypeStored or computedNotes
eId, supplier, name, sku, orderMethod, url, orderQuantity, unitCost, averageLeadTime1:1 with item_supply columnsStored

ItemReference.Value (the projection published by /distinct, consumed by every module that references an Item)

Section titled “ItemReference.Value (the projection published by /distinct, consumed by every module that references an Item)”
FieldTypeStored or computedNotes
eId, rId, name, retired, provenancemixedComputedItemReference.Value.fromItem(...) — retired/provenance denormalize the item’s current state at read time
FieldTypeStored or computedNotes
card, label, breadcrumbMap of size → template configStored (packaged JSON) + Computedthe default flag per form is resolved per-caller, not stored on the item row
ColumnTargetKindNotes
item_supply.supplier_ref_entity_idBusiness Affiliate — BusinessRole (VENDOR)floatingresolved via resolveVendorRef/resolveRoleRef
item_supply.supplier_ref_record_idBusiness Affiliate BusinessRole tombstoneversion-pinpinned only on retirement
item.manufacturer_ref_entity_idBusiness Affiliate BusinessRole (MANUFACTURER)floatingresolved by resolveManufacturer
item.manufacturer_ref_record_idBusiness Affiliate BusinessRole tombstoneversion-pinpinned only on retirement
item.primary_supply_* / secondary_supply_*item.item_supply (authoritative row)value-snapshotkept in sync by reconcileItemProjection and the BA→item vendor cascade
item.tenant_idtenant scopetenant/scopeitem_supply inherits scope via the parent, enforced query-side, not by a stored column

No foreign key exists from item_supply.parent_eid to item — children reference the parent by floating eId, never a DB FK to a specific row (cross-Universe rule).

Item is Live, on the legacy URL shape. The routes below are the designed shape; today the module answers on POST /v1/item/item and PUT /v1/item/item/{entity-id} — see the API Endpoint Catalog. It is converging on what is recorded here.

Follows the request/response call-shape convention in The Orders v2 Call Shape.

MethodPathPurposeMain refusals
POSTitem/addCreate an item (supplies + manufacturer resolved in-transaction)
PUTitem/updateUpdate an item
(framework)readByEid, readByRId, forceDelete, queryNode, distinctNode, historyNode, bulkNode, draftNodeGeneric data-authority CRUD/query/history/bulk/draft surface
POSTprint-label, print-breadcrumbPrint card labels / breadcrumbs for a set of items
GETsettings/printingEffective print-template configuration
POST/PUT/DELETE/GET{parent-item-id}/supply[/{item-supply-id}]Add/update/remove/list an item’s supplies
POST/GETlookup, lookup/{pageId}Fuzzy lookup over the generated search text
GETby-codeExact code resolution (QR/UPC/EAN/GTIN/ISBN/ASIN)
POSTimage-upload/request-upload-credentialsPresigned S3 upload credentials + CDN URL
GETlookup-suppliers, lookup-units, lookup-items, lookup-types, lookup-subtypes, lookup-usecases, lookup-facilities, lookup-departments, lookup-locations, lookup-sublocationsFuzzy type-ahead lookups
POSTCSV upload routesBulk CSV item import (implemented outside this module, in system.batch.csvupload)

Canonical reference-data aliases for the supply routes also exist at /v1/reference-data/item/item-supply/supply/..., alongside legacy /v1/item/item/... paths sharing the same handlers.

Emits NewEntity/UpdateEntity on item create/update. Listens for BusinessRole change notifications — VENDOR changes drive the vendor cascade onto linked item_supply rows and their parent item projections; MANUFACTURER changes drive the manufacturer cascade.

  • Duplicate index in the migration SQL. idx_item_seondary_supplier_eid (sic, misspelled in the migration itself) is created twice — the second CREATE INDEX is a no-op, not a functional bug, but it’s a pre-existing typo in V010__item_supply.sql, left as found.
  • This module predates the occurrence-kind pattern. No ItemOccurrenceKind exists; change notification uses the generic DataAuthorityNotification<Item, ItemMetadata> sealed type (NewEntity/UpdateEntity/DeleteEntity) instead of a domain-specific kind enum.
  • primary_supply_*/secondary_supply_* on item are a maintained projection, not client-authoritative data — every write re-derives them from the authoritative item_supply rows, so treat item_supply as the source of truth for supply details even though the fields are readable directly off item.