Skip to content

Business Affiliate: Data Model

The Business Affiliate module stores an organization’s identity and contact information in business_affiliate, with each role that organization plays (vendor, customer, carrier, operator, manufacturer, other) broken out into a child row in business_role. This page is the field-complete reference for both tables and their DTOs; see Data Models — Reading Guide for the shared envelope shapes referenced throughout, and for why this module predates the occurrence pattern.

business_role is a same-module child table (parent_eid, no FK). Other modules pin a specific role, not the affiliate directly — Item pins a MANUFACTURER role and a VENDOR role (per supply), and Orders pins a VENDOR role for its supplier binding.

PlantUML diagram

ColumnTypeNullRole
idUUIDNOT NULL (PK)identity
effective_as_oftimestampNOT NULLidentity
recorded_as_oftimestampNOT NULL, default current_timestamp(3)identity
bts_author (was author)varchar(255)NOT NULLbookkeeping
eidUUIDNOT NULLidentity
previousUUIDNULLidentity
retiredbooleanNOT NULL, default falsebookkeeping
created_by, created_at_effective, created_at_recordedvarchar / timestamp(3)NOT NULLbookkeeping
tenant_idUUIDNOT NULLidentity
ba_namevarchar(255)NOT NULLpayload
legal_name, legal_legal_name, legal_country, legal_tax_id, legal_registration_id, legal_naics_codevarcharNULLpayload — CompanyInformation component
contact_salutation/first_name/middle_name/last_name/job_title/email/phonevarchar(255)NULLpayload — Contact component
contact_postal_address_address_line_1/_2/city/state/postal_code/countryvarcharNULLpayload — nested PostalAddress
contact_postal_address_geo_location_latitude/longitude/altitudedouble precisionNULLpayload — nested GeoLocation
contact_emails, contact_phones, contact_addresses, contact_sitesJSONBNULLpayload — Contact map fields
main_address_address_line_1/_2/city/state/postal_code/countryvarcharNULLpayload — PostalAddress component
main_address_geo_location_latitude/longitude/altitudedouble precisionNULLpayload
other_contactsJSONBNULLpayload — Map<String, Contact>
other_addressesJSONBNULLpayload — Map<String, PostalAddress.Value>
termsvarchar(2048)NULL, default NULLpayload — free-text payment terms
bts_author_sub (was oidc_sub), bts_note (was note), bts_created_by_subvarcharNULLbookkeeping

Constraints: PK id. Unique: none — see Schema notes; name uniqueness is enforced only in application code.

ColumnTypeNullRole
idUUIDNOT NULL (PK)identity
effective_as_oftimestampNOT NULLidentity
recorded_as_oftimestampNOT NULL, default current_timestamp(3)identity
bts_author (was author)varchar(255)NOT NULLbookkeeping
eidUUIDNOT NULLidentity
previousUUIDNULLidentity
retiredbooleanNOT NULL, default falsebookkeeping
created_by, created_at_effective, created_at_recordedvarchar / timestamp(3)NOT NULLbookkeeping
parent_eidUUIDNOT NULLidentity — parent-linking metadata, not a SQL FK
rolevarchar(255)NOT NULLpayload — BusinessRoleType enum
notesvarchar(8192)NULLpayload — free text about the affiliate in this role; see BusinessRole.notes
ba_namevarchar(255)NOT NULLbookkeeping — denormalized copy of the parent’s ba_name
bts_author_sub (was oidc_sub), bts_note (was note), bts_created_by_subvarcharNULLbookkeeping

Constraints: PK id. Unique: none — see Schema notes. No embedded-component families — all payload columns are scalar.

FieldTypeStored or computedNotes
eId, name, legal, contact, mainAddress, contacts, addressesmixedStored1:1 with business_affiliate columns
termsString?StoredFree-text payment terms, up to 2,048 characters; defaults to null. Preserves empty strings and whitespace; not searchable or sortable.
FieldTypeStored or computedNotes
eId, name, role, notesmixedStored1:1 with business_role columns

BusinessRole.notes and an item supply’s orderingNotes

Section titled “BusinessRole.notes and an item supply’s orderingNotes”

Both hold ordering information as free text, at two different scopes. The notes of a supplier’s VENDOR role apply to every supply from that supplier: an account number, an ordering portal, a minimum order value. An item supply’s orderingNotes (see Item: Data Model) apply to one item from one supplier: a part to ask for by name, a pack size to avoid. The two fields are independent: neither is seeded from, copied to or kept in step with the other.

BusinessAffiliateRoleDetails (read model, GET .../with-details/{entity-id})

