Booking — Core concepts
Domain objects to know before booking
Normal booking does not create courses or schedules from scratch. It reads configuration from the product hierarchy, lets the user select sessions, then writes that selection into the order/booking runtime.
ProgramCategory -> Program -> TermProgramSet -> TermProduct / Session
^
|
Term| Object | What it is | Role in booking |
|---|---|---|
| Term | Operating period/term with StartDate, EndDate, status, and usually a site/org scope. | Sets the time boundary for courses and sessions; one Term contains many TermProgramSet records. |
| ProgramCategory / Category | Program type, such as TermCare, Holiday, or Class. | Holds many base rules for booking, payment, discount, invoice, cut-off, and combine booking. |
| Program | A concrete program under a category, usually tied to a site/org. | Acts as the course/template source used to create term-specific offerings. |
| TermProgramSet / TPS | A concrete offering/course within a Term. | Defines schedule, price, capacity, weekdays, discounts, and booking display metadata. |
| TermProduct / Session | A concrete session/slot generated from a TPS, with date, time, capacity, price, and ProductType. | The smallest selectable unit recorded on booking lines. If someone says "Section" in this booking context, read it as Session/TermProduct, not a separate entity. |
| Holiday | A special ProgramType/category (Holiday = 12). | Uses booking modes Casual/Standard, FullTime/Full Term, and PartTime/Full Week; also supports day activities. |
| Activity | ProductType.Activity, used only by Holiday. | A holiday day theme/activity; it has no price and is not the main time slot like a Session. |
Runtime objects connect the user to that setup:
| Object | What it is | When it appears |
|---|---|---|
| Account | Family/payer responsible for finance. | Selected manually in admin booking, or resolved from the authenticated customer flow. |
| Attendee | Child/participant being booked into sessions. | Each order/booking targets one attendee; validation uses profile fields such as age, school year, height, and weight. |
| TermBookingOrder | In-flight booking request, like a cart/request. | Created when sessions are selected and submitted before confirmation. |
| TermBooking | Confirmed booking, the official runtime record. | Created when a TermBookingOrder is confirmed. |
| TermAttendance | Roll-call/attendance record for each confirmed session. | Created during booking confirmation. |
| Billing / Invoice | Booking finance ledger and invoice. | Billing is created on submit; invoice generation is triggered on confirm according to payment/finance rules. |
Order vs Booking: request vs confirmed
The most important distinction:
| TermBookingOrder | TermBooking | |
|---|---|---|
| What | An in-flight booking request (cart) | A confirmed booking (actual runtime) |
| When | From creation until confirmation | After confirmation |
| Child rows | TermBookingOrderLine/Extra/Discount | TermBookingLine/Extra/Discount |
| Finance | Submit → creates Billing | Confirm → triggers Invoice + TermAttendance |
The system models the flow using
TermBookingOrderfor in-flight requests andTermBookingfor confirmed bookings.
Status lifecycle
TermBookingStatus (Tux/src/Tux.Core/Enums/TermBookingStatus.cs):
| Status | Code | Meaning |
|---|---|---|
Removed | 0 | Deleted request / change request. |
Draft | 100 | Draft. |
Created | 200 | Saved to cart (after CreateBookingOrder). |
Submitted | 300 | Submitted new request / saved change request. |
Submitted_Change | 350 | Affected by other submits — not displayed. |
Attendance_Approved | 450 | Attendance-only confirmation. |
Quote | 475 | Quote generated. |
Accept | 485 | Quote accepted. |
Approved | 500 | Confirmed (final). |
Archived/Suspended/Closed | 600/700/800 | Archived/suspended/closed states. |
Canceled | 900 | Cancelled. |
Processing(400) is NOT IN USE.
Confirm — target status and conditions
ActionType decides the target status on confirm:
Action (ActionType) | Target status | Meaning |
|---|---|---|
Confirm_Booking | Approved (500) | Full confirm: creates TermAttendance, triggers Invoice. |
Confirm_Attendance | Attendance_Approved (450) | Confirms attendance only, not gated by Billing Difference (INV-BOOK-26). |
Confirm_Invoice | Quote (475) | Creates a quote only. |
An order is eligible for confirm when:
IsActiveandTypeId∈ {Booking(0),TrialSession(2)} —WaitingList/Subscription/EoIare not confirmed through this path.StatusId∈ {Submitted(300),Submitted_Change(350),Attendance_Approved(450),Quote(475)}.- One confirm pass only groups orders with the same
OrgId/AccountId/StatusId/TypeId/ProgramCategoryId/BillingId(INV-BOOK-03);Confirm_BookingonQuotestatus uses a narrow order set only (INV-BOOK-16).
Gates before confirm:
- Add-on capacity: an order line fills add-on capacity → confirm blocked, unless
IgnoreDoubleBooking. - Session capacity:
ValidateCapacityForConfirmBookingchecks session headcount before confirm. - Billing Difference: editing a confirmed booking (original status
Approved/Attendance_Approved/Quote) requires a preview before confirm;Confirm_Attendancealways skips this step.
Order type — booking kinds
TermBookingOrderType (Tux/src/Tux.Core/Enums/TermBookingOrderType.cs):
| Type | Code | Note |
|---|---|---|
| Booking | 0 | Normal booking (scope of this page). |
| WaitingList | 1 | Waiting list. |
| TrialSession | 2 | Trial session (shares submit/confirm path with Booking). |
| Subscription | 4 | Subscription booking (Enrollment path). |
| EoI | 5 | Expression of Interest. |
⚠️ In the normal booking double-booking guard, records with
TypeId = SubscriptionorWaitingListare not treated as conflicts.MakeupSession/ClosureDayare used only on Line/Extra, not as TypeId.
Booking type — FULL_TIME / PART_TIME / CASUAL
Each line is classified by rate type, then the order type is derived by priority. Enum BookingType: {0,1,2} = {CASUAL, FULL_TIME, PART_TIME} (INV-BC-001).
| Level | Where it is determined | How it is derived |
|---|---|---|
| Line | TermBookingOrderLine.BookingRateTypeId / TermBookingLine.BookingRateTypeId | A saved value → used directly; not saved → compare AppliedItemId against the term product's rate items: RateFullTimeItemId → FULL_TIME, RatePartTimeItemId → PART_TIME, otherwise → CASUAL (INV-BC-003) |
| Order | Derived at use time (BookingTypeHelper.DetermineBookingType), no dedicated column stored | FULL_TIME > PART_TIME > CASUAL: a FULL_TIME line → FULL_TIME; a PART_TIME line → PART_TIME; otherwise → CASUAL (INV-BC-002) |
Line resolve order: TermBookingOrderLine.BookingRateTypeId → TermBookingLine.BookingRateTypeId → ItemId vs TermProduct.RateFtItemId/RatePtItemId → null (falls back to CASUAL). An order with no lines → CASUAL.
Classification affects:
- Payment / eligibility: Direct Debit (
DirectDebitEligibilityService), PayNow (DirectPaymentService.ValidatePayNowEligibilityAsync), deposit (DepositEligibilityService). - Discount:
DiscountEligibilityService+BookingHelper; the PartTime discount scope also includesFULL_TIMElines (INV-BC-004). - Pricing:
BillingServiceresolves rate/item and enriches the preview. - Subsidy gating: shared in the predicate with subsidy flags.
- The family-discount flow uses a separate day-count classifier (
TermBookingService.GetBookingType), not the same item-mapping algorithm; preview and commit semantics differ.
⚠️
TermBookingOrder.IsCasualOrSpecialOnlyonly appears in the DTO projection; the model is marked[NotMapped].
Real-world example: An order has one full-time line (matching
RateFullTimeItemId) and one casual line. → Determine the booking type to compute pricing/discount/payment eligibility. → The order ranksFULL_TIMEby the priorityFULL_TIME > PART_TIME > CASUAL.
Confirmation type — confirm tied to payment
TermBookingConfirmationType (...Enums/TermBookingConfirmationType.cs):
| Type | Code | When |
|---|---|---|
Confirm_Without_Payment | 101 | Confirm with no payment. |
Pay_Now_Pay_All | 201 | Pay in full now. |
Pay_Now_Pay_Deposit | 202 | Pay deposit now. |
Direct_Debit | 301 | Set up recurring debit. |
Admin_Confirm | 401 | Admin confirm (used by subscription direct-confirm). |
PayNow / Deposit / Direct Debit may arm an auto-cancel timeout; PayLater has no timeout but must pass eligibility validation.
The backend fixes ConfirmationTypeId itself if it differs from the paying PaymentOptionId and logs a warning (invariant INV-BOOKING-CONFIRM-001): PayNow/PayNowDeposit → 201; DirectDebit → 301; PayLater (no PaymentOptionId) keeps 101. Checkout does not call ConfirmBookingOrders for PayLater.
Account resolution: admin vs customer
| Context | Target account |
|---|---|
| Admin — new booking | Empty, must select account/attendee manually (not from login/profile/subscription). |
| Admin — edit booking/order | Account of the edited booking/order, locked. |
| Customer — new/edit | Authenticated account, locked. |
Billing Difference review
When editing a confirmed booking (original status Approved/Attendance_Approved/Quote), confirmation is gated by the Billing Difference modal: the financial delta must be reviewed before the real confirm. Submit & Confirm Attendance never previews.
- UI order: Billing Difference preview → action confirmation popup (with Mute Notifications) → then submit/confirm. Closing the preview → booking is not confirmed.
- Edit submit is muted server-side (invariant
INV-BOOK-26) even when the client sendsmuteNotification=false; the final mute choice is collected at the confirm popup and sent with the confirm request. - Preview/confirm uses the complete
TermBookingOrderIdsreturned by submit (invariantINV-BOOK-21,INV-BOOK-25), not guessed from the selection UI. - Edit confirm builds a shared
BookingConfirmationPlanfrom the order ids before applying real side effects (invariantINV-BOOK-23); the replaced attendance keeps the old roll call when it was already signed (invariantINV-BOOK-27).
Submit & Confirm Attendance is not gated by Billing Difference (invariant INV-BOOK-26): confirming attendance creates no billing/invoice change, so no delta review is needed.
Next: Architecture & data.