Booking — Kiến trúc & dữ liệu
Trang này phác họa các "vật thể" dữ liệu của normal booking và quan hệ giữa chúng — góc nhìn khái niệm, không phải schema chính xác.
Sơ đồ quan hệ (mức khái niệm)
Hai mức record: Order → Booking
Confirm copy OrderLine → BookingLine, OrderExtra → BookingExtra, OrderDiscount → BookingDiscount; rồi tạo
TermAttendancecho từng session và trigger billing.
Ranh giới sở hữu (ownership)
| Khối | Ai được sửa |
|---|---|
| Lựa chọn session Step 1/Step 2 (client-side) | UI (chỉ state, không mutate DB) |
| TermBookingOrder / TermBooking / Billing / Invoice / CreditNote / TermAttendance (runtime) | Chỉ các service booking/confirmation hiện có |
UI không tự tạo
TermBookingOrderlúc load Step 1/Step 2, không gọi API billing/invoice mức thấp để giả lập submit/confirm. Mọi mutation đi qua service backend.
Subscription Enrichment boundary
- Setup/Subscription chỉ cung cấp eligibility; Booking sở hữu runtime line, attendance và charge.
- Confirm và Manage Enrichments dùng identity
TermBookingId + TermProductId + Dateđể dedupe. - Manage Enrichments chỉ mutate occurrence tương lai chưa bị invoice/credit/attendance/lock; mọi Invoice/CreditNote adjustment đi qua Finance/Booking owner.
Ví dụ thực tế: Staff bỏ một ngày chưa invoice trong Booking có năm ngày Enrichment. → Chỉ line đó bị deactivate và Booking total được tính lại. → Bốn ngày khác, invoice Subscription và finance log không bị sửa trực tiếp.
Các API chính (đường đọc/ghi)
| Mục đích | API |
|---|---|
| Lấy session khả dụng | GET /api/ConsumerBooking/GetAvailableSessionsAsync |
| Lấy capacity | GET /api/ConsumerBooking/GetCapacitiesAsync |
| Tạo order (giỏ) | POST /api/ConsumerBooking/CreateBookingOrder |
| Submit | POST /api/ConsumerBooking/SubmitBookingOrders |
| Preview billing | POST /api/ConsumerBooking/PreviewBillingDifference |
| Bulk action queue | POST /api/ConsumerBooking/BatchActionInQueue |
| Confirm | POST /api/ConsumerBooking/ConfirmBookingOrders |
| Quote / Accept | POST .../GenerateQuotesAsync, .../AcceptQuoteAsync |
| Hủy | POST /api/ConsumerBooking/DeleteBookingOrdersAsync |
| Discount config | GET/POST /api/discount |
Billing Difference: đường preview → confirm
- Preview đọc dữ liệu hiện tại và dựng projection trong memory; trả
Difference, breakdown vàPreviewSnapshotToken, không ghi database. - Phép tính chạy một lần trên toàn billing scope; breakdown attendee chỉ là phần giải thích và phải reconcile về
Summary.Difference. - Confirm dùng lại
BookingConfirmationPlan, kiểm tra token trong transaction rồi mới persist. - Tắt Preview hoặc không đủ điều kiện → bỏ qua token và confirm trực tiếp theo flow hiện có.
Điều kiện, fallback và error code xem Edit Booking & Preview Billing Diff. Chi tiết controller/service xem Code flow Preview Billing Diff.
Quy ước dữ liệu đáng nhớ
| Field | Quy ước |
|---|---|
TermBookingOrder.TypeId | Booking(0) = normal; Subscription(4)/WaitingList(1) loại trừ khỏi conflict normal. |
| Discount slot trên line | Tối đa 3: DiscountId, Discount2Id, Discount3Id. |
ProgramCategory.CombinedBookingOptionId | 0 = theo course; 1 = gộp theo program category. |
ActionLogId | Khóa nhóm các booking đặt cùng một thao tác (cho combined edit). |
| Booking number | Hiển thị dạng BK-xxxx cho người dùng. |
Calendar read model
Booking Calendar dùng event projection đã map sẵn từ TPS/session/attendance/attendee. Click event chỉ đọc projection đó để mở popup; không có mutation, không gọi popup mặc định của thư viện. sessionGroupKey và bucket matching là boundary để phân biệt special-day programme, Transportation và Makeup-session.
Ví dụ thực tế: Popup hiển thị nhiều attendee cùng event. → Không flatten thành chuỗi “and N others”. → Projection giữ list và render từng tên, trong khi tile vẫn dùng dữ liệu tương tự.
Tiếp theo: Luồng xử lý trong code.