Skip to content

Mô hình miền

Tất cả schema được định nghĩa trong @nx/core/src/models/schemas/inventory/. Tên bảng dùng PascalCase. Cột số dùng standardNumeric = decimal(15, 4).

1. ERD đầy đủ

2. Cột Chung

Mọi entity bên dưới đều thêm các cột này qua generateCommonColumnDefs() trừ khi có ghi chú.

CộtKiểuGhi chú
idtextPK, Snowflake qua IdGenerator
createdAttimestamptzmặc định now()
modifiedAttimestamptzcập nhật khi ghi
createdBytextuser id
modifiedBytextuser id
deletedAttimestamptzmarker soft-delete
metadatajsonbextension bag
statustextkhi dùng generateCommonColumnWithStatusDefs

3. Entities

3.1 InventoryLocation

Thuộc tínhGiá trị
BảngInventoryLocation
Sourcecore/src/models/schemas/inventory/inventory-location/schema.ts
Soft-delete
OwnermerchantId
TrườngKiểuBắt buộcMô tả
merchantIdtextOwner
parentIdtextSelf-ref cho phân cấp
identifiertextTự động, tiền tố LOC
isDefaultbooleanMặc định false; partial unique theo merchant khi true
statustextNEW / ACTIVATED / DEACTIVATED / ARCHIVED (mặc định NEW)
namei18n jsonbTên hiển thị
typetextInventoryLocationTypes (mặc định PHYSICAL)
locationjsonbAddress ({ main, sub, long, lat, postCode })

Invariants: chính xác một isDefault=true mỗi merchant; parentId an toàn cycle.


3.2 InventoryItem

Thuộc tínhGiá trị
BảngInventoryItem
Đa hìnhcó — (itemType, itemId) qua generatePrincipalColumnDefs({ discriminator: 'item' }); tham chiếu Material hoặc ProductVariant
TrườngKiểuBắt buộcMô tả
merchantIdtextOwner
itemTypetextMATERIAL / PRODUCT_VARIANT
itemIdtextFK target id
identifiertextTự sinh, tiền tố INI
statustextInventoryItemStatuses (mặc định ACTIVATED)

Indexes: partial unique (merchantId, itemType, itemId) WHERE deletedAt IS NULL; non-unique (merchantId), (merchantId, status). Upsert idempotent: InventoryItemRepository.ensureInventoryItem key theo (merchantId, itemType, itemId).


3.3 InventoryStock

Thuộc tínhGiá trị
BảngInventoryStock
Sourcecore/src/models/schemas/inventory/inventory-stock/schema.ts
Bucket key(inventoryItemId, inventoryLocationId, lotNumber, serialNumber) UNIQUE NULLS NOT DISTINCT
TrườngKiểuBắt buộcMô tả
inventoryItemIdtextFK
inventoryLocationIdtextFK
merchantIdtextDenormalize từ InventoryItem
quantityOnHanddecimal(15,4)Tổng stock vật lý
quantityReserveddecimal(15,4)Đã reserve cho đơn
quantityAvailabledecimal(15,4)Trường lưu trữ; service duy trì = onHand − reserved
lastCountedAttimestamptz
lastStockedAttimestamptz
lotNumbertextMở rộng bucket-key
serialNumbertextMở rộng bucket-key
expiryDatetimestamptzHỗ trợ FEFO
manufactureDatetimestamptz
averageCostdecimal(15,4)Snapshot AVCO
costingMethodtextAVERAGE (mặc định), FIFO, LIFO, v.v.

Indexes: (inventoryItemId), (inventoryLocationId), (merchantId).

Mutator atomic: InventoryStockRepository.adjustStock({ stockId, adjustOnHand, adjustAvailable, adjustReserved, forceNonNegative }) — một SQL UPDATE duy nhất với guard non-negative tùy chọn. Trả về null nếu guard fail.


3.4 InventoryTracking

