Skip to content

ADR-0002. Vendor link via VendorItem only — no vendorId on principals

FieldValue
StatusAccepted
Date2026-03-20
Decidersinventory-team, PM
Supersedes

Context

  • A Material (or ProductVariant) can be supplied by multiple vendors at different prices and UoMs.
  • A vendor supplies many items.
  • Real-world relationship is M:N with non-trivial attributes (price, UoM, multiplier, isPreferred, lastInvoiced snapshot).
  • Initial impulse: add vendorId column to Material (the "preferred vendor"). This was rejected.

Decision

Vendor↔principal link lives only in the VendorItem join table. Material and ProductVariant have no vendorId column. To express a "preferred vendor" for an item, set VendorItem.isPreferred = true (atomic flip via setPreferredAtomic, partial unique per (merchantId, itemType, itemId)).

Consequences

ProsCons
Single source of truth for vendor relationsQuerying "preferred vendor for material X" requires a JOIN
Naturally supports M:N: same material from 3 vendors at different prices"Material has no preferred vendor" is allowed (no NOT NULL constraint)
lastInvoiced snapshot scoped to VendorItem (one history per vendor-item pair)Need atomic flip helper (setPreferredAtomic)
recordPurchase updates one row, not a Material columnUI must show a "vendor selector" rather than a hardcoded one

Alternatives Considered

OptionProsConsWhy rejected
Material.vendorId (single)Simple JOIN-free readCan't model M:N; "secondary vendors" need parallel structure anywayDoesn't match real procurement
Material.preferredVendorId + VendorItem tableFast lookupTwo sources of truth, sync drift inevitableAnti-pattern
Vendor.itemIds[] jsonbSingle-row readsNo constraint, no queryability per-itemWorse than no decision

References

  • core/src/models/schemas/inventory/vendor-item/schema.ts
  • inventory/src/services/vendor-item.service.tssetPreferred, recordPurchase
  • Memory: feedback_vendor_via_vendoritem_only.md

Proprietary and Confidential. Unauthorized copying, distribution, or use of this software is strictly prohibited.