ADR-0002. Liên kết Vendor chỉ qua VendorItem — không có vendorId trên principal
| Trường | Giá trị |
|---|---|
| Status | Accepted |
| Date | 2026-03-20 |
| Deciders | inventory-team, PM |
| Supersedes | — |
Bối cảnh
- Một
Material(hoặcProductVariant) có thể được nhiều vendor cung cấp với giá và UoM khác nhau. - Một vendor cung cấp nhiều item.
- Quan hệ thực tế là M:N với các thuộc tính không tầm thường (price, UoM, multiplier, isPreferred, snapshot lastInvoiced).
- Phản xạ ban đầu: thêm cột
vendorIdvàoMaterial("vendor ưu tiên"). Đã bị từ chối.
Quyết định
Liên kết Vendor↔principal chỉ tồn tại trong bảng nối VendorItem. Material và ProductVariant không có cột vendorId. Để biểu diễn "vendor ưu tiên" cho một item, set VendorItem.isPreferred = true (flip atomic qua setPreferredAtomic, partial unique theo (merchantId, itemType, itemId)).
Hệ quả
| Pros | Cons |
|---|---|
| Một nguồn-sự-thật duy nhất cho quan hệ vendor | Truy vấn "vendor ưu tiên cho material X" cần JOIN |
| Tự nhiên hỗ trợ M:N: cùng material từ 3 vendor với giá khác nhau | Cho phép "Material không có vendor ưu tiên" (không có ràng buộc NOT NULL) |
Snapshot lastInvoiced thuộc phạm vi VendorItem (một lịch sử cho mỗi cặp vendor-item) | Cần helper flip atomic (setPreferredAtomic) |
recordPurchase cập nhật một row, không phải cột Material | UI phải hiển thị "vendor selector" thay vì hardcode |
Phương án thay thế đã cân nhắc
| Phương án | Pros | Cons | Lý do từ chối |
|---|---|---|---|
Material.vendorId (đơn) | Đọc đơn giản không cần JOIN | Không mô hình hóa được M:N; "vendor phụ" vẫn cần cấu trúc song song | Không khớp với thực tế procurement |
Material.preferredVendorId + bảng VendorItem | Lookup nhanh | Hai nguồn-sự-thật, đồng bộ trôi không tránh khỏi | Anti-pattern |
Vendor.itemIds[] jsonb | Đọc một-row | Không có constraint, không query được theo item | Tệ hơn không quyết định |
Tham chiếu
core/src/models/schemas/inventory/vendor-item/schema.tsinventory/src/services/vendor-item.service.ts—setPreferred,recordPurchase- Memory:
feedback_vendor_via_vendoritem_only.md