Thuộc tínhGiá trị
BảngInventoryTracking
Mutabilityappend-only audit log; repository có CRUD nhưng service chỉ ghi khi stock thay đổi
TrườngKiểuBắt buộcMô tả
inventoryStockIdtextFK
merchantIdtextDenormalize từ stock chain
referenceTypetextMột trong §4; typed<TInventoryTrackingReferenceType>
referenceIdtextID doc khởi nguồn (nullable cho adjustment mồ côi)
uomIdtextĐơn vị lúc ghi
multiplierdecimal(15,4)Hệ số quy đổi UoM (mặc định 1)
quantityBeforedecimal(15,4)Snapshot trước mutation
quantityChangedecimal(15,4)Delta (có dấu)
quantityAfterdecimal(15,4)Snapshot sau mutation
effectivePricedecimal(15,4)Cost đơn vị (chỉ với write PURCHASE)
fromLocationIdtextNguồn TRANSFER
toLocationIdtextĐích TRANSFER
reasonCodetextMột trong 13 InventoryTrackingReasons
lotNumber / serialNumber / expiryDatetext / timestamptzSnapshot bất biến của bucket được di chuyển
remainingQuantitydecimal(15,4)Tracker layer FIFO (giảm dần khi outbound tiêu thụ)
notetextTự do (xem InventoryTrackingNotes)
createdBy / modifiedBytextAudit user (cho phép ẩn danh)

Indexes: (inventoryStockId), (referenceId), (referenceType, referenceId), (fromLocationId), (toLocationId), (uomId), (merchantId).

Idempotency: lookup theo (referenceType, referenceId, inventoryStockId) trước khi ghi để tránh đếm kép khi Kafka redeliver.


3.5 InventoryIdentifier

Thuộc tínhGiá trị
BảngInventoryIdentifier
Đa hìnhtag InventoryItem hoặc InventoryStock
TrườngKiểuBắt buộcMô tả
principalTypetextINVENTORY_ITEM / INVENTORY_STOCK
principalIdtextFK target id
schemetextSKU / BARCODE / QRCODE / IMEI / SERIAL
valuetextChuỗi identifier

Constraint: unique (scheme, value) mỗi principal — ngăn barcode trùng.


3.6 InventoryTicket / InventoryTicketItem

Trường InventoryTicket:

TrườngKiểuBắt buộcMô tả
merchantIdtextOwner
identifiertextTự động, tiền tố ITI
typetextInventoryTicketTypes (mặc định UNKNOWN); xem §5.4 cho tập đầy đủ
statustextMặc định DRAFT
partnerType / partnerIdtextVENDOR / CUSTOMER (khi áp dụng)
sourceLocationId / destinationLocationIdtextCho TRANSFER
originReferenceType / originReferenceIdtextLineage tới doc gốc (vd ref sale return)
returnOfTicketIdtextFK self — khi ticket này là return của ticket khác
backorderOfTicketIdtextFK self — lineage backorder
spawnedPurchaseOrderIdtextFK tới PurchaseOrder — khi ticket sinh ra một PO
reasonCodetextMột trong InventoryTrackingReasons
effectiveDate / submittedAt / approvedAt / startedAt / completedAt / cancelledAttimestamptzTimestamp theo từng trạng thái
approvedBytextAudit user cho bước approval
notetextTự do

Hành vi: ticket là một workflow document — không có hiệu ứng stock đến khi COMPLETED (lúc đó các dòng InventoryTicketItem mới điều khiển stock adjust).


3.7 PurchaseOrder / PurchaseOrderItem

Trường PurchaseOrder:

TrườngKiểuBắt buộcMô tả
merchantIdtextOwner
purchaseOrderNumbertextUnique; mặc định = <YYYYMMDDHHmmss>-<snowflake>
nametextMặc định = PurchaseOrder-<snowflake>
slugtextUnique; cùng pattern mặc định
vendorIdtextFK tới Vendor
inventoryLocationIdtextĐích khi nhận hàng
statustextXem §5.1 — 6 giá trị, mặc định DRAFT
orderDatetimestamptzMặc định now()
expectedDeliveryDate / actualDeliveryDatetimestamptz
draftAt / processingAt / confirmedAt / receivedAt / completedAt / closedAt / cancelledAttimestamptzTimestamp theo từng trạng thái
currencytextMặc định VND
exchangeRatedecimal(12,6)Mặc định 1
subtotal / discount / tax / totaldecimal(15,4)Tính lại bởi updateSummaryFromItems

Indexes: (inventoryLocationId), (merchantId), (merchantId, status), (vendorId).

Trường PurchaseOrderItem:

