Booking — Khái niệm cốt lõi
Các đối tượng cần biết trước khi booking
Normal booking không tự tạo course hay lịch học từ đầu. Nó đọc cấu hình từ product hierarchy, cho người dùng chọn session, rồi ghi lựa chọn đó vào order/booking runtime.
ProgramCategory -> Program -> TermProgramSet -> TermProduct / Session
^
|
Term| Đối tượng | Là gì | Vai trò trong booking |
|---|---|---|
| Term | Kỳ hoạt động/học kỳ, có StartDate, EndDate, trạng thái và thường thuộc một site/org. | Đặt biên thời gian cho course và session; một Term chứa nhiều TermProgramSet. |
| ProgramCategory / Category | Loại chương trình, ví dụ TermCare, Holiday, Class. | Giữ nhiều rule nền như booking/payment/discount, invoice, cut-off và combine booking. |
| Program | Chương trình cụ thể dưới một category, thường gắn với site/org. | Là template/course source để tạo offering theo từng term. |
| TermProgramSet / TPS | Offering/course cụ thể trong một Term. | Nơi cấu hình lịch, giá, capacity, ngày trong tuần, discount và metadata hiển thị booking. |
| TermProduct / Session | Buổi/slot cụ thể được sinh từ TPS, có ngày, giờ, capacity, giá và ProductType. | Đây là đơn vị nhỏ nhất mà booking line ghi nhận. Khi nói "Section" trong bối cảnh booking, hiểu là Session/TermProduct, không phải entity riêng. |
| Holiday | Một ProgramType/category đặc biệt (Holiday = 12). | Dùng booking modes Casual/Standard, FullTime/Full Term, PartTime/Full Week; có thêm activity theo ngày. |
| Activity | ProductType.Activity, chỉ dùng cho Holiday. | Là theme/hoạt động của ngày Holiday, không có giá và không phải time slot chính như Session. |
Các đối tượng runtime nối người dùng với cấu hình trên:
| Đối tượng | Là gì | Khi nào xuất hiện |
|---|---|---|
| Account | Gia đình/người chịu trách nhiệm tài chính. | Được chọn khi admin book hoặc lấy từ user đăng nhập ở customer flow. |
| Attendee | Trẻ/người tham gia được book vào session. | Mỗi booking/order gắn với một attendee; validation dùng profile như tuổi, school year, height/weight. |
| TermBookingOrder | Yêu cầu booking đang xử lý, giống giỏ hàng/request. | Tạo khi chọn session và submit trước confirm. |
| TermBooking | Booking đã confirm, là runtime chính thức. | Tạo khi confirm từ TermBookingOrder. |
| TermAttendance | Bản ghi điểm danh cho từng session đã confirm. | Sinh khi confirm booking. |
| Billing / Invoice | Sổ tài chính và hóa đơn của booking. | Billing được tạo khi submit; invoice được trigger khi confirm theo payment/finance rule. |
Order vs Booking: request vs confirmed
Phân biệt quan trọng nhất:
| TermBookingOrder | TermBooking | |
|---|---|---|
| Là gì | Yêu cầu booking đang xử lý (request/giỏ hàng) | Booking đã confirm (runtime chính thức) |
| Khi nào | Từ lúc tạo tới lúc confirm | Sau khi confirm |
| Dòng con | TermBookingOrderLine/Extra/Discount | TermBookingLine/Extra/Discount |
| Tài chính | Submit → tạo Billing | Confirm → trigger Invoice + TermAttendance |
Hệ thống mô hình hoá flow bằng
TermBookingOrdercho request in-flight vàTermBookingcho booking đã confirm.
Subscription Enrichment là Booking-owned charge
Subscription Booking không đi qua TermBookingOrder của normal booking. Khi Enrollment được confirm, mỗi Enrichment occurrence hợp lệ tạo một TermBookingLine active và một TermAttendance tương ứng.
| Khái niệm | Quy tắc |
|---|---|
| Occurrence identity | TermBookingId + TermProductId + Date; không có hai active line cùng key. |
| Giá | Đọc TermProduct.RateStandard hiện tại tại server; không nhận giá/count/amount từ client. |
| Accounting item | Đọc TermProduct.RateStandardItemId; thiếu hoặc stale thì fail closed trước mutation. |
| Base Subscription session | Vẫn là line zero-priced theo semantics hiện có, không bị thay bằng Enrichment. |
| Tổng Booking | BookingAmount/TotalAmount gồm các Enrichment line có giá và các adjustment hiện hành. |
| Subscription Billing | Không copy amount từ TermBookingLine vào schedule hoặc Subscription invoice. |
Generation và Manage Enrichments đều retry-safe; replay chỉ converge về một active line cho mỗi occurrence identity.
Ví dụ thực tế: Một Booking có Morning Care ngày 01/10 giá $5 và base session zero-priced. → Booking total là $5; retry worker không tạo dòng thứ hai; Subscription invoice vẫn lấy schedule riêng.
Vòng trạng thái (status lifecycle)
TermBookingStatus (Tux/src/Tux.Core/Enums/TermBookingStatus.cs):
| Status | Mã | Ý nghĩa |
|---|---|---|
Removed | 0 | Đã xóa request / change request. |
Draft | 100 | Nháp. |
Created | 200 | Đã lưu vào "giỏ" (sau CreateBookingOrder). |
Submitted | 300 | Đã submit request mới / lưu change request. |
Submitted_Change | 350 | Bị ảnh hưởng bởi submit khác — không hiển thị. |
Attendance_Approved | 450 | Chỉ confirm điểm danh. |
Quote | 475 | Đã sinh quote. |
Accept | 485 | Đã accept quote. |
Approved | 500 | Đã confirm (chính thức). |
Archived/Suspended/Closed | 600/700/800 | Trạng thái lưu trữ/treo/đóng. |
Canceled | 900 | Đã hủy. |
Processing(400) NOT IN USE.
Confirm — trạng thái đích và điều kiện
ActionType quyết định trạng thái đích khi confirm:
Action (ActionType) | Target status | Ý nghĩa |
|---|---|---|
Confirm_Booking | Approved (500) | Confirm đầy đủ: sinh TermAttendance, trigger Invoice. |
Confirm_Attendance | Attendance_Approved (450) | Chỉ confirm điểm danh, không gate bởi Billing Difference (INV-BOOK-26). |
Confirm_Invoice | Quote (475) | Chỉ sinh quote. |
Order đủ điều kiện confirm khi:
IsActivevàTypeId∈ {Booking(0),TrialSession(2)} —WaitingList/Subscription/EoIkhông confirm qua đường này.StatusId∈ {Submitted(300),Submitted_Change(350),Attendance_Approved(450),Quote(475)}.- Một lượt confirm chỉ gom các order cùng
OrgId/AccountId/StatusId/TypeId/ProgramCategoryId/BillingId(INV-BOOK-03);Confirm_Bookingtrên statusQuotechỉ dùng narrow order set (INV-BOOK-16).
Gate trước confirm:
- Add-on capacity: order line chạm đủ add-on capacity → chặn confirm, trừ khi
IgnoreDoubleBooking. - Session capacity:
ValidateCapacityForConfirmBookingkiểm tra sĩ số session trước confirm. - Billing Difference: edit booking đã confirm (status gốc
Approved/Attendance_Approved/Quote) phải xem preview trước khi confirm;Confirm_Attendanceluôn bỏ qua bước này.
Order type — phân biệt loại booking
TermBookingOrderType (Tux/src/Tux.Core/Enums/TermBookingOrderType.cs):
| Loại | Mã | Ghi chú |
|---|---|---|
| Booking | 0 | Normal booking (phạm vi trang này). |
| WaitingList | 1 | Danh sách chờ. |
| TrialSession | 2 | Buổi học thử (submit/confirm dùng chung đường với Booking). |
| Subscription | 4 | Booking từ subscription (đường Enrollment). |
| EoI | 5 | Expression of Interest. |
⚠️ Khi chống trùng lịch normal booking, record
TypeId = SubscriptionhoặcWaitingListkhông bị tính là conflict.MakeupSession/ClosureDaychỉ dùng ở Line/Extra, không phải TypeId.
Booking type — phân loại FULL_TIME / PART_TIME / CASUAL
Phân loại từng line theo rate type, rồi suy ra loại order theo độ ưu tiên. Enum BookingType: {0,1,2} = {CASUAL, FULL_TIME, PART_TIME} (INV-BC-001).
| Cấp | Xác định ở đâu | Cách suy ra |
|---|---|---|
| Line | TermBookingOrderLine.BookingRateTypeId / TermBookingLine.BookingRateTypeId | Có giá trị đã lưu → dùng thẳng; chưa lưu → so AppliedItemId với rate item của term product: RateFullTimeItemId → FULL_TIME, RatePartTimeItemId → PART_TIME, còn lại → CASUAL (INV-BC-003) |
| Order | Suy lúc dùng (BookingTypeHelper.DetermineBookingType), không lưu cột riêng | FULL_TIME > PART_TIME > CASUAL: có line FULL_TIME → FULL_TIME; có line PART_TIME → PART_TIME; còn lại → CASUAL (INV-BC-002) |
Thứ tự resolve line: TermBookingOrderLine.BookingRateTypeId → TermBookingLine.BookingRateTypeId → ItemId vs TermProduct.RateFtItemId/RatePtItemId → null (rơi về CASUAL). Order không line → CASUAL.
Classification ảnh hưởng:
- Thanh toán / eligibility: Direct Debit (
DirectDebitEligibilityService), PayNow (DirectPaymentService.ValidatePayNowEligibilityAsync), deposit (DepositEligibilityService). - Discount:
DiscountEligibilityService+BookingHelper; scope discount PartTime bao gồm cả lineFULL_TIME(INV-BC-004). - Pricing:
BillingServiceresolve rate/item và enrich preview. - Subsidy gating: dùng chung trong predicate với subsidy flags.
- Luồng family-discount dùng classifier riêng theo day-count (
TermBookingService.GetBookingType), không cùng thuật toán item-mapping; semantic preview vs commit khác nhau.
⚠️
TermBookingOrder.IsCasualOrSpecialOnlychỉ xuất hiện trong DTO projection, model đánh[NotMapped].
Ví dụ thực tế: Order gồm 1 line full-time (khớp
RateFullTimeItemId) và 1 line casual. → Xác định loại booking để tính pricing/discount/eligibility thanh toán. → Order xếpFULL_TIMEtheo ưu tiênFULL_TIME > PART_TIME > CASUAL.
Confirmation type — cách confirm gắn với thanh toán
TermBookingConfirmationType (...Enums/TermBookingConfirmationType.cs):
| Type | Mã | Khi nào |
|---|---|---|
Confirm_Without_Payment | 101 | Confirm không cần thanh toán. |
Pay_Now_Pay_All | 201 | Trả hết ngay. |
Pay_Now_Pay_Deposit | 202 | Đặt cọc ngay. |
Direct_Debit | 301 | Thiết lập ghi nợ tự động. |
Admin_Confirm | 401 | Admin confirm (dùng cho subscription direct-confirm). |
PayNow / Deposit / Direct Debit có thể arm timeout auto-cancel; PayLater không timeout nhưng phải qua validate eligibility.
Backend tự sửa ConfirmationTypeId nếu lệch với PaymentOptionId trả tiền và log warning (invariant INV-BOOKING-CONFIRM-001): PayNow/PayNowDeposit → 201; DirectDebit → 301; PayLater (không có PaymentOptionId) giữ 101. Checkout không gọi ConfirmBookingOrders cho PayLater.
Account resolution: admin vs customer
| Bối cảnh | Account target |
|---|---|
| Admin — booking mới | Rỗng, phải chọn account/attendee thủ công (không lấy từ login/profile/subscription). |
| Admin — edit booking/order | Lấy account của booking/order đang edit, khóa. |
| Customer — mới/edit | Account đã đăng nhập, khóa. |
Billing Difference review
Billing Difference là phép tính thử tác động tài chính trước khi commit thay đổi. Tính năng áp dụng cho Admin Edit booking đã confirm (status gốc Approved/Attendance_Approved/Quote). Submit & Confirm Attendance không bao giờ preview.
- Khi bật và đủ điều kiện: Submit → Preview Billing Difference → Confirm + Mute Notifications.
- Preview dùng đầy đủ
TermBookingOrderIdssau Submit và tính theo toàn billing scope; breakdown attendee chỉ để giải thích, không phải phép tính riêng từng người. - Preview chỉ đọc. Confirm dùng cùng IDs/options, kiểm tra
PreviewSnapshotTokenrồi mới ghi kết quả. - Khi tắt hoặc không đủ điều kiện, flow bỏ qua Preview/token và confirm trực tiếp theo policy hiện có.
Submit & Confirm Attendancekhông bị gate bởi Billing Difference vì không chốt thay đổi billing.
Chi tiết điều kiện, token, amount và error code nằm ở Edit Booking & Preview Billing Diff. Chi tiết request qua từng controller/service nằm ở Code flow Preview Billing Diff. Các tình huống đặc thù như shared BillingId, archived line và bulk edit giữ ở Case thường gặp.
Ví dụ thực tế: Admin giảm booking đã confirm từ $65 xuống $55. → Preview cho xem tác động -$10. → Confirm mới ghi billing; nếu token stale, hệ thống yêu cầu Preview lại.
Calendar event popup và special-day programme
- Booking Calendar map event một lần rồi dùng cùng object cho tile và popup; vì vậy thời gian không lệch. Popup chỉ đọc và attendee được giữ dạng list đầy đủ.
+N more, quick-info, editor và alert mặc định của thư viện đều bị hủy trong Booking Calendar. Transportation và Makeup-session calendar không dùng policy này.- Special-day programme được group/match theo
sessionGroupKey/bucket riêng và không bookable; đây là rule của surface booking, không phải capacity fallback.
Ví dụ thực tế: Hai session đặc biệt có cùng bucket nhưng khác product/time slot. → Không ghép nhầm attendee. → Matcher dùng cả product và time slot, còn popup hiển thị event đã map đúng.
Tiếp theo: Kiến trúc & dữ liệu.