Kiến trúc
1. System Context (C4 L1)
2. Container View (C4 L2)
3. Component View (C4 L3) — Phân lớp nội bộ
| Lớp | Trách nhiệm |
|---|---|
| Routes | /inventory-stocks, /purchase-orders, … (15 trong RestPaths) |
| Controllers | Cổng auth (JWT / BASIC), kiểm tra permission, mapping DTO |
| Services | Logic nghiệp vụ, transaction, phát sự kiện, guard idempotency |
| Workers | InventoryWorkerService, MaterialWorkerService — Kafka handler stateless |
| Repositories | Drizzle queries, soft-delete, atomic adjuster (adjustStock) |
| Components | Kafka producer/consumer, WebSocket, Redis cache |
4. State Machines Index
| Entity | Trạng thái | Sơ đồ |
|---|---|---|
PurchaseOrder | DRAFT, PROCESSING, RECEIVED, COMPLETED, CLOSED, CANCELLED | → |
MaterialRecipe | DRAFT, ACTIVATED, DEACTIVATED | → |
Vendor / VendorItem | ACTIVATED, DEACTIVATED, ARCHIVED | → |
InventoryLocation | NEW, ACTIVATED, DEACTIVATED, ARCHIVED | → |
InventoryTicket | DRAFT, SUBMITTED, APPROVED, IN_PROGRESS, COMPLETED, CANCELLED | → |
PurchaseOrder
| Từ | Sự kiện | Đến | Guards |
|---|---|---|---|
DRAFT | confirm | PROCESSING | items length ≥ 1 |
PROCESSING | receive(items) | RECEIVED | nhận một phần |
PROCESSING | receive(items) | COMPLETED | tất cả items có receivedQuantity ≥ quantity |
RECEIVED | receive(items) | RECEIVED / COMPLETED | nhận tiếp phần còn lại |
RECEIVED / COMPLETED | complete | COMPLETED | idempotent |
RECEIVED / COMPLETED | close | CLOSED | terminal |
PROCESSING | revert | DRAFT | chưa nhận item nào |
| any non-terminal | cancel | CANCELLED | terminal |
MaterialRecipe
Vendor / VendorItem
InventoryLocation
InventoryTicket
5. Kịch bản Runtime
5.1 Sale Payment → Trừ Stock
5.2 Kitchen Ticket Item READY → Tiêu thụ Material
5.3 PO Confirm → Receive → Complete
5.4 Merchant CDC → Seed Default Location
5.5 ProductVariant CDC seed — COMBO tự bị skip
InventoryWorkerService.handleProductVariantCDC chỉ seed InventoryItem cho các variant type stockable. ProductVariantTypes.COMBO không nằm trong STOCKABLE_SET, nên guard có sẵn
ts
if (!ProductVariantTypes.isStockable(productVariant.type)) return;đã loại combo variant — không cần category lookup, không cần branch thêm. Combo variant là ảo; component giữ stock.
Combo deduction chảy qua path per-item hiện có tại payment-success vì cart-add layer (@nx/sale) đã insert các leaf component thành SaleOrderItem con với leadItemId. Worker xem chúng như item PRODUCT_VARIANT thường và trừ kho bình thường.
ADR: ./decisions/0006-combo-explosion-at-cart-add.
6. Crosscutting Concerns
| Concern | Cách service xử lý |
|---|---|
| AuthN | JWT (ES256, JWKS lấy từ identity); fallback sang BASIC cho service-to-service |
| AuthZ | Casbin qua PolicyDefinitionService (cache ở Redis) — code <Resource>.<action> từ controllers/permissions.ts |
| i18n | Cột jsonb dạng { en, vi }; locale resolve từ Accept-Language |
| Logging | Structured logger của IGNIS; định dạng key: %s; topic/partition/offset cho Kafka |
| Idempotency | Lookup theo (referenceType, referenceId, inventoryStockId) trong InventoryTracking trước khi ghi |
| Atomicity | InventoryStockRepository.adjustStock — một SQL UPDATE duy nhất với forceNonNegative tùy chọn |
| Soft-delete | SoftDeletableRepository (deletedAt); audit trail (InventoryTracking) KHÔNG soft-delete |
| IDs | Snowflake qua IdGenerator, worker 5 |
| Transactions | Phương thức service nhận tham số transaction tùy chọn; aggregate ops scope qua helper _withTransaction |
| Merchant scoping | MerchantScopedService chèn filter merchantId cho mọi read/write với role không phải system |