TrườngKiểuBắt buộcMô tả
purchaseOrderIdtextFK
itemTypetextMATERIAL / PRODUCT_VARIANT (mặc định ProductVariant)
itemIdtextFK target id (đa hình, không có DB FK)
currencytextMặc định VND
uomIdtextSoft-ref tới UnitOfMeasure.id
multiplierdecimal(15,4)UoM-to-base (mặc định 1)
quantitydecimal(15,4)Số lượng đặt
receivedQuantitydecimal(15,4)Đã nhận tích lũy
unitPricedecimal(15,4)Mỗi UoM
discount / taxdecimal(15,4)Mỗi line
totaldecimal(15,4)Tính toán
lotNumber / expiryDate / manufactureDatetext / timestamptzMetadata lot/serial mỗi line
serialNumbersjsonbstring[] — cho inventory có serial
landedCostSharedecimal(15,4)Phần landed cost được phân bổ
effectiveCostdecimal(15,4)Cột generated (unitPrice + landedCostShare)

Idempotency khi add: cùng (purchaseOrderId, itemType, itemId, uomId) → cộng dồn quantity thay vì duplicate.


3.8 Vendor / VendorItem

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 — catalog M:N (KHÔNG có vendorId trên Material / ProductVariant):

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

Atomic preferred flip: VendorItemRepository.setPreferredAtomic hạ cấp các row khác và nâng cấp target trong một câu lệnh.


3.9 Material / MaterialIdentifier

Trường Material:

TrườngKiểuBắt buộcMô tả
merchantIdtextOwner
identifiertextTự động, tiền tố MAT
slugtextURL-safe
namei18n jsonbHiển thị
descriptioni18n jsonb
statustextMaterialStatuses (mặc định ACTIVATED)
typetextMaterialTypes (mặc định RAW); xem enum nguồn cho tập đầy đủ
uomjsonbIUomRole{ base, purchase, sale } (mặc định chuỗi rỗng); xem ADR-0005
costdecimal(15,4)Tham chiếu standard cost (không có mặc định)
weightdecimal(15,4)
categoryIdtextFK tới Category
metadatajsonbIMaterialMetadata — theo quy ước chứa inventory.allowOversell (mặc định false) + inventory.isInventoryTracked (mặc định true)

Trường MaterialIdentifier:

TrườngKiểuBắt buộcMô tả
materialIdtextFK
schemetextSYSTEM (tự động, tiền tố MAT) / SLUG / SKU / BARCODE / QRCODE
valuetextUnique theo (materialId, scheme)

3.10 MaterialRecipe / MaterialRecipeItem

Trường MaterialRecipe:

TrườngKiểuBắt buộcMô tả
merchantIdtextOwner
principalTypetextMATERIAL / PRODUCT_VARIANT
principalIdtextRecipe này tạo ra cái gì
statustextDRAFT / ACTIVATED / DEACTIVATED
typetextKIT (trừ lúc bán) / MANUFACTURED (cần ProductionOrder)
versionintTăng khi update aggregate

Trường MaterialRecipeItem:

TrườngKiểuBắt buộcMô tả
materialRecipeIdtextFK tới MaterialRecipe
principalTypetextLoại component đa hình (MATERIAL hoặc PRODUCT_VARIANT)
principalIdtextFK target id (component)
quantitydecimal(15,4)Lượng cần mỗi đơn vị principal
uomIdtextSoft ref
isOptionalbooleanMặc định false — khi true, thiếu component không block production

Indexes: partial unique (principalType, principalId, materialRecipeId) WHERE deletedAt IS NULL; non-unique (materialRecipeId), (uomId).


3.11 ProductionOrder

TrườngKiểuBắt buộcMô tả
merchantIdtextOwner
productionNumbertextUnique theo merchant
targetTypetextLoại đa hình của thứ được sản xuất (MATERIAL / PRODUCT_VARIANT); mặc định ProductVariant
targetIdtextFK target id
materialRecipeIdtextFK tới recipe được dùng
plannedQuantity / actualQuantity / scrapQuantitydecimal(15,4)Theo dõi sản xuất
uomtextCode UoM (text, không phải jsonb)
locationIdtextFK tới InventoryLocation
statustextProductionOrderStatuses (mặc định DRAFT)
scheduledStartAt / scheduledEndAt / startedAt / completedAt / cancelledAttimestamptzTimestamp vòng đời
outputLotNumber / outputExpiryDatetext / timestamptzMetadata bucket output

3.12 UnitOfMeasure

TrườngKiểuBắt buộcMô tả
merchantIdtextNULL = system-wide; nếu khác là override theo merchant
codetextvd kg, box, pair
namei18n jsonbHiển thị
categorytextCOUNT / WEIGHT / VOLUME / TIME
referenceCodetextCode base unit (self-ref)
ratiodecimal(15,4)Tỷ lệ tới base (1.0 cho base unit)

