Subscription — Architecture & data
This page sketches the main data "objects" and their relationships — conceptual view, not exact schema.
Relationship diagram (conceptual)
Two BillingSchedule levels
Manage Add-ons/Discounts only uses
EnrollmentBillingSchedule(booked periods). Invariants:INV-SUB-ADDON-SCHEDULE-001,INV-SUB-DISCOUNT-SCHEDULE-001.
Enrichment flow and ownership boundary
- Setup only exposes options with an active/approved Product, a
TermProductin the correct Term/Program scope, a valid rate, and a valid accounting item. - Preview, submit, and confirmed generation use the same occurrence resolver; the client sends only identity (
subscriptionProductId,weeklyPattern, orTermProductId + Datewhen managing). TermBookingLineowns the Enrichment charge; do not write it toEnrollmentExtra,SubscriptionTermBillingSchedule, or the Subscription Billing invoice.TermBookingId + TermProductId + Dateis the semantic key; replay does not create a duplicate active line.
Real-world example: An Enrichment Product loses its RateStandardItemId after Setup. → Preview/submit do not guess an amount. → The system fails closed before creating an Enrollment, Booking, or Invoice.
Ownership boundary
Important to avoid breaking consistency:
| Block | Who may change it |
|---|---|
| Subscription (template, price, product, content) | UI Setup |
| Enrollment / TermBookingOrder / TermBooking / Billing / Invoice / CreditNote (runtime) | Only existing booking/enrollment/confirmation services |
UI Setup / add-on popup does NOT directly approve/skip/schedule/regenerate invoice. Every billing recalculation goes through the backend service that owns that behavior.
Main APIs (read/write paths)
| Purpose | API |
|---|---|
| Billing schedule for booking | GET /api/ConsumerEnrollment/Subscriptions/{id}/BillingSchedules |
| Subscription metadata (price, product, linked term) | GET /api/ConsumerEnrollment/Subscriptions/{id} |
| Submit enrollment | POST .../ConsumerEnrollment/Submit |
| Options for Subscription Manager filter | GET /api/Subscription (admin, includes unpublished) |
| Options for Billing Manager filter | GET /Subscription/WithTerms (not /Subscription/List) |
| Booking options (customer) | GET /api/ConsumerEnrollment/Subscriptions |
| Enrichment pricing preview | POST /api/ConsumerEnrollment/EnrichmentPricingPreview (authenticated, read-only) |
| Manage Enrichments | GET/POST /api/SubscriptionManager/Enrollment/Enrichments/Manage (admin scope) |
The Manager filter uses
GET /api/Subscription(admin, sees unpublished packages too); the Billing Manager uses/Subscription/WithTerms(does not readPeriods); customer booking usesConsumerEnrollment(bookable packages only). The three paths do not mix.
Units & data conventions to remember
| Field | Convention |
|---|---|
Subscription.CutOffMinute | Stored in minutes; UI displays whole days (1 day = 1440). Decimal is rejected. |
Subscription.TypeId | null/0 = BookingAndBilling; 1 = BillingOnly. |
| Booking Start/End Selection | 0 = Staff Only (default); otherwise Staff & Customer. |
| Billing Type | Hard-locked to Flat Rate (Dynamic not ready). |
Term & related booking
- Term = academic period. Subscription links to term through
SubscriptionTerm(fieldLinked). - "Normal" booking (
TermBookingOrder/TermBooking) is a different flow from subscription but intersects for schedule-clash validation — see Booking and Common cases.
Public-safe vs private
Customer-facing APIs (Course List, Step 1) only expose public-safe data: title, price info, weekday availability, description, site map metadata, closure-day. They do not expose private booking/enrollment/account/attendee/invoice/payment data. Every display path is read-only (does not mutate state).
Next: Codebase flow.
Runtime capabilities in the current implementation
| Capability | Runtime contract |
|---|---|
| Extend | Same-Term adjacent future schedule; separate period; original booking/invoices unchanged. |
| Rollover | Existing runtime to active linked approved target Term; no new Subscription or finance write. |
| Duplicate | Setup-only copy; same BU/site scope; Unpublished/Unbookable. |
| Flyer | PDF/image manifest with size/count limits; omitted/empty/non-empty Save semantics. |
| Auto billing | One scheduler job for earliest unbilled period; cancel on unpublish/Manual; no backfill. |
| Invoice split | Account/Attendee/Subscription grouping with attendee identity kept on lines. |
Real-world example: An Auto scheduler retries after a failed period. → The same period remains the stop point; it does not create a second active job or backfill later periods.