Section titled “BusinessAffiliateRoleDetails (read model, GET .../with-details/{entity-id})”
FieldTypeStored or computedNotes
businessAffiliateBusinessAffiliateStoreddirect read
rolesList<BusinessRoleType>ComputedroleService.rolesFor(...).map { it.role }

VendorLookupResult / BusinessRoleReference.Value (response DTO, GET .../lookup/vendors — the shape used everywhere a module pins a role)

Section titled “VendorLookupResult / BusinessRoleReference.Value (response DTO, GET .../lookup/vendors — the shape used everywhere a module pins a role)”
FieldTypeStored or computedNotes
nameStringComputedjoined from the parent affiliate
eIdEntityId?Computedthe role’s own eId (aliased brTable.eId)
affiliateEIdEntityId?Computedthe parent affiliate’s eId
rIdRecordId?Computedalways null for lookup results — floating
retiredBooleanComputedalways false for lookup results
provenanceProvenance.Value?Computedaliased from the role’s own author/recorded-time
roleBusinessRoleTypeComputed@Transient — stamped by the caller, not read from any column

This BusinessRoleReference.Value shape is what appears embedded on the manufacturer field of Item and the reference field of Orders’ SupplierBinding — same DTO, per-slot role stamp.

BusinessAffiliateLookupResult, BusinessAffiliateRoles (list/lookup DTOs)

Section titled “BusinessAffiliateLookupResult, BusinessAffiliateRoles (list/lookup DTOs)”
FieldTypeStored or computedNotes
matchesList<BusinessAffiliateLookupEntry>Computedfuzzy-match query, de-duplicated and limited
rolesList<BusinessRole>Storeddirect list of child records
FromField/ColumnTargetKind
ItemmanufacturerBusinessRole (MANUFACTURER)floating; pins to a tombstone rId on role retirement
Itemitem_supply.supplierBusinessRole (VENDOR)floating; pins to a tombstone rId on role retirement
OrdersSupplierBinding.referenceBusinessRole (VENDOR)floating; pins on retirement; frozen post-Submit
business_role.parent_eidbusiness_affiliate.eidsame-module parent-childno SQL FK — BusinessRoleReferenceComponent flattens the reference into <prefix>_* columns with no FK

The <prefix>_* reference-cluster shape these consumers embed (entity_id, record_id, name, retired, provenance_*) is the one described once in Data Models — Reading Guide.

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

MethodPathPurposeMain refusals
(framework)create, readByEid, readByRId, update, delete, query, history, bulkStandard data-authority CRUD on BusinessAffiliate
GET{entity-id}/with-detailsThe affiliate plus its enabled roles
GET{entity-id}/rolesList roles for an affiliate
POST{entity-id}/rolesCreate a role
PUT{entity-id}/roles/{business-role}Update a role
DELETE{entity-id}/roles/{business-role}Delete a role
GETlookupFuzzy name lookup, optional role filter
GETlookup/vendors (canonical, reference-data/business-affiliate)Fuzzy VENDOR-role lookup, ready-to-link references — the FE’s vendor-linking capability

BusinessAffiliateInput accepts terms on create and update, and affiliate reads return it. Values longer than 2,048 characters are refused with HTTP 400 identifying terms. PUT replaces the complete entity: omitting terms or explicitly sending null clears it. Existing affiliates read terms as null until a value is supplied. The field has no search index or lookup behavior.

Emits UpdateEntity (BA rename), and NewEntity/UpdateEntity/DeleteEntity for BusinessRole. Item’s ItemRoleListener and Orders’ OrderSupplierRoleListener both subscribe to the role channel: a VENDOR update propagates a name change to every linking item_supply/order (Orders only touches DRAFT orders — never SUBMITTED or terminal ones); a VENDOR delete marks the reference retired; MANUFACTURER events drive the equivalent cascade in Item only.

  • No database unique constraint on affiliate name. Uniqueness on (ba_name, tenant_id) is enforced only in application code (BusinessAffiliateValidator.validateForCreate/validateForUpdate), not a SQL UNIQUE constraint.
  • Same gap on business_role — no SQL UNIQUE constraint exists on this table at all.
  • This module predates the occurrence-kind pattern. No *OccurrenceKind type exists for either BusinessAffiliate or BusinessRole; both implement the generic EntityPayload interface and surface mutations through the generic DataAuthorityNotification.NewEntity/UpdateEntity/DeleteEntity cases rather than a module-specific kind enum. (Contrast Orders, which does have OrderOccurrenceKind — the pattern exists elsewhere in the stack but wasn’t brought into this module.)