Phạm vi 3 cấp: system (merchantId NULL) → merchant override → product/material (qua uom jsonb trên Material).


4. InventoryTrackingReferenceTypes

Giá trịDùng bởi
PURCHASE_ORDERPO receive
SALE_ORDERSale payment success → trừ product + reserve material
KITCHEN_TICKETTiêu thụ kitchen ticket
KITCHEN_TICKET_ITEMTiêu thụ cấp item của kitchen ticket
INVENTORY_TICKETWorkflow ticket (transfer, adjust, count, scrap, return)
PRODUCTION_ORDERTiêu thụ + output sản xuất
ADJUSTMENTNhập tay của admin
UNKNOWNFallback

5. Enum Trạng thái

5.1 PurchaseOrderStatuses

Giá trịCodeGiai đoạn
DRAFT001_DRAFTItems có thể sửa
PROCESSING203_PROCESSINGItems đóng băng, chờ hàng
RECEIVED205_RECEIVEDMột phần/toàn bộ items đã nhận, hoàn thành một phần
COMPLETED303_COMPLETEDTất cả items đã nhận đầy đủ
CLOSED404_CLOSEDTerminal — không còn thay đổi
CANCELLED505_CANCELLEDTerminal — đã hủy

5.2 MaterialRecipeStatuses

Giá trịCode
DRAFT001_DRAFT
ACTIVATED201_ACTIVATED
DEACTIVATED202_DEACTIVATED

5.3 Vendor / VendorItemStatuses

Giá trịCode
ACTIVATED201_ACTIVATED
DEACTIVATED202_DEACTIVATED
ARCHIVED300_ARCHIVED

5.4 InventoryTicketStatuses

Giá trịCode
DRAFT001_DRAFT
SUBMITTED200_SUBMITTED
APPROVED250_APPROVED
IN_PROGRESS300_IN_PROGRESS
COMPLETED303_COMPLETED
CANCELLED505_CANCELLED

5.5 ReceivePurchaseOrderItemModes

Giá trịHành vi
OVERRIDE (mặc định)newReceived = receivedQuantity
ACCUMULATIVEnewReceived = currentReceived + receivedQuantity

6. FixedInventoryTrackingTypes (19)

HướngTypes
Inbound (6)STOCK_IN, PURCHASE, TRANSFER_IN, RETURN_FROM_CUSTOMER, ADJUSTMENT_IN, PRODUCTION_COMPLETE
Outbound (10)STOCK_OUT, SALE, TRANSFER_OUT, RETURN_TO_VENDOR, ADJUSTMENT_OUT, EXPIRED, LOST, DAMAGED, USED_INTERNAL, USED_AS_MATERIAL
Neutral (2)INVENTORY_COUNT, ADJUSTMENT_NEUTRAL
Custom (1)CUSTOM

7. Invariant cross-entity

InvariantÉp buộc bởi
quantityAvailable = quantityOnHand − quantityReserved (post-condition)Service layer duy trì; adjustStock mutate cả ba
Chính xác một InventoryLocation mặc định mỗi merchantInventoryLocationRepository.setDefaultAtomic
Chính xác một VendorItem ưu tiên mỗi (merchantId, itemType, itemId)VendorItemRepository.setPreferredAtomic
MaterialRecipeItem.principalId tham chiếu Material hoặc ProductVariant (đa hình qua principalType)Schema; zod cấp service validate principal tồn tại
InventoryTracking là append-only (không UPDATE trừ qua tooling admin)Quy ước repository; service chỉ ghi khi stock thay đổi
Đa hình InventoryItem — chính xác một (merchantId, itemType, itemId)upsert ensureInventoryItem
Material.metadata.inventory.allowOversell điều khiển cờ forceNonNegative truyền vào adjustStockInventoryService.loadPrincipalRefs
Liên kết Vendor tới items đi qua VendorItem only — không có cột vendorId trên principalSchema + ADR

8. Hành vi Soft-delete

EntitySoft-deleteGhi chú
InventoryLocation, InventoryItem, InventoryStock, InventoryIdentifier, InventoryTicket, InventoryTicketItem, Vendor, VendorItem, Material, MaterialIdentifier, MaterialRecipe, MaterialRecipeItem, ProductionOrder, PurchaseOrder, PurchaseOrderItem, UnitOfMeasureMarker deletedAt; mặc định read là IS NULL
InventoryTracking✓ chỉ phía schemaService xem là audit bất biến; không bao giờ ghi deletedAt

9. Trang liên quan

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