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.
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
Khi edit booking đã confirm (status gốc Approved/Attendance_Approved/Quote), confirm bị gate bằng modal Billing Difference: phải xem chênh lệch tài chính trước khi confirm thật. Submit & Confirm Attendance không bao giờ preview.
- Thứ tự UI: preview Billing Difference → popup xác nhận action (có Mute Notifications) → mới submit/confirm. Đóng preview → booking chưa confirm.
- Submit edit bị mute server-side (invariant
INV-BOOK-26), dù client gửimuteNotification=false; lựa chọn mute cuối cùng lấy ở popup confirm, gửi kèm request confirm. - Preview/confirm dùng đủ
TermBookingOrderIdssubmit trả về (invariantINV-BOOK-21,INV-BOOK-25), không đoán từ selection UI. - Confirm edit build một
BookingConfirmationPlanchung từ các order id trước khi áp side-effect thật (invariantINV-BOOK-23); attendance thay thế giữ roll call cũ khi đã điểm danh (invariantINV-BOOK-27).
Submit & Confirm Attendance không bị gate bởi Billing Difference (invariant INV-BOOK-26): confirm attendance không sinh thay đổi billing/invoice nên không cần review chênh lệch.
Tiếp theo: Kiến trúc & dữ liệu.