Mô hình miền
Bảng sale nằm trong schema PostgreSQL
sale; bảng allocation trong schemaallocation. Tất cả schema định nghĩa trong@nx/core/src/models/schemas/{sale,allocation}/. Cột số dùngdecimal(15, 4).
1. ERD đầy đủ
2. Cột chung
| Cột | Kiểu | Ghi chú |
|---|---|---|
id | text | PK, Snowflake |
createdAt / modifiedAt | timestamptz | — |
createdBy / modifiedBy | text | Audit user |
deletedAt | timestamptz | Soft-delete |
metadata | jsonb | Túi mở rộng |
3. Thực thể
3.1 SaleOrder
| Thuộc tính | Giá trị |
|---|---|
| Bảng | SaleOrder |
| Nguồn | core/src/models/schemas/sale/sale-order/schema.ts |
| Soft-delete | có |
| Trường | Kiểu | Bắt buộc | Mô tả |
|---|---|---|---|
orderNumber | text | ✓ | Unique partial per merchant |
name / slug | text | Slug unique partial | |
validity | jsonb | { from, to } cho order giới hạn thời gian | |
status | text | ✓ | Xem §4.1; mặc định DRAFT |
draftAt / processingAt / partialAt / completedAt / cancelledAt | timestamptz | Timestamp theo trạng thái | |
cancellationReason | text | — | |
customerId | text | FK | |
merchantId | text | ✓ | Owner |
saleChannelId | text | ✓ | FK |
openedInSessionId / closedInSessionId | text | FK đến PosSession | |
currency | text | ✓ | Mặc định VND |
exchangeRate | decimal(12,6) | Mặc định 1 | |
subtotal / tax / discount / total | decimal(15,4) | ✓ | Mặc định 0; duy trì bởi updateSummaryFromItems |
originOrderId | text | Theo dõi parent của order-split | |
checkSplitAt / orderSplitAt / mergedAt | timestamptz | Timestamp thao tác | |
counter | jsonb | { paid, paidItemIds[], total } — tiến trình thanh toán |
3.2 SaleOrderItem
| Thuộc tính | Giá trị |
|---|---|
| Bảng | SaleOrderItem |
| Polymorphic | (itemType, itemId) qua generatePrincipalColumnDefs({ discriminator: 'item', defaultPolymorphic: 'ProductVariant' }) |
| Trường | Kiểu | Bắt buộc | Mô tả |
|---|---|---|---|
saleOrderId | text | ✓ | FK |
itemType | text | ✓ | PRODUCT_VARIANT (mặc định) / khác |
itemId | text | ✓ | FK target |
mode | text | ✓ | PRODUCT (mặc định — auto-merge duplicate) / CUSTOM (luôn dòng mới) |
leadItemId | text | Group lead cho combo items | |
currency | text | ✓ | Mặc định VND |
basePrice / unitPrice | decimal(15,4) | ✓ | Trước/sau discount per đơn vị |
discount / tax | decimal(15,4) | ✓ | Mặc định 0 |
quantity | decimal(15,4) | ✓ | Mặc định 1 |
total | decimal(15,4) | ✓ | Tính toán |
fareId / fareProvider | text | Tham chiếu nguồn pricing | |
priceMetadata | jsonb | Snapshot pricing (chi tiết v2) | |
transferHistory | jsonb | Array<TTransferHistoryEntry> — theo dõi merge/split | |
recipeId | text | Liên kết MaterialRecipe.id đang active (snapshot) |
3.3 SaleCheck / SaleCheckItem
SaleCheck:
| Trường | Kiểu | Bắt buộc | Mô tả |
|---|---|---|---|
saleOrderId | text | ✓ | Order parent |
status | text | ✓ | PROCESSING (mặc định) / COMPLETED / CANCELLED |
subtotal / tax / discount / total | decimal(15,4) | ✓ | Tính lại bởi recalculateTotals |
customerId | text | Khách per-check (khác khách của order) |
SaleCheckItem: saleCheckId, saleOrderItemId, quantity, subtotal/tax/discount/total.
3.4 KitchenStation / KitchenTicket / KitchenTicketItem
KitchenStation: merchantId, name (i18n), status (mặc định ACTIVATED).
KitchenTicket:
| Trường | Kiểu | Bắt buộc | Mô tả |
|---|---|---|---|
ticketNumber | text | ✓ | Unique partial; tuần tự per station |
saleOrderId | text | ✓ | FK |
merchantId | text | ✓ | Owner |
kitchenStationId | text | Mục tiêu định tuyến (tùy chọn) | |
status | text | ✓ | Xem §4.3; mặc định PENDING |
priority | int | ✓ | Mặc định 0 (cờ rush làm tăng priority) |
sequence | int | ✓ | Mặc định 1 (gợi ý thứ tự) |
pendingAt / processingAt / readyAt / completedAt / voidedAt | timestamptz | Timestamp trạng thái |
KitchenTicketItem:
| Trường | Kiểu | Bắt buộc | Mô tả |
|---|---|---|---|
kitchenTicketId | text | ✓ | FK |
saleOrderItemId | text | ✓ | FK |
quantity | decimal(15,4) | ✓ | Mặc định 1 |
status | text | ✓ | Xem §4.4; mặc định PENDING |
startedAt / readyAt / servedAt / voidedAt | timestamptz | Timestamp trạng thái |
Mỗi lần đổi trạng thái phát Kafka
KITCHEN_TICKET_ITEM_STATUS_CHANGEDvà kích hoạt đánh giá auto-progression của ticket.
3.5 AllocationUsage / AllocationUnit / AllocationZone / AllocationLayout
Schema nằm trong schema
allocation(tách khỏisale).
AllocationUsage — sử dụng đa hình của một allocation unit:
| Trường | Kiểu | Bắt buộc | Mô tả |
|---|---|---|---|
usageType | text | ✓ | SALE_ORDER / RESERVATION (qua discriminator: 'usage') |
usageId | text | ✓ | Id FK target |
unitId | text | ✓ | FK đến AllocationUnit |
merchantId | text | ✓ | Owner |
assigneeId | text | Người/staff được gán | |
status | text | ✓ | ACTIVE (mặc định) / SUCCESS / CANCELLED / EXPIRED |
type | text | ✓ | GENERAL (mặc định) / DINE_IN / TAKEAWAY / DELIVERY |
reservedFrom / reservedTo / reservedAt / startedAt / completedAt | timestamptz | Timestamp vòng đời |
AllocationUnit — đơn vị vật lý (bàn, ghế, tủ):
name (i18n),zoneId(notNull),placement(jsonb vị trí),style(jsonb),capacity(int),status.
AllocationZone — section / floor / area:
name (i18n),layoutId(notNull),style(jsonb),parentId(cấp bậc tự tham chiếu),status.
AllocationLayout — container floor plan cấp cao nhất.
3.6 Reservation
| Trường | Kiểu | Bắt buộc | Mô tả |
|---|---|---|---|
merchantId | text | ✓ | Owner |
guestName | text | ✓ | — |
guestPhone | text | ✓ | — |
guestEmail | text | — | |
partySize | int | ✓ | — |
reservedFrom | timestamptz | ✓ | — |
reservedTo | timestamptz | — | |
notes | text | — | |
source | text | ✓ | Mặc định PHONE; WEB / WALK_IN / APP |
occasion | text | Sinh nhật / kỷ niệm / v.v. | |
status | text | ✓ | PENDING (mặc định) / CONFIRMED / CHECKED_IN / CANCELLED |
confirmedAt / checkedInAt / cancelledAt | timestamptz | — | |
cancellationReason | text | — | |
saleOrderId | text | FK sau check-in |
3.7 PosSession / PosSessionReport
PosSession:
| Trường | Kiểu | Bắt buộc | Mô tả |
|---|---|---|---|
merchantId | text | ✓ | Owner |
saleChannelId | text | ✓ | FK |
deviceId | text | ✓ | FK |
openedById | text | ✓ | User |
closedById | text | — | |
status | text | ✓ | OPEN (mặc định) / CLOSED |
openedAt / closedAt | timestamptz | — | |
openingFloat | decimal(15,4) | ✓ | Mặc định 0 |
expectedCash / actualCash / cashDiscrepancy | decimal(15,4) | Đối chiếu | |
expectedNonCash / actualNonCash | jsonb | TPosSessionNonCashBreakdown | |
closeRecountCount | int | ✓ | Mặc định 0; theo dõi số lần đếm lại |
notes | text | — |
PosSessionReport: snapshot metric của session khi đóng (sales, refunds, cash flow).
3.8 Customer
| Trường | Kiểu | Bắt buộc | Mô tả |
|---|---|---|---|
name | text | ✓ | — |
phone | text | — | |
email | text | — | |
userId | text | Tài khoản user được liên kết (tùy chọn) | |
merchantId | text | ✓ | Owner |
pointBalance | decimal(15,4) | ✓ | Mặc định 0 |
3.9 PointTransaction
| Trường | Kiểu | Bắt buộc | Mô tả |
|---|---|---|---|
customerId | text | ✓ | FK |
merchantId | text | ✓ | Owner |
saleOrderId | text | ✓ | Order nguồn |
type | text | ✓ | AWARD / REDEEM / ADJUST (theo PointTransactionTypes) |
points | decimal(15,4) | ✓ | Delta có dấu |
conversionRate | decimal(15,4) | ✓ | Snapshot points-per-currency tại lúc award |
Idempotency:
PointTransactionRepository.existsBySaleOrderIdchặn award duplicate.
4. Status Enums
4.1 SaleOrderStatuses
| Giá trị | Giai đoạn |
|---|---|
DRAFT | Cart / item có thể đổi |
PROCESSING | Checkout xong, chờ thanh toán |
PARTIAL | Đã nhận một phần thanh toán |
COMPLETED | Đã thanh toán đủ |
CANCELLED | Terminal |
4.2 SaleCheckStatuses
| Giá trị | Giai đoạn |
|---|---|
PROCESSING | Mặc định — chấp nhận thanh toán |
PARTIAL | Đã nhận một phần thanh toán |
COMPLETED | Đã thanh toán đủ |
CANCELLED | Terminal |
4.3 KitchenTicketStatuses
Nguồn:
core/src/models/schemas/sale/kitchen-ticket/constants.ts. Ticket ở lớp khác với các item — chúng có bộ status khác nhau.
| Giá trị | Code | Giai đoạn |
|---|---|---|
PENDING | 103_PENDING | Vừa gửi xuống bếp, chưa item nào COOKING |
PROCESSING | 203_PROCESSING | Ít nhất một item đang COOKING (auto-progress từ PENDING) |
READY | 302_SUCCESS | Tất cả item READY-trở-đi (auto) |
COMPLETED | 303_COMPLETED | Tất cả item terminal, ≥1 SERVED (auto) |
VOIDED | 505_CANCELLED | Void thủ công |
Helper guards: canVoid (bất kỳ active), canProgress (chỉ PENDING), canMarkReady (chỉ PROCESSING), canComplete (chỉ READY).
4.4 KitchenTicketItemStatuses
Nguồn:
core/src/models/schemas/sale/kitchen-ticket-item/constants.ts. Bộ khác với enum cấp ticket.
| Giá trị | Code | Trigger |
|---|---|---|
PENDING | 103_PENDING | Khởi tạo |
COOKING | 203_PROCESSING | startCookingItem |
READY | 302_SUCCESS | markItemReady — phát Kafka KITCHEN_TICKET_ITEM_STATUS_CHANGED |
SERVED | 303_COMPLETED | markItemServed |
VOIDED | 505_CANCELLED | voidTicketItem |
4.5 AllocationUsageStatuses
| Giá trị | Giai đoạn |
|---|---|
ACTIVE | Đặt/đang chiếm dụng |
SUCCESS | Order đã thanh toán → usage đóng |
CANCELLED | Order/reservation đã hủy |
EXPIRED | Reservation timeout |
4.6 ReservationStatuses
| Giá trị | Giai đoạn |
|---|---|
PENDING | Đã tạo, chờ xác nhận |
CONFIRMED | Host đã xác nhận |
CHECKED_IN | Khách đã đến; spawn SaleOrder |
CANCELLED | Terminal |
4.7 PosSessionStatuses
| Giá trị | Giai đoạn |
|---|---|
OPEN | Ca đang active |
CLOSED | Đã đối chiếu và đóng |
5. Bất biến liên thực thể
| Bất biến | Áp đặt |
|---|---|
SaleOrder.subtotal/tax/discount/total = Σ(items) | Service updateSummaryFromItems sau mỗi lần thay đổi item |
Nhiều nhất một OPEN PosSession per (merchantId, deviceId) | Service validateAndAttachSession |
Tổng SaleCheck = Σ(SaleCheckItem) cho dòng của nó | SaleCheckRepository.recalculateTotals |
KitchenTicket auto-progress qua PENDING→COOKING→READY→SERVED dựa trên status item | KitchenTicketRepository.evaluateTicketAutoProgression sau mỗi lần đổi status item |
KitchenTicket.ticketNumber unique partial per kitchen station (sequence reset khi đổi station) | Schema partial unique + getNextSequence |
PointTransaction idempotent per (customerId, saleOrderId) | Lookup existsBySaleOrderId trước khi ghi |
AllocationUsage theo vòng đời order/reservation (cascade hủy) | Cấp service trên cancelOrder / hủy |
Thao tác merge / split order bảo toàn tổng item | Transaction cấp service; audit transferHistory |
6. Hành vi Soft-delete
| Thực thể | Soft-delete | Ghi chú |
|---|---|---|
| Tất cả thực thể sale | ✓ | Marker deletedAt; archive cho SaleOrder = soft-delete |
KitchenTicket voidedAt | logical | voidedAt không phải soft-delete; ticket vẫn truy vấn được |