Skip to content

Vendor & VendorItem

1. Tổng quan

Thuộc tínhGiá trị
IDFEAT-INV-VEN
StatusStable
Ownerinventory-team
Phụ thuộcMaterial / ProductVariant, UnitOfMeasure, PurchaseOrder (caller của recordPurchase)

Vendor là một nhà cung cấp. VendorItem là junction M:N liên kết một vendor với một principal item (Material hoặc ProductVariant) cho một merchant cụ thể — với giá báo, UoM, multiplier, cờ preferred, và snapshot điều khoản hóa đơn gần nhất. Principal không bao giờ mang vendorId (ADR-0002).

2. Mô hình Entity

Trường Vendor

TrườngKiểuBắt buộcMô tả
merchantIdtextOwner
identifiertextTự động, tiền tố VEN
slugtextURL-safe
namei18n jsonbHiển thị
descriptioni18n jsonb
statustextACTIVATED / DEACTIVATED / ARCHIVED (mặc định ACTIVATED)
locationjsonb{ main, sub, long, lat, postCode }
taxNumbertextMã số thuế
currencytextMặc định VND
contactsjsonb (array)Array<IVendorContact> (mặc định [])
notetextTự do

Trường VendorItem

TrườngKiểuBắt buộcMô tả
vendorIdtextFK tới Vendor
merchantIdtextDenormalize từ Vendor.merchantId
itemTypetextMATERIAL / PRODUCT_VARIANT
itemIdtextFK target id (đa hình)
uomIdtextUoM trong catalog của vendor (soft ref)
multiplierdecimal(15,4)Quy đổi UoM-to-base
unitPricedecimal(15,4)Giá báo mỗi UoM
isPreferredbooleanPartial unique theo (merchantId, itemType, itemId) — tối đa một preferred mỗi item
statustextACTIVATED / DEACTIVATED / ARCHIVED
lastInvoicedjsonbSnapshot từ PO receive mới nhất: { unitPrice, uomId, multiplier, orderedAt, receivedAt }

3. Vòng đời

TừSự kiệnĐến
ACTIVATEDdeactivateDEACTIVATED
DEACTIVATEDactivateACTIVATED
* (chưa archive)archiveARCHIVED (terminal)

Cả VendorVendorItem chia sẻ vòng đời này.

4. Vận hành

VendorService (vendor.service.ts — 313 dòng)

Phương thứcSignatureMục đích
createAggregate{ context, data: TCreateVendorAggregateRequest }Tạo Vendor + (tùy chọn) batch VendorItem trong một TX. Tên trường aggregate: items[] (ngữ nghĩa per-parent — xem rule memory feedback_aggregate_field_naming)
updateAggregate{ context, id, data: TUpdateVendorAggregateRequest }Patch Vendor + grant/omit batch VendorItem

VendorItemService (vendor-item.service.ts — 772 dòng)

Phương thứcSignatureMục đích
create (override){ context, data, transaction? }Tạo VendorItem; validate vendor.merchantId === context.merchantId; flip isPreferred atomic nếu isPreferred=true
updateById (override){ context, id, data, transaction? }Update; cùng atomicity isPreferred
setPreferred{ context, id }Promote thành nguồn ưu tiên của vendor cho item này — atomic qua phương thức repo setPreferredAtomic
activate{ context, id }DEACTIVATED → ACTIVATED
deactivate{ context, id }ACTIVATED → DEACTIVATED
recordPurchase{ vendorItemId, unitPrice, uomId, multiplier, orderedAt?, receivedAt? }Cập nhật snapshot lastInvoiced — gọi bởi PurchaseOrderService khi receive; bỏ qua scope check (service-internal)
upsertBatch{ context, merchantId, entries: TVendorItemAssignment[], transaction }Bulk create/update — dùng bởi aggregate Vendor
omitBatch{ context, vendorId, itemIds, transaction }Soft-delete (archive) batch

5. REST Endpoints

/vendors

VerbPathAuthPermissionHandler
6× CRUD/vendorsJWT/BASICVendor.<crud>CRUD
POST/vendors/aggregateJWT/BASICVendor.createAggregateVendorService.createAggregate
PATCH/vendors/:id/aggregateJWT/BASICVendor.updateAggregateVendorService.updateAggregate

/vendor-items

VerbPathAuthPermissionHandler
6× CRUD/vendor-itemsJWT/BASICVendorItem.<crud>CRUD (với validate vendor↔merchant)
POST/vendor-items/:id/set-preferredJWT/BASICVendorItem.setPreferredsetPreferred
POST/vendor-items/:id/activateJWT/BASICVendorItem.activateactivate
POST/vendor-items/:id/deactivateJWT/BASICVendorItem.deactivatedeactivate

recordPurchase, upsertBatch, omitBatch không lộ ra REST — chỉ dùng nội bộ.

6. Sự kiện

Inbound: không trực tiếp.

Outbound: không trực tiếp. Các mutation Vendor / VendorItem chỉ được persist.

7. Pattern Quan trọng

Atomic isPreferred flip

VendorItemRepository.setPreferredAtomic thực hiện trong một câu lệnh:

  1. Hạ cấp tất cả row isPreferred=true nơi (merchantId, itemType, itemId) = nhóm của target AND id ≠ target.id.
  2. Promote target thành isPreferred=true.

Kết hợp với partial unique index trên (merchantId, itemType, itemId) WHERE isPreferred=true, điều này đảm bảo tối đa một preferred mỗi item.

Snapshot lastInvoiced từ PO receive

Bỏ qua scope check vì được gọi bên trong luồng PO receive đã được xác thực.

Backfill từ lịch sử PO sẵn có

inventory-0005-backfill-vendor-item.ts (migration one-shot) materialize các row VendorItem cho nhóm (merchantId, vendorId, itemType, itemId) bằng cách snapshot dòng PO non-cancelled mới nhất.

8. Đặt tên trường aggregate

Theo rule memory feedback_aggregate_field_naming:

AggregateTrường collection con
Vendor → VendorItemsitems[] (vendor-centric: "các items mà vendor này cung cấp")
Material → VendorItemsvendors[] (material-centric: "các vendor cung cấp material này")

Cùng bảng junction, đặt tên semantic khác nhau theo context của parent.

9. Trang liên quan

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