Skip to content

Mô hình miền

Bảng sale nằm trong schema PostgreSQL sale; bảng allocation trong schema allocation. Tất cả schema định nghĩa trong @nx/core/src/models/schemas/{sale,allocation}/. Cột số dùng decimal(15, 4).

1. ERD đầy đủ

2. Cột chung

CộtKiểuGhi chú
idtextPK, Snowflake
createdAt / modifiedAttimestamptz-
createdBy / modifiedBytextAudit user
deletedAttimestamptzSoft-delete
metadatajsonbTúi mở rộng

3. Thực thể

3.1 SaleOrder

Thuộc tínhGiá trị
BảngSaleOrder
Nguồncore/src/models/schemas/sale/sale-order/schema.ts
Soft-delete
TrườngKiểuBắt buộcMô tả
orderNumbertextUnique partial theo merchant
name / slugtextSlug unique partial
validityjsonb{ from, to } cho order giới hạn thời gian
statustextXem §4.1; mặc định DRAFT
draftAt / processingAt / partialAt / completedAt / cancelledAttimestamptzTimestamp theo trạng thái
cancellationReasontext-
customerIdtextFK
merchantIdtextChủ sở hữu
saleChannelIdtextFK
shiftIdtextFK đến Shift
openedInShiftEnrollmentId / closedInShiftEnrollmentIdtextFK đến ShiftEnrollment
currencytextMặc định VND
exchangeRatedecimal(12,6)Mặc định 1
subtotal / tax / discount / totaldecimal(15,4)Mặc định 0; duy trì bởi updateSummaryFromItems
originOrderIdtextTheo dõi parent của order-split
checkSplitAt / orderSplitAt / mergedAttimestamptzTimestamp thao tác
counterjsonb{ paid, paidItemIds[], total } - tiến trình thanh toán

3.2 SaleOrderItem

Thuộc tínhGiá trị
BảngSaleOrderItem
Polymorphic(itemType, itemId) qua generatePrincipalColumnDefs({ discriminator: 'item', defaultPolymorphic: 'ProductVariant' })
TrườngKiểuBắt buộcMô tả
saleOrderIdtextFK
itemTypetextPRODUCT_VARIANT (mặc định) / khác
itemIdtextFK target
modetextPRODUCT (mặc định - auto-merge duplicate) / CUSTOM (luôn dòng mới)
leadItemIdtextGroup lead cho combo items
currencytextMặc định VND
basePrice / unitPricedecimal(15,4)Trước/sau giảm giá, trên mỗi đơn vị
discount / taxdecimal(15,4)Mặc định 0
quantitydecimal(15,4)Mặc định 1
totaldecimal(15,4)Tính toán
fareId / fareProvidertextTham chiếu nguồn pricing
priceMetadatajsonbSnapshot pricing (chi tiết v2)
transferHistoryjsonbArray<TTransferHistoryEntry> - theo dõi merge/split
recipeIdtextLiên kết MaterialRecipe.id đang hoạt động (snapshot)

3.3 SaleCheck / SaleCheckItem

SaleCheck:

TrườngKiểuBắt buộcMô tả
saleOrderIdtextOrder parent
statustextPROCESSING (mặc định) / COMPLETED / CANCELLED
subtotal / tax / discount / totaldecimal(15,4)Tính lại bởi recalculateTotals
customerIdtextKhách theo từng 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ườngKiểuBắt buộcMô tả
ticketNumbertextUnique partial; tuần tự theo station
saleOrderIdtextFK
merchantIdtextChủ sở hữu
kitchenStationIdtextMục tiêu định tuyến (tùy chọn)
statustextXem §4.3; mặc định PENDING
priorityintMặc định 0 (cờ rush làm tăng priority)
sequenceintMặc định 1 (gợi ý thứ tự)
pendingAt / processingAt / readyAt / completedAt / voidedAttimestamptzTimestamp trạng thái

KitchenTicketItem:

TrườngKiểuBắt buộcMô tả
kitchenTicketIdtextFK
saleOrderItemIdtextFK
quantitydecimal(15,4)Mặc định 1
statustextXem §4.4; mặc định PENDING
startedAt / readyAt / servedAt / voidedAttimestamptzTimestamp trạng thái

Mỗi lần đổi trạng thái phát Kafka KITCHEN_TICKET_ITEM_STATUS_CHANGED và 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ỏi sale).

AllocationUsage - sử dụng đa hình của một allocation unit:

TrườngKiểuBắt buộcMô tả
usageTypetextSALE_ORDER / RESERVATION (qua discriminator: 'usage')
usageIdtextId FK target
unitIdtextFK đến AllocationUnit
merchantIdtextChủ sở hữu
assigneeIdtextNgười/staff được gán
statustextACTIVE (mặc định) / SUCCESS / CANCELLED / EXPIRED
typetextGENERAL (mặc định) / DINE_IN / TAKEAWAY / DELIVERY
reservedFrom / reservedTo / reservedAt / startedAt / completedAttimestamptzTimestamp 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ườngKiểuBắt buộcMô tả
merchantIdtextChủ sở hữu
guestNametext-
guestPhonetext-
guestEmailtext-
partySizeint-
reservedFromtimestamptz-
reservedTotimestamptz-
notestext-
sourcetextMặc định PHONE; WEB / WALK_IN / APP
occasiontextSinh nhật / kỷ niệm / v.v.
statustextPENDING (mặc định) / CONFIRMED / CHECKED_IN / CANCELLED
confirmedAt / checkedInAt / cancelledAttimestamptz-
cancellationReasontext-
saleOrderIdtextFK sau check-in

3.7 Shift / ShiftDrawer / ShiftEnrollment / ShiftEnrollmentEvent / ShiftReport

Mô hình Shift v2. Nguồn: core/src/models/schemas/sale/shift*. Tất cả thực thể đều soft-delete.

Shift - aggregate ca cấp cao nhất, scope theo (merchantId, saleChannelId):

TrườngKiểuBắt buộcMô tả
merchantIdtextChủ sở hữu
saleChannelIdtextScope kênh bán
codetextĐịnh danh (prefix SHIFT)
namejsonbi18n { default, en?, vi? }
notesjsonbi18n { default, en?, vi? }
statustextXem §4.7; mặc định 100_OPENING
openedByIdtextUser mở ca
closedByIdtextUser đóng ca
openedAt / closedAttimestamptzopenedAt mặc định là hiện tại
startAt / endAttimestamptzKhung giờ dự kiến (tùy chọn)
metadatajsonbTự do (key cũ openNotes/closeNotes đã gỡ - ghi chú nằm ở cột notes)

Một kênh có thể có nhiều ca 100_OPENING cùng lúc (mỗi thiết bị chỉ ở 1 ca mở - chặn qua enrollment); (merchantId, saleChannelId) là index thường.

ShiftDrawer - ngăn kéo tiền theo thiết bị, 1:1 với một ShiftEnrollment:

TrườngKiểuBắt buộcMô tả
shiftEnrollmentIdtextChủ 1:1 (partial unique)
shiftId / merchantId / deviceIdtextScope
statustextCùng bộ với Shift; mặc định 100_OPENING
openingFloatdecimal(15,4)Tiền float lúc mở
expectedCash / actualCash / cashDiscrepancydecimal(15,4)Đối chiếu
expectedNonCash / actualNonCashjsonbTShiftNonCashBreakdown (theo phương thức non-cash)
closeRecountCountintMặc định 0; số lần đếm lại
openedAt / closedAttimestamptzopenedAt mặc định là hiện tại

ShiftEnrollment - một principal (device hoặc user) tham gia vào một Shift:

TrườngKiểuBắt buộcMô tả
shiftId / merchantIdtextScope
principalTypetextDevice / User
principalIdtextId device hoặc user
statustextACTIVATED (mặc định) / DEACTIVATED
roletextVai trò trong ca
drawerIdtextPhiên ngăn kéo dùng chung đang gắn
enrolledByIdtext-
enrolledAt / leftAttimestamptzenrolledAt mặc định là hiện tại
notesjsonbi18n { default, en?, vi? } - ghi chú lúc join/tạo ca

Nhiều nhất một enrollment ACTIVATED cho mỗi principal - partial unique index.

ShiftEnrollmentEvent - ledger chỉ-ghi-thêm của các sự kiện enrollment + cash-movement:

TrườngKiểuBắt buộcMô tả
shiftEnrollmentId / shiftId / merchantIdtextScope
typetextXem §4.8
amountdecimal(15,4)Chỉ set cho cash movement (PAY_IN / PAY_OUT / SAFE_DROP)
occurredAttimestamptzMặc định là hiện tại
notetext-

ShiftReport - các dòng báo cáo X/Z; theo từng enrollment khi có shiftEnrollmentId, toàn ca khi NULL:

TrườngKiểuBắt buộcMô tả
shiftId / merchantId / saleChannelIdtextScope
shiftEnrollmentIdtextNULL = tổng hợp toàn ca; có giá trị = theo từng enrollment
reportTypetextX (snapshot trực tiếp) / Z (cuối cùng)
reportNointTuần tự theo scope
openedById / closedById / generatedByIdtextAudit
openedAt / closedAt / generatedAttimestamptzgeneratedAt mặc định là hiện tại
openingFloat, cashSales, cashRefunds, payIns, payOuts, safeDrops, expectedCash, actualCash, cashDiscrepancydecimal(15,4)Tổng kết tiền mặt
grossSales, discounts, taxAmount, netSales, voidAmountdecimal(15,4)Tổng kết doanh số
orderCount / itemCount / voidCountintMặc định 0
paymentMethods / sections / categoryBreakdown / actualNonCashjsonbCác phân rã
notestext-

3.8 Customer

TrườngKiểuBắt buộcMô tả
nametext-
phonetext-
emailtext-
userIdtextTài khoản user được liên kết (tùy chọn)
merchantIdtextChủ sở hữu
pointBalancedecimal(15,4)Mặc định 0

3.9 PointTransaction

TrườngKiểuBắt buộcMô tả
customerIdtextFK
merchantIdtextChủ sở hữu
saleOrderIdtextOrder nguồn
typetextAWARD / REDEEM / ADJUST (theo PointTransactionTypes)
pointsdecimal(15,4)Lượng chênh lệch có dấu
conversionRatedecimal(15,4)Snapshot tỷ lệ điểm trên tiền tệ tại thời điểm cộng điểm

Tính idempotent: PointTransactionRepository.existsBySaleOrderId chặn cộng điểm trùng lặp.

4. Status Enums

4.1 SaleOrderStatuses

Giá trịGiai đoạn
DRAFTGiỏ hàng / item còn sửa được
PROCESSINGCheckout xong, chờ thanh toán
PARTIALĐã nhận một phần thanh toán
COMPLETEDĐã thanh toán đủ
CANCELLEDTrạng thái cuối

4.2 SaleCheckStatuses

Giá trịGiai đoạn
PROCESSINGMặc định - đang nhận thanh toán
PARTIALĐã nhận một phần thanh toán
COMPLETEDĐã thanh toán đủ
CANCELLEDTrạng thái cuối

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ịCodeGiai đoạn
PENDING103_PENDINGVừa gửi xuống bếp, chưa item nào COOKING
PROCESSING203_PROCESSINGÍt nhất một item đang COOKING (auto-progress từ PENDING)
READY302_SUCCESSTất cả item READY-trở-đi (auto)
COMPLETED303_COMPLETEDTất cả item ở trạng thái cuối, ≥1 SERVED (auto)
VOIDED505_CANCELLEDVoid thủ công

Các guard helper: canVoid (bất kỳ trạng thái hoạt động nào), 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ịCodeTrigger
PENDING103_PENDINGKhởi tạo
COOKING203_PROCESSINGstartCookingItem
READY302_SUCCESSmarkItemReady - phát Kafka KITCHEN_TICKET_ITEM_STATUS_CHANGED
SERVED303_COMPLETEDmarkItemServed
VOIDED505_CANCELLEDvoidTicketItem

4.5 AllocationUsageStatuses

Giá trịGiai đoạn
ACTIVEĐang giữ chỗ/đang chiếm dụng
SUCCESSOrder đã thanh toán → usage đóng
CANCELLEDOrder/reservation đã huỷ
EXPIREDReservation quá hạn

4.6 ReservationStatuses

Giá trịGiai đoạn
PENDINGĐã tạo, chờ xác nhận
CONFIRMEDĐã được chủ đặt chỗ xác nhận
CHECKED_INKhách đã đến; sinh SaleOrder
CANCELLEDTrạng thái cuối

4.7 ShiftStatuses

Nguồn: core/src/models/schemas/sale/shift/constants.ts. ShiftDrawerStatuses dùng bộ giống hệt; isOpen = 100_OPENING.

Giá trịGiai đoạn
100_OPENINGCa / drawer đang hoạt động
200_CLOSEDĐã đóng, chờ đối chiếu
300_RECONCILEDĐã đối chiếu tiền - trạng thái cuối

4.8 ShiftEnrollment Enums

ShiftEnrollmentStatuses: ACTIVATED (mặc định) ↔ DEACTIVATED.

ShiftPrincipalType: Device / User.

ShiftEnrollmentEventTypes - ledger chỉ-ghi-thêm; amount chỉ set cho cash movement:

Giá trịCash movement
DROP_IN / DROP_OUTkhông
CHECK_IN / CHECK_OUTkhông
DRAWER_OPEN / RECONCILEkhông
PAY_IN / PAY_OUT / SAFE_DROPcó (amount bắt buộc)

4.9 ShiftReportTypes

Giá trịGiai đoạn
XSnapshot trực tiếp (chạy lại được)
ZBáo cáo cuối cùng - một cho mỗi scope

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 Shift 100_OPENING cho mỗi (merchantId, saleChannelId)Partial unique index + ShiftService
Nhiều nhất một ShiftEnrollment ACTIVATED cho mỗi principalPartial unique index
ShiftDrawer 1:1 với một ShiftEnrollmentPartial unique index trên shiftEnrollmentId
ShiftEnrollmentEvent.amount chỉ set cho PAY_IN / PAY_OUT / SAFE_DROPShiftEnrollmentEventTypes.isCashMovement
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 itemKitchenTicketRepository.evaluateTicketAutoProgression sau mỗi lần đổi status item
KitchenTicket.ticketNumber unique partial theo kitchen station (sequence reset khi đổi station)Schema partial unique + getNextSequence
PointTransaction idempotent theo (customerId, saleOrderId)Tra cứu existsBySaleOrderId trước khi ghi
AllocationUsage theo vòng đời order/reservation (huỷ lan truyền)Ở cấp service trên cancelOrder / huỷ
Thao tác merge / split order bảo toàn tổng itemTransaction cấp service; audit transferHistory

6. Hành vi Soft-delete

Thực thểSoft-deleteGhi chú
Tất cả thực thể saleMarker deletedAt; archive cho SaleOrder = soft-delete
KitchenTicket voidedAtlogicalvoidedAt không phải soft-delete; ticket vẫn truy vấn được

7. Trang liên quan

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