Skip to content

Purchase Order

1. Tổng quan

Thuộc tínhGiá trị
IDFEAT-INV-PO
StatusStable
Ownerinventory-team
Phụ thuộcVendor, VendorItem, Material / ProductVariant, InventoryLocation, UnitOfMeasure

Purchase Order đại diện cho việc nhận hàng từ vendor. Aggregate API (POST /aggregate, PATCH /:id/aggregate) tạo/cập nhật một PO + các item của nó trong một transaction. Nhận hàng mutate InventoryStock và ghi vào InventoryTracking. Các chân thanh toán tùy chọn trong receive call kích hoạt một Kafka emit được tiêu thụ bởi @nx/finance.

2. Mô hình Entity

Trường PurchaseOrder

TrườngKiểuBắt buộcGhi chú
idtextSnowflake
purchaseOrderNumbertextUnique; mặc định <YYYYMMDDHHmmss>-<snowflake>
nametextMặc định PurchaseOrder-<snowflake>
slugtextUnique; cùng pattern mặc định
merchantIdtextOwner
vendorIdtextFK
inventoryLocationIdtextĐích nhận hàng
statustextXem §3; 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)Duy trì bởi updateSummaryFromItems
metadatajsonbIPurchaseOrderMetadata

Trường PurchaseOrderItem

TrườngKiểuBắt buộcGhi chú
purchaseOrderIdtextFK
itemTypetextMATERIAL / PRODUCT_VARIANT
itemIdtextFK target id (đa hình)
uomIdtextSoft ref
multiplierdecimal(15,4)Quy đổi UoM-to-base
quantitydecimal(15,4)Đã đặt
receivedQuantitydecimal(15,4)Tích lũy
unitPricedecimal(15,4)Mỗi UoM
discount / taxdecimal(15,4)Mỗi line

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

3. Vòng đời

TừSự kiệnĐếnGuard
DRAFTconfirmPurchaseOrderPROCESSINGitems.length ≥ 1
PROCESSINGrevertPurchaseOrderDRAFTchưa nhận item nào
PROCESSING / RECEIVEDreceivePurchaseOrder(items)RECEIVEDnhận một phần
PROCESSING / RECEIVEDreceivePurchaseOrder(items)COMPLETEDtất cả items có receivedQuantity ≥ quantity
RECEIVED / COMPLETEDcompletePurchaseOrderCOMPLETEDidempotent
RECEIVED / COMPLETEDclosePurchaseOrderCLOSEDterminal
any non-terminalcancelPurchaseOrderCANCELLEDterminal
Status code (giá trị DB)Hiển thị
001_DRAFTDRAFT
203_PROCESSINGPROCESSING
205_RECEIVEDRECEIVED
303_COMPLETEDCOMPLETED
404_CLOSEDCLOSED
505_CANCELLEDCANCELLED

4. Chế độ Receive

Định nghĩa trong inventory/src/common/constants.ts:20 (ReceivePurchaseOrderItemModes).

ModeHành vi
OVERRIDE (mặc định)newReceivedQuantity = receivedQuantity (caller tự tính giá trị target tuyệt đối)
ACCUMULATIVEnewReceivedQuantity = currentReceivedQuantity + receivedQuantity

InventoryService.receiveInventoryFromPurchaseOrderItem() luôn vận hành trên một delta — caller resolve delta từ mode đã chọn trước khi truyền xuống.

Quy tắc auto-complete: nếu mọi item thỏa receivedQuantity ≥ quantity sau một receive call, PurchaseOrder.status chuyển sang COMPLETED trong cùng operation.

5. Vận hành

Phương thứcSignatureMục đíchDòng
createAggregate{ context, data: TCreatePurchaseOrderAggregateRequest }PO + items trong một TX; merge item idempotent212
updateByIdAggregate{ context, id, data: TUpdatePurchaseOrderAggregateRequest }Patch metadata + items qua sự hiện diện id277
addItemToPurchaseOrder{ purchaseOrderId, item: TAddPurchaseOrderItemRequest }Append vào DRAFT; merge nếu cùng (itemType, itemId, uomId)338
clearPurchaseOrderItems{ purchaseOrderId }Soft-delete tất cả items; chỉ DRAFT379
confirmPurchaseOrder{ purchaseOrderId }DRAFT → PROCESSING412
receivePurchaseOrder{ purchaseOrderId, inventoryLocationId?, items, transaction? }PROCESSING/RECEIVED → RECEIVED/COMPLETED; mutate stock; ghi tracking; emit Kafka436
revertPurchaseOrder{ purchaseOrderId }PROCESSING → DRAFT522
cancelPurchaseOrder{ purchaseOrderId, reason? }bất kỳ non-terminal → CANCELLED534
completePurchaseOrder{ purchaseOrderId }RECEIVED → COMPLETED (idempotent)564
closePurchaseOrder{ purchaseOrderId }RECEIVED/COMPLETED → CLOSED591

Nguồn: inventory/src/services/purchase-order.service.ts (1227 dòng).

Ngữ nghĩa items của updateByIdAggregate

Hình dạngDiễn giải
Chỉ { id }DELETE item này
{ id, ...fields }UPDATE item hiện có
{ mode, itemId, quantity, ... } (không có id)CREATE mới

Chi tiết luồng Receive

effectiveQuantity = adjustedQuantity * multiplier — quy đổi base-unit từ UoM của line.

6. REST Endpoints

VerbPathAuthPermissionHandler
POST/purchase-orders/aggregateJWT/BASICPurchaseOrder.createAggregatecreateAggregate
PATCH/purchase-orders/:id/aggregateJWT/BASICPurchaseOrder.updateAggregateupdateByIdAggregate
POST/purchase-orders/:id/itemsJWT/BASICPurchaseOrder.addItemaddItemToPurchaseOrder
DELETE/purchase-orders/:id/itemsJWT/BASICPurchaseOrder.clearItemsclearPurchaseOrderItems
POST/purchase-orders/:id/confirmJWT/BASICPurchaseOrder.confirmconfirmPurchaseOrder
POST/purchase-orders/:id/revertJWT/BASICPurchaseOrder.revertrevertPurchaseOrder
POST/purchase-orders/:id/receiveJWT/BASICPurchaseOrder.receivereceivePurchaseOrder
POST/purchase-orders/:id/cancelJWT/BASICPurchaseOrder.cancelcancelPurchaseOrder
POST/purchase-orders/:id/completeJWT/BASICPurchaseOrder.completecompletePurchaseOrder
POST/purchase-orders/:id/closeJWT/BASICPurchaseOrder.closeclosePurchaseOrder
6× CRUD/purchase-ordersJWT/BASICPurchaseOrder.<crud>CRUD

7. Sự kiện

Inbound: không trực tiếp. PO chỉ được mutate qua REST.

Outbound:

TopicKhi nàoPayload
purchase-order.received (KafkaTopics.PURCHASE_ORDER_RECEIVED)Sau khi receivePurchaseOrder commit, chỉ khi payments[] không rỗng{ purchaseOrderId, merchantId, payments[], items[] (với inventoryItemId, quantity, unitCost, uomId, multiplier, effectiveAt) }

Consumer: @nx/finance (ghi nhận chân EXPENSE / COGS).

8. Side effects khi receive

Side effectỞ đâu
InventoryStock.adjustStock(+effectiveQuantity)Mỗi item, atomic
Insert row InventoryTracking (type=PURCHASE, referenceType=PURCHASE_ORDER, referenceId=<poId>)Mỗi item, sau khi adjust stock
Cập nhật snapshot VendorItem.lastInvoicedMỗi item qua VendorItemService.recordPurchase
Tính lại PurchaseOrder.subtotal/tax/totalSau khi xử lý hết items
Chuyển trạng thái sang RECEIVED hoặc COMPLETEDDựa trên receivedQuantity tích lũy
Emit Kafka PURCHASE_ORDER_RECEIVEDPost-commit, chỉ khi payments[] không rỗng

9. Trang liên quan

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