Subscription — Common cases
"Customer booking reports a schedule clash"
The system intentionally blocks double-booking. On submit, it compares the schedule against the attendee's existing enrollments/bookings; on a clash → the whole request fails, no partial state is created.
- Editing one enrollment: the current EnrollmentId is excluded from the check (no self-conflict).
- Direct-confirm / future-confirm: uses the same guard → also blocked on a clash.
Spec:
subscription-booking-double-booking-guard+ the requirement "validates existing enrollment schedules before runtime state creation". For normal booking, see Booking → Common cases.
Real-world example: A child already has an After School enrollment on Wednesday; the parent books another package that clashes on the same Wednesday afternoon. → Avoid recording the child in two places at the same time. → The whole request fails at submit, and no partial enrollment is created.
"Manage Add-ons doesn't show the period I want"
The popup only shows periods from EnrollmentBillingSchedule (booked), and does not show unbooked periods of the subscription term. Submitting an unbooked period → rejected.
- Bulk "select all" only appears when a group has > 3 periods.
- Period rows come from the enrollment schedule, not the add-on price type.
Real-world example: Admin wants to add a meal add-on for Term 3, but the child has not booked Term 3. → Only attach add-ons to periods that were actually booked. → Term 3 does not appear in the popup; forced submit is rejected.
"What happens to the invoice when I Skip a period?"
Skip returns the invoice to Initialised (0) and cancels the active scheduler (if any). Depending on the invoice's prior state: the system regenerates the invoice or issues a credit note to keep finances consistent, and archives the related booking line.
Real-world example: A family goes on holiday and asks to skip a July period that already has an invoice. → Do not charge for a period the child will not use. → Skip → invoice
Initialised, credit note for the issued part, and the July booking line is archived.
"Create a BillingOnly package without a product"
BillingOnly (TypeId=1):
- Hides the Program section; does not block save/publish.
- Can be published without a
SubscriptionProduct. - Link term does not need a product planned check.
Whereas BookingAndBilling still requires a product to publish + a planned check when linking a term.
Note: the type is chosen before creation; once the subscription exists it is read-only.
Real-world example: A center charges a recurring facility fee that is not tied to any class session. → Create a billing-only package without configuring product. → Create a
BillingOnlypackage and publish it immediately withoutSubscriptionProduct.
"Can't archive a package"
Only unpublished packages (Unbookable) can be archived. A published package → the Current tab does not allow archive, and a direct request is rejected by the server. Archive/restore does not change publish status.
Real-world example: Admin wants to clean up "Term 1 2025" from the list while it is still bookable. → Avoid hiding a package customers can still book. → Unpublish first; if the package is still bookable, archive is hidden and the server rejects direct requests.
"Unlinking a booked term still shows in Billing but disappears from Booking"
This is by design. Unlinking a term that has booking usage → the system archives it (keeps IsActive = true, sets StatusId = 600) and does not delete the schedules.
- Booking UI: the archived term is no longer offered for new period selection.
- Subscription Manager & Subscription Billing Manager: still show the archived term + schedules to handle in-flight booking/billing.
- If you unlink a term without booking → normal soft delete (
IsActive = false), it disappears from all three surfaces.
A term with
IsActive = falseis soft deleted, not archived — it cannot be restored from the UI.
Real-world example: Admin removes Term 2 from a package, but Term 2 already has 30 active bookings. → Keep active booking/billing intact while stopping new sales. → The term is archived: hidden in Booking UI, still visible in Subscription/Billing Manager.
"Re-linking an old term asks to choose a product / creates duplicates"
Re-linking exactly the archived term (same subscription + term) will restore, not create new:
- Sets
StatusIdfrom600→500; skips the product-selection popup. - Keeps the existing
ProductIdsandSubscriptionTermBillingSchedule— no product loss. - Does not create duplicate
SubscriptionTerm/schedule records; reloading each surface shows one active set. - If there is no active archived row → it opens the product-selection popup and creates a new linked term.
Real-world example: Admin accidentally unlinks Term 2 (archived) and wants to link it again. → Restore the exact old product + schedule instead of creating duplicates. → Re-link changes
StatusId600 → 500, skips the product popup, and keepsProductIds.
"Booking Cut-Off rejects a fractional number"
The UI accepts whole days and stores minutes (CutOffMinute, 1 day = 1440). Entering 1.5 → rejected (no auto-rounding). 0/null = no cut-off → booking is not blocked.
Real-world example: Admin wants registration to close 1.5 days before and enters
1.5. → Cut-off uses whole days to avoid ambiguous minute thresholds. → The system rejects1.5; admin enters1or2(CutOffMinute= 1440/2880).
"Changing the Subscription Manager filter doesn't show new data"
By design: changing a filter does not auto-reload the table — click Apply to load per the current filter. Initial page load runs automatically; a successful mutation (confirm/cancel/ terminate/…) also refreshes. A stale pending request response does not override new data; if data jumps → check the "latest active request wins" logic.
Real-world example: Admin changes the Term to view another period, but the table still shows the old period. → Avoid expensive loads while merely adjusting the filter. → After changing, click
Apply; the table then reflects the selected Term.
"The booking period the customer picks doesn't match"
The UI must fetch periods from GET .../Subscriptions/{id}/BillingSchedules (canonical), and not compute them client-side. Each row has BookingStart/BookingEnd that take precedence over billing period start/end. An empty endpoint response → an "unavailable" state + blocks submit; on error → a retryable error state that blocks submit with the old selection.
Real-world example: A package bills monthly, but booking starts mid-month. → Let the customer choose the correct bookable range. → UI reads
BookingStart/BookingEndfromBillingSchedules; empty response shows "unavailable" and blocks submit.
"Rollover alert disappears after linking a term"
Intentional: the rollover alert is muted until the rollover feature is ready. The muting is notification-only — it does not change Subscription/Booking/Enrollment/Billing/Invoice/Term Planner/queue/worker state. Link-term success still completes + refreshes the linked term as before; error handling stays the same.
"Customer's permission to choose start/end"
Depends on the package settings (Booking Start Selection, Booking End Selection):
| Setting | Customer can choose? |
|---|---|
Staff Only (default, 0) | No |
Staff & Customer | Yes |
Staff can always choose both start and end.
Real-world example: A center wants customers to choose their own package start date. → Allow parents to pick the start instead of staff only. → Set
Booking Start Selection=Staff & Customer; customers can see and choose the start date.