Architecture
1. System Context (C4 L1)
2. Container View (C4 L2)
3. Component View (C4 L3) — Internal Layering
| Layer | Responsibility |
|---|---|
| Routes | 13 base paths in RestPaths |
| Controllers | Auth gate, permission, DTO mapping |
| Services | Business logic, transactions, event emission |
| Repositories | Drizzle queries, soft-delete, FOR UPDATE locks |
| Components | Kafka producer, WebSocket emitter, Redis cache |
4. State Machines Index
| Entity | States | Diagram |
|---|---|---|
SaleOrder | DRAFT, PROCESSING, PARTIAL, COMPLETED, CANCELLED | → |
SaleCheck | PROCESSING, COMPLETED, CANCELLED | → |
KitchenTicket | PENDING, PROCESSING, READY, COMPLETED, VOIDED | → |
KitchenTicketItem | PENDING, COOKING, READY, SERVED, VOIDED | → |
AllocationUsage | ACTIVE, SUCCESS, CANCELLED, EXPIRED | → |
Reservation | PENDING, CONFIRMED, CANCELLED, CHECKED_IN | → |
PosSession | OPEN, CLOSED | → |
SaleOrder
| From | Event | To | Trigger |
|---|---|---|---|
DRAFT | checkout | PROCESSING | CheckoutService.checkout |
PROCESSING | revert | DRAFT | CheckoutService.revertCheckout |
PROCESSING | payment success (partial) | PARTIAL | MQ-Pay webhook → _handleOrderPaymentSuccess |
PROCESSING / PARTIAL | payment success (full) | COMPLETED | same |
* | cancel / fail / expire | CANCELLED | webhook handlers + manual cancel |
SaleCheck
KitchenTicket
The ticket-level state machine is different from the item-level one — see Domain Model §4.3 for status mapping.
KitchenTicketItem
Each item status change emits Kafka
KITCHEN_TICKET_ITEM_STATUS_CHANGEDconsumed by@nx/inventory.
AllocationUsage
Reservation
PosSession
5. Runtime Scenarios
5.1 Cart → Checkout → Payment → Stock Deduct
5.2 Add Item — Pessimistic Lock
Prevents duplicate-merge race when two devices add the same item simultaneously.
5.3 Send to Kitchen → Cook → Serve
5.4 Bill Split (SaleCheck)
6. Crosscutting Concerns
| Concern | How this service handles it |
|---|---|
| AuthN | JWT (ES256, JWKS from identity); BASIC for service-to-service. Webhook endpoint has no auth (trusted internal). |
| AuthZ | Casbin via PolicyDefinitionService (cached in Redis) |
| i18n | jsonb { en, vi }; Accept-Language header |
| Logging | IGNIS structured (key: %s); request-id propagated |
| Idempotency | Per-saleOrderId for Kafka emit; per-saleOrderId on customer-point award; webhook handlers check existing transitions |
| Race protection | SELECT ... FOR UPDATE on add-item and bulk update flows |
| Soft-delete | SoftDeletableRepository (deletedAt) |
| IDs | Snowflake via IdGenerator, worker 3 |
| Dual mode | ApplicationRoles.API / WORKER in constants — for future API/worker split (currently single-process) |