Master Data — Architecture & data
Dependency flow
Master Data is widely read, but mutations must go through the right owner. Example: updating an account holder's avatar = updating Contact, not adding an avatar field on Account.
Boundary rules
| Rule | Meaning |
|---|---|
| BU is a security/config boundary | Token/payment/customer/account queries must scope by BU where business requires it. |
| Account Holder is Contact | "Account holder avatar" belongs to Contact.ProfileImage, not Account. |
| Program setup differs from booking runtime | Editing TPS/TermProduct does not auto-edit confirmed bookings when there is no clear migration/adjustment flow. |
| Image crop policy per field | Avatar 1:1; wide course/activity image 16:9; generic file uploads are exempt. |
| Site/program links affect availability | Booking/payment/subscription read this config to expose options, they do not own the config. |
| Subscription Config only for SuperAdmin BU sys-admin | Subscription Config tab + API limited to sys-admin in a BU TypeId = 99; gate uses already-loaded context, no extra BU query. |
_Enabled gates but never deletes | Requirement customization hides a dimension on every surface but keeps saved attendee/Product values; _Required applies only when _Enabled. |
Site-category selection = StatusId | OrgProgramCategory.StatusId Approved (500) = selected, Initialised (0) = unselected; IsActive stays true for the canonical row. |
| Staff create auto-links site | Creating staff with RoleId = Staff auto-creates active OrgEmployee for every active Org in the same BU (idempotent); Admin role and edits do not auto-link. |
| Archived staff excluded from surfaces | Employee StatusId = Archived does not appear in link lists, Weekly Planner, or shift selector even with IsActive = true. |
| TermProduct inherits Product | Sign In/Out Method and requirements copy from Product to TermProduct when creating a course; no manual selection in the New Course drawer. |
| ProgramCapacity reads from TPS capacity | Program-level capacity comes from TermProgramSetCapacity per TermProgram.Date; no record → unlimited (ProgramRemainingCount = 999). |
Main data
| Group | Data |
|---|---|
| Organization | Enterprise, BusinessUnit, Org, site relationship, custom URL. |
| Customer | Account, BusinessUnitAccount, Contact, account holder relationship. |
| Attendee | Attendee, measurements (Height/Weight), requirement values (SchoolYear/Level/SwimmingCompetency), profile/system image. |
| Subscription Config | Attendee_Subscription, Attendee_ProductSet, SubscriptionExtra/ProductSet scope. |
| Customization | BU-level Reference_Enabled/_Text/_Required/_Options; CustomConsent1..3, measurement customization, Enterprise Setting units. |
| Program | Term, ProgramCategory, Program, TermProgramSet, TermProduct, TermProgramSetCapacity. |
| Links/settings | OrgProgramCategory, site-program category link, booking rule options, category payment option. |
| Staff links | Employee, OrgEmployee, EmployeePermissionTemplate, TrackingCode (colorCode). |
Program structure — hierarchy and capacity
- Runtime hierarchy:
ProgramCategory(TypeId)→Program→TermProgramSet→TermProduct; booking resolves the session payload viaTermProductId. Programis a template;Program.Capacityhas been removed. Program-level capacity reads fromTermProgramSetCapacity(TermProgramSetId+Date); no record for a date → unlimited (ProgramRemainingCount = 999), booking not blocked.- Slot-level capacity is enforced at
TermProduct; counters live onTermProductBookingCount(BookingCount/ReservedCount). TermProgramSetstarts withStatus=Approved,PublishStatus=Unbookable. Delete is blocked when bookings/orders are active; shrinking the date range is blocked if a booking line falls outside.
Real-world example: Admin sets capacity 20 for the
TermProgramSetTerm 2 but leaves2026-06-15blank. → No unfair booking blocks for an unconfigured date. → That dateProgramCapacity= null →ProgramRemainingCount = 999, booking still passes.
Requirement summary and special type
- The course requirement summary shows a row only for dimensions that have a value. Formats:
{min} - {max}/{min} and above/Up to {max}; age suffixyears old; height/weight append the unit label from Enterprise Setting. - The summary shows only dimensions with
<Reference>_Enabled = true, labels from_Text; hidden dimensions do not create empty rows. - Term Planner allows editing requirement/price metadata (manual overrides are kept; only overwritten when the user overwrites/resets); the customer-facing summary is read-only.
Product.SwimmingCompetencyis a single value (nullablenvarchar(50)), copied toTermProduct. Null = dimension not configured; a value removed from options is kept and still matches the attendee.- Term Care
SpecialTypeId(1 = Special, 0 = Default) round-trips through Program setup, propagates to activeTermProgramSetin the same BU. Customer Portal separates special Term Care from regular.
Real-world example: Admin sets
Product "Swim Level 2"with requirementMinAge 5,MaxAge 7, Swimming CompetencyCompetent, labelWater Confidence. → Booking card shows the right needs. → Summary shows5 - 7 years oldandWater Confidence: Competent;TermProductcarriesCompetent; class card (Class/Subscription) has a weekday tag, Holiday/Term Care does not.
Event side effects
Many master data changes can publish EntityEvent so the Platform writes history/notifications or refreshes projections. Events do not move ownership; the owner stays the original aggregate.