Booking — Codebase flow
This page traces API → service → validation for normal booking. Key files:
- Controller:
Tux/src/Tux.Api/Controllers/Consumer/ConsumerBookingController.cs - Service:
Tux/src/Services/Tux.Service/BookingService.cs - Helper:
Tux/src/Services/Tux.Service/Helpers/BookingHelper.cs
Overall
Flow 1 — Create order (cart)
- API:
CreateBookingOrder. - Creates
TermBookingOrder+TermBookingOrderLine(statusCreated). - Validates: valid attendee/term product, org settings, measurement (height/weight) if enabled, double-booking (unless
IgnoreDoubleBooking). - Does not create Billing yet.
Flow 2 — Submit
- API:
SubmitBookingOrders→BookingService.SubmitBookingAsync. - Accepts only order types
BookingandTrialSession. - Creates
Billing(financial summary); validates Pay Later eligibility (viaBookingHelper). - May arm an auto-cancel timeout if the payment option requires one (PayNow/Deposit/Direct Debit).
- Final double-booking check. Status →
Submitted. Does not create the Invoice yet. - Returns the complete set of
TermBookingOrderIdsfor the later confirm (full or attendance), so the UI does not have to guess.
Flow 3 — Billing Difference preview
- Main entry:
PreviewBillingDifference→ConsumerBookingController→BillingService. - The backend validates canonical order IDs, permission, scope and billing context, then uses the shared
BookingConfirmationPlanand financial rules with Confirm. - Preview is read-only and returns
Difference, projected transaction data, breakdowns andPreviewSnapshotToken; it calls no writer orSaveChanges. - Confirm prepares the result again, checks the token inside the transaction, and persists; disabled or ineligible flows skip Preview/token according to policy.
For the file-by-file trace, conditions and error codes, see Preview Billing Diff — code flow.
Flow 3b — Booking Manager bulk Confirm
- UI:
BatchActionToolbargroups selected rows and callsBatchActionInQueue. - A selection containing only new bookings keeps the existing bulk
Confirm_Bookingqueue path. - A selection with an edit or mixed new/edit rows stops before the API call and requires each booking group to be confirmed through the preview-first flow.
- Backend
BookingService.BatchActionInQueueAsyncrechecksTermBookingId,editOrderIdlineage/action and returnsPREVIEW_BILLING_DIFF_BATCH_EDIT_NOT_SUPPORTEDbefore creating locks or schedulers. Confirm_Attendancedoes not go through the billing guard and continues to queue normally.
Real-world example: Admin selects a new booking and an edited booking together in Booking Manager. → Avoid queueing only part of the selection while skipping billing review. → The whole selection is rejected; the new booking is not locked, and the edited booking is confirmed separately through Preview.
Flow 4 — Confirm / Quote
- API:
ConfirmBookingOrders→BookingService.ConfirmBookingAsync(orGenerateQuotesAsync/AcceptQuoteAsync). - Creates
TermBookingfromTermBookingOrder; copies Line/Extra/Discount. - Applies discounts, computes totals (see Discount rule).
- Creates
TermAttendanceper session; triggers billing to generate invoices. - Status →
Approved(orAttendance_Approved, orQuote). - Action type
Confirm_Attendance(1050) → confirms attendance only, statusAttendance_Approved, no full confirm; used for adminSubmit & Confirm Attendance, does not open Billing Difference. - Edited booking: confirm builds a shared
BookingConfirmationPlanfromTermBookingOrderIdsbefore applying real side effects (invariantINV-BOOK-23).BookingConfirmationPlanis the projection source of truth from order → booking (header/line/extra/discount); preview code does not duplicate this projection algorithm.- Edit confirm recreates lines and replaces
TermAttendancebecause price/discount changed → keeps the old roll call (status, times, sign-in/out,RollCallHistory) when the old attendance hasRollCallStatusId > 0(invariantINV-BOOK-27); a retry does not duplicate history.
Subscription Enrichment flow in Booking
This is the runtime path for Subscription Booking, not the CreateBookingOrder path for normal booking:
ConsumerEnrollmentreceives selection-only Enrichment and stores the pattern by date.- Preview/submit/confirm resolve the same effective
TermProduct, closure day, and occurrence date. EnrollmentService.GenerateBookingFromEnrollmentcreates one priced/itemizedTermBookingLinefor each occurrence and creates the corresponding attendance.- Booking Billing/Reconciliation processes those lines;
SubscriptionBillingServiceonly processes the base schedule/add-on fromEnrollmentBillingSchedule. - Replay uses an operation/semantic-key guard; it does not create a duplicate active line or duplicate charge.
Real-world example: The confirm worker reruns the same operation after a timeout. → The resolver sees that the Booking + TermProduct + Date key already exists. → No second line/attendance is added and the Booking total does not increase.
Flow 5 — Cancel
- API:
DeleteBookingOrdersAsync→BookingService.CancelBookingAsync. - Status →
Canceled; reverses invoices via credit note; deactivatesTermAttendance(even if some lines previously leftApproved).
Main validation
| Type | Where |
|---|---|
| Capacity | TermProduct.Capacity, TermProgramSetCapacity; checked at create/submit/confirm. |
| Double-booking | Attendee × session overlap on date/time; bypass via IgnoreDoubleBooking. |
| Measurement | Height/weight vs course config; admin overrides each rule. |
| Lock & pending | Locked booking / conflicting pending order → edit blocked. |
| Discount eligibility | DiscountEligibilityService + BookingHelper.ApplyDiscount. |
Next: Discount rule.
Calendar and special-day flow
- The Calendar wrapper maps an event to a click handler that opens a read-only detail popup and cancels Syncfusion quick-info, editor, alert and
+N morepopups. The time formatter is shared with the tile. - Special-day matching groups by
sessionGroupKey, then matches product and time slot to avoid false merges. Tuke excludes special sessions from the booking surface because they are not bookable.
Real-world example: A previous popup is still open when another event is clicked. → Replace all details with the new event. → Do not keep the old attendee/time values; clicking an empty cell opens nothing.