Booking — Architecture & data
This page sketches the normal-booking data entities and their relationships. For exact table/field names see docs/DB_SCHEMA_DESIGN/ and the spec.
Relationship diagram (conceptual)
Two record levels: Order → Booking
Confirm copies OrderLine → BookingLine, OrderExtra → BookingExtra, OrderDiscount → BookingDiscount; then creates
TermAttendanceper session and triggers billing.
Ownership boundary
| Block | Who may mutate |
|---|---|
| Step 1/Step 2 session selection (client-side) | UI (state only, no DB mutation) |
| TermBookingOrder / TermBooking / Billing / Invoice / CreditNote / TermAttendance (runtime) | Only the existing booking/confirmation services |
The UI does not create a
TermBookingOrderon Step 1/Step 2 load, and does not call low-level billing/invoice APIs to emulate submit/confirm. All mutations go through backend services.
Subscription Enrichment boundary
- Setup/Subscription only provides eligibility; Booking owns the runtime line, attendance, and charge.
- Confirm and Manage Enrichments use the identity
TermBookingId + TermProductId + Datefor deduplication. - Manage Enrichments only mutates future occurrences that have not been invoiced/credited/attended/locked; all Invoice/CreditNote adjustments go through the Finance/Booking owner.
Real-world example: Staff removes one not-yet-invoiced day from a Booking with five Enrichment days. → Only that line is deactivated and the Booking total is recalculated. → The other four days, the Subscription invoice, and the finance log are not edited directly.
Key APIs (read/write paths)
| Purpose | API |
|---|---|
| Available sessions | GET /api/ConsumerBooking/GetAvailableSessionsAsync |
| Capacities | GET /api/ConsumerBooking/GetCapacitiesAsync |
| Create order (cart) | POST /api/ConsumerBooking/CreateBookingOrder |
| Submit | POST /api/ConsumerBooking/SubmitBookingOrders |
| Billing preview | POST /api/ConsumerBooking/PreviewBillingDifference |
| Bulk action queue | POST /api/ConsumerBooking/BatchActionInQueue |
| Confirm | POST /api/ConsumerBooking/ConfirmBookingOrders |
| Quote / Accept | POST .../GenerateQuotesAsync, .../AcceptQuoteAsync |
| Cancel | POST /api/ConsumerBooking/DeleteBookingOrdersAsync |
| Discount config | GET/POST /api/discount |
Billing Difference: preview → confirm path
- Preview reads current data and builds an in-memory projection; it returns
Difference, breakdowns andPreviewSnapshotTokenwithout writing to the database. - The calculation runs once across the whole billing scope; attendee breakdowns explain the result and must reconcile to
Summary.Difference. - Confirm reuses
BookingConfirmationPlan, checks the token inside the transaction, and only then persists. - Disabled or ineligible Preview skips the token and confirms through the existing direct flow.
For conditions, fallback and error codes, see Edit Booking & Preview Billing Diff. For the controller/service trace, see Preview Billing Diff — code flow.
Data conventions worth remembering
| Field | Convention |
|---|---|
TermBookingOrder.TypeId | Booking(0) = normal; Subscription(4)/WaitingList(1) excluded from normal conflict. |
| Discount slots per line | Up to 3: DiscountId, Discount2Id, Discount3Id. |
ProgramCategory.CombinedBookingOptionId | 0 = per course; 1 = combine by program category. |
ActionLogId | Groups bookings placed in the same action (for combined edit). |
| Booking number | Shown as BK-xxxx to users. |
Next: Codebase flow.
Calendar read model
Booking Calendar uses a mapped event projection built from TPS/session/attendance/attendee. Clicking an event only reads that projection to open a popup; it does not mutate state or call the library's default popup. sessionGroupKey and bucket matching separate special-day programmes, Transportation and Makeup-session events.
Real-world example: An event has several attendees. → Do not flatten them into an “and N others” string. → Keep the list in the projection and render each name separately.