Skip to content

Inventory Location

1. Tổng quan

Thuộc tínhGiá trị
IDFEAT-INV-LOC
StatusStable
Ownerinventory-team
Phụ thuộcMerchant (nguồn CDC cho default seed)

InventoryLocation đại diện cho một warehouse, store, hoặc sub-location của merchant. Chính xác một location mỗi merchant mang isDefault=true. Locations hỗ trợ phân cấp self-reference (parentId) và vòng đời 4 trạng thái. Default location được tự tạo khi một row Merchant được commit qua Debezium CDC.

2. Mô hình Entity

Trường

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 (mặc định) / ACTIVATED / DEACTIVATED / ARCHIVED
namei18n jsonbHiển thị
typetextInventoryLocationTypes (mặc định PHYSICAL)
locationjsonb{ main, sub, long, lat, postCode }

3. Vòng đời

TừSự kiệnĐếnGuards
NEW / DEACTIVATEDactivateACTIVATED
ACTIVATEDdeactivateDEACTIVATED
ACTIVATED / DEACTIVATEDarchiveARCHIVEDkhông phải default của merchant; không có row InventoryStock đang sống

4. Vận hành

InventoryLocationService (inventory-location.service.ts)

Phương thứcSignatureMục đíchDòng
getOverview{ context, filter }Thẻ KPI cho màn hình Location (phạm vi theo merchant)122-129
getList{ context, filter }Danh sách phân trang + tổng hợp tồn kho & cảnh báo theo từng location131-145
getCount{ context, where }Tổng số đi kèm cho getList147-158
createAggregate{ context, data }Tạo location + reconcile link kênh bán (một TX)194-221
updateAggregate{ context, id, data }Patch location + thay thế toàn bộ link kênh bán (một TX)224-272
setDefault{ context, id }Promote thành default của merchant; atomic hạ cấp các default khác478-494
activate{ context, id }NEW/DEACTIVATED → ACTIVATED497-505
deactivate{ context, id }ACTIVATED → DEACTIVATED507-519
archive{ context, id }Chuyển sang ARCHIVED với guard522-548

Phương thức tùy biến InventoryLocationRepository

Phương thứcMục đích
getLocationOverviewThẻ KPI tổng hợp: số lượng location + tổng giá trị tồn + số location cần chú ý. Tái sử dụng các fragment dùng chung stock-posture.sql
findInventoryLocationsDanh sách phân trang; tổng hợp tồn theo location + số kênh bán + cờ cảnh báo (out/low/oversell qua BOOL_OR)
countInventoryLocationsTổng số đi kèm cho findInventoryLocations
ensureDefaultLocationIdempotent — tìm hoặc tạo default cho merchant; gọi bởi handler Merchant CDC
setDefaultAtomicMột câu lệnh: hạ cấp các row isDefault khác + nâng cấp target
findParentChainĐi qua chuỗi parentId lên đến maxDepth — dùng để phát hiện cycle
countStockReferencesĐếm InventoryStock đang sống tham chiếu location này — dùng bởi guard beforeDelete, KHÔNG bởi archive

5. REST Endpoints

VerbPathAuthPermissionHandler
6× CRUD/inventory-locationsJWT/BASICInventoryLocation.<crud>merchant-scoped
POST/inventory-locations/aggregateJWT/BASICInventoryLocation.createAggregatecreateAggregate
PATCH/inventory-locations/:id/aggregateJWT/BASICInventoryLocation.updateAggregateupdateAggregate
GET/inventory-locations/overviewJWT/BASICInventoryLocation.findgetOverview
GET/inventory-locations/listJWT/BASICInventoryLocation.findgetList
GET/inventory-locations/list/countJWT/BASICInventoryLocation.countgetCount
POST/inventory-locations/:id/defaultJWT/BASICInventoryLocation.setDefaultsetDefault
POST/inventory-locations/:id/activateJWT/BASICInventoryLocation.activateactivate
POST/inventory-locations/:id/deactivateJWT/BASICInventoryLocation.deactivatedeactivate
POST/inventory-locations/:id/archiveJWT/BASICInventoryLocation.archivearchive

5.1 Số liệu List & Overview

Cả ba route đều bắt buộc chỉ định merchant qua filter where (merchant picker trên màn hình) — /overview/list đọc filter[where][merchantId], còn /list/count đọc where[merchantId]. Service bắt buộc phải có (400 nếu thiếu), kiểm tra nằm trong scope của người gọi (403 nếu không), rồi query đúng merchant đó. Phần stock posture theo từng location tái sử dụng các fragment dùng chung stock-posture.sql, nên phép tính cảnh báo dùng chung một nguồn với danh sách stock và item.

Row của /list — các trường location cộng summaryneedAttention:

KhốiTrườngÝ nghĩa
saleChannelstotalSố sale channel được route tới location này (link SaleChannelInventoryLocation đang sống)
summaryitemCountSố InventoryItem riêng biệt có bucket tồn tại location này
summaryonHand{ quantity, value } cộng dồn qua các bucket của location
summaryreserved{ quantity, value } cộng dồn qua các bucket của location
needAttentionoutCó bucket với available ≤ 0
needAttentionoversellCó bucket với available < 0 (tập con của out)
needAttentionlowCó bucket với 0 < available ≤ lowStockThreshold

Sắp xếp nhận name / identifier / type / status / createdAt (id làm tiebreaker); mặc định name ASC.

Thẻ của /overview:

KhốiTrườngÝ nghĩa
countstotal / physical / simulation / activeSố location theo type / trạng thái ACTIVATED
countswithStock / emptyLocation có ≥1 bucket tồn vs. không có (empty = total − withStock)
stocktotalOnHand / totalValueSố lượng on-hand + định giá trên toàn bộ bucket của merchant
needAttentionSố location có ≥1 bucket cần chú ý (outlow)

5.2 Aggregate create / update

POST /aggregatePATCH /{id}/aggregate tạo/patch location reconcile các kênh bán bán từ location đó, trong một transaction. Phần ghi location đi qua các lifecycle hook bình thường (trạng thái NEW, isDefault atomic, kiểm tra cycle cha); sau đó các link kênh bán được reconcile với SaleChannelInventoryLocation.

saleChannels[] (mỗi phần tử { saleChannelId, priority? }) là tập khai báo đầy đủ (full set):

VerbsaleChannelsTác dụng
POSTcó / bỏ quaTập link cần tạo (bỏ qua = không tạo).
PATCHbỏ quaGiữ nguyên link hiện có.
PATCHTrở thành tập chính xác — kênh có trong list được upsert (patch priority), kênh không có bị soft-delete. [] xoá hết.

Mỗi saleChannelId được kiểm tra phải thuộc merchant của location (400 không tìm thấy, 403 khác merchant). merchantId / identifier bất biến khi update. Response: { location, saleChannels[] }.

isDefault: được áp dụng ở cả hai verb, nhưng chỉ promote (đặt làm default). Với PATCH, isDefault: true chạy đúng logic của POST /{id}/default (dùng chung _promoteToDefault): row phải đang ACTIVATED (nếu không → 400), rồi flip atomic — hạ default cũ của merchant và promote row này trong một câu lệnh. isDefault: false / bỏ qua là no-op (bỏ default bằng cách promote location khác). status không được aggregate nhận — chuyển trạng thái đi qua /activate · /deactivate · /archive.

Commerce cũng có CRUD ghi các link này (phía sale-channel). Cả hai cùng ghi bảng SaleChannelInventoryLocation — một nguồn sự thật, hai entry point.

6. Sự kiện

Inbound:

TopicTriggerHandlerTác dụng
nx.seller.public.merchant (Debezium CDC)INSERT/UPDATE row MerchantInventoryWorkerService.handleMerchantCDCensureDefaultLocation(merchantId) — idempotent

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

7. Luồng Seed Default Location

Idempotent — re-delivery là no-op.

8. Invariants

InvariantÉp buộc bởi
Chính xác một isDefault=true mỗi merchantPartial unique index + setDefaultAtomic
Chuỗi parentId không có cyclePhát hiện cycle ở cấp service khi update
Không thể archive default locationGuard service trong archive
Không thể xóa location còn stock đang sốngGuard beforeDelete qua countStockReferences (lưu ý: archive hôm nay không có guard này)
archive không cascade tới childrenChildren phải được archive/reparent trước

9. Trang liên quan

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