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. If they 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. If a period was not booked for that enrollment → it does not appear, and submitting it is 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 returns the invoice to
Initialised, issues a credit note for the issued part, and archives the July booking line.
"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/bookable package → the Current tab does not allow archive, and a direct request is rejected by the server. Archive does not change publish status; restore keeps it unchanged too (no auto-publish).
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; it stores minutes (CutOffMinute, 1 day = 1440). Entering 1.5 → rejected (no auto-rounding). 0/null = no cut-off → booking is not blocked by this rule.
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).
"Switching Subscription Manager tabs jumps to stale data"
Already handled: switching tabs auto-reloads by the current filter; the old tab's response (a still-pending request) does not override the new tab's data. If data jumps → check the "latest active tab wins" logic.
"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. If the endpoint returns empty → show an "unavailable" state and block 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, not just billing dates. → 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.