Scheduling App
🎯 Application Overview
This document demonstrates how to implement a comprehensive appointment scheduling system using the Flexbase framework, featuring domain models, workflow states, resource availability management, appointment booking, and enterprise-grade role-based access control. This system is designed to be universally applicable across different business scenarios including healthcare, education, consulting, fitness, professional services, and inventory-backed offerings.
📋 Business Requirements
Core Entities:
Offering - Catalog items offered (Services, Products, Rentals, Subscriptions)
Resource - Fulfillment resource (People, Assets/Equipment, Facilities, Inventory Pools)
AvailabilitySchedule - Recurring/single schedules created by resources
TimeSlot - Individual time slots available for booking
Appointment - Actual booking by customers
Customer - Users who book appointments (Patients, Students, Customers, Attendees)
StaffMember - Admin/staff who can manage schedules on behalf of resources
Notification - Appointment reminders and notifications
Workflow Requirements:
Resource Availability Management - Daily, Weekly, Single, Recurring schedules
Time Slot Generation - Automatic or manual slot creation based on schedule
Appointment Booking - User-initiated or staff-initiated bookings
Appointment Management - Confirmation, rescheduling, cancellation
Staff Override - Admin/staff can create schedules on behalf of resources
Automatic Notifications - Reminders before appointments
Availability Blocking - Block time for breaks, meetings, leave
Role-Based Access Control:
Resource - Create/manage own availability, view own appointments
Customer/User - View available slots, book appointments, manage own bookings
Receptionist - Manage appointments, send notifications
Admin/Staff - Create schedules on behalf of resources, manage all appointments
Billing/Finance - View payment and billing information
FacilityManager - Manage resources and overall facility schedules
Admin - Full system access
🎯 Business Scenario Adaptations
This scheduling system is designed to be universally applicable. Here are example adaptations:
Healthcare
Doctor, Therapist, Dentist
Patient
Consultation, Checkup, Surgery
Daily clinic hours
Education
Teacher, Instructor, Tutor
Student
Class, Tutoring Session, Workshop
Weekly class schedule
Consulting
Consultant, Coach, Advisor
Customer
Consultation, Strategy Session
By appointment
Fitness
Trainer, Instructor
Member
Personal Training, Group Class
Class schedule
Legal Services
Lawyer, Attorney
Customer
Legal Consultation, Document Review
Office hours
IT Support
Technician, Support Engineer
User
Technical Support, System Setup
Support hours
Salon Services
Stylist, Beautician
Customer
Haircut, Massage, Spa Service
Service schedule
🧱 Core Scheduling Modules (In Depth)
1) Offerings Module
OfferingsController Features
CreateOffering (POST) →
Offering.CreateOffering()UpdateOffering (PUT) →
Offering.UpdateOffering()PublishOffering (POST) →
Offering.Publish()DeactivateOffering (POST) →
Offering.Deactivate()ArchiveOffering (POST) →
Offering.Archive()GetOffering(s) (GET)
Domain Object: Offering
Workflow:
OfferingWorkflowStateStates: Draft → Published → Deactivated → Archived
RBAC:
Admin/Staff: Create, Update, Publish, Deactivate, Archive
Resource: View
Customer: View published
2) Resources Module
ResourcesController Features
RegisterResource (POST) →
Resource.RegisterResource()UpdateProfile (PUT)
Activate (POST) →
Resource.Activate()Deactivate (POST) →
Resource.Deactivate()PutOnLeave (POST) →
Resource.PutOnLeave()ReturnFromLeave (POST) →
Resource.ReturnFromLeave()
Domain Object: Resource
Workflow:
ResourceWorkflowStateStates: Registered → Active → OnLeave → Inactive
RBAC:
Resource: Update own profile
Staff/Admin: Activate/Deactivate/OnLeave
3) AvailabilitySchedules Module
AvailabilitySchedulesController Features
CreateSchedule (POST) →
AvailabilitySchedule.CreateSchedule()UpdateSchedule (PUT)
PublishSchedule (POST) →
AvailabilitySchedule.Publish()PauseSchedule (POST) →
AvailabilitySchedule.Pause()ArchiveSchedule (POST) →
AvailabilitySchedule.Archive()GenerateTimeSlots (POST) →
AvailabilitySchedule.GenerateTimeSlots()
Domain Object: AvailabilitySchedule
Types: Single, Daily, Weekly, BiWeekly, Monthly, Custom Recurring
Fields: Date range, daily window, days of week, slot duration, buffer
Workflow:
AvailabilityScheduleWorkflowState(Draft → Published → Paused → Archived)RBAC: Resource (own), Staff/Admin (all)
4) TimeSlots Module
TimeSlotsController Features
GenerateFromSchedule (POST) →
AvailabilitySchedule.GenerateTimeSlots()BlockSlot (POST) →
TimeSlot.Block()ReleaseSlot (POST) →
TimeSlot.Release()GetAvailableSlots (GET)
Domain Object: TimeSlot
Workflow:
TimeSlotWorkflowState(Available → Blocked → Reserved → Completed/Released)RBAC: Resource/Staff can Block/Release, Customer can reserve
5) Bookings (Appointments) Module
BookingsController Features
CreateBooking (POST) →
Appointment.CreateBooking()ConfirmBooking (POST) →
Appointment.Confirm()RescheduleBooking (POST) →
Appointment.Reschedule()CancelBooking (POST) →
Appointment.Cancel()CompleteBooking (POST) →
Appointment.Complete()
Domain Object: Appointment
Workflow:
AppointmentWorkflowState(Requested → Confirmed → Rescheduled → Cancelled/Completed)Links:
ResourceId,OfferingId,TimeSlotId,CustomerUserAccountId(or AccountableEntityId per your model)Validations: resource/time conflicts via
CalendarIndexoverlaps; capacity vsConcurrentLimitfor the slot
Booking flow (end-to-end):
Discover: client queries available
TimeSlots for aResource/Offering.Validate: server checks
CalendarIndexfor overlaps and capacity.Reserve: create
Appointmentand a matchingCalendarIndexAppointment row.Confirm: optional state move; reminders/notifications scheduled.
Reschedule: create new slot mapping and update corresponding
CalendarIndexrows (old row deleted, new row inserted).Cancel/Complete: update appointment state and delete/mark
CalendarIndexAppointment row accordingly; restore capacity.
📅 Schedule Calendar (Timezone-Aware, Resource-Centric)
Purpose: Present a resource’s working calendar (availability + appointments) in the viewer’s timezone, with fast navigation (day/week/month) and conflict-safe validations.
Data sources:
TimeSlot(Available/Blocked/Reserved/Completed)Appointment(Requested/Confirmed/Rescheduled/Cancelled/Completed)Projected into
CalendarEntryandCalendarIndexwithStartUtc,EndUtc,TimeZoneId
API pattern (accepts viewTz):
Rendering rules:
Availability entries:
EntryType = "Availability"(fromTimeSlot)Appointments:
EntryType = "Appointment", includeAppointmentIdBlocked: show as non-bookable
Respect DST by always converting
StartUtc/EndUtctoviewTzat response time
Validation endpoint:
6) Appointment Requests Module
Purpose: Allow customers (or anonymous users) to request an appointment without picking an exact slot. Requests can be targeted to a specific Resource or left open. Resources (or staff) review, select a suitable slot, and convert the request into a Booking.
RequestsController Features
CreateRequest (POST) →
AppointmentRequest.Create()(supports authenticated customer or anonymous)AssignResource (POST) →
AppointmentRequest.AssignResource()(staff/resource)ProposeSlot (POST) →
AppointmentRequest.ProposeSlot()(staff/resource proposes candidate TimeSlot)AcceptProposal (POST) →
AppointmentRequest.AcceptProposal()(customer confirms)ConvertToBooking (POST) →
AppointmentRequest.ConvertToBooking()(createsAppointmentand CalendarIndex row)CancelRequest (POST) →
AppointmentRequest.Cancel()
Domain Object: AppointmentRequest
Workflow:
AppointmentRequestWorkflowState(Submitted → Assigned → Proposed → Converted | Cancelled)Fields: optional
ResourceId,OfferingId, desired date/time window, notes, requester identity or anonymous contact infoVisibility: anonymous requests are visible to all resources of the Offering; targeted requests only to the selected Resource
List endpoints to fetch:
Open requests (no
ResourceId) per Offering for staff/resources.Targeted requests by
ResourceId.Customer’s own requests (if authenticated).
Conversion notes:
Use
CalendarIndexoverlap/capacity validation when converting to booking.On conversion, create
Appointmentand correspondingCalendarIndexAppointment row, then mark request as Converted.
📊 Summary Calendar (Aggregated, Timezone-Aware)
Purpose: Executive/operations view with daily/weekly counts and utilization across resources and assets, rendered in a chosen timezone.
New projection table:
Aggregation job (daily/hourly):
Read
CalendarIndexin UTCFor each resource and day in
summaryTz, convert intervals to local and accumulate buckets:Availability, Booked (appointments), Blocked, etc.
Upsert by deterministic
Key
API examples:
Why separate summary:
Keeps operational queries fast and cheap (pre-aggregated)
Stable across DST boundaries by anchoring per-day in the summary timezone
Clean separation:
CalendarIndex→ operational events;CalendarSummaryIndex→ aggregated KPI view
This brings the scheduling calendar and summary calendar back to the center of the application, both correctly timezone-aware and optimized for their respective use cases.
📚 Operational CalendarIndex (Expanded Slots for Fast Validation)
Purpose: Store one row per concrete availability slot and appointment interval to make conflict detection, capacity checks, and rendering fast and simple—even for recurring schedules.
Why expand recurring schedules:
Simplifies overlap queries to range checks over a flat table.
Avoids complex on-the-fly recurrence math at request time.
Enables indexing by Resource, day, and time for fast lookups.
Recommended schema:
Population strategy:
On schedule Publish/Pause/Archive and on GenerateTimeSlots, expand recurrence into concrete
TimeSlotrows (as you already do), then project those intoCalendarIndexwith Kind = Availability.On Appointment create/update/cancel, write/update
CalendarIndexrows with Kind = Appointment andIsExclusive = trueunless the Offering is concurrent.For blocks/maintenance, upsert Kind = Block with
IsExclusive = true.
Linking rules:
When a
TimeSlotis created from a schedule, setTimeSlot.ScheduleId = schedule.Idand setCalendarIndex.AvailabilityScheduleId = schedule.IdandTimeSlot.CalendarIndexId = CalendarIndex.Idfor that slot.When an
Appointmentis created on a slot, create aCalendarIndexrow (Kind = Appointment) and setCalendarIndex.TimeSlotId = slot.Idand, if known,CalendarIndex.AvailabilityScheduleId = slot.ScheduleId.
Horizon policy (to control volume):
Expand and maintain a rolling horizon, e.g., [today − 7 days, today + 365 days].
Nightly job extends the future horizon; cleanup job prunes beyond the past window.
Validation using CalendarIndex:
Notes:
Timezone: always store Start/End in UTC for math; include
TimeZoneIdso localDatecan be computed deterministically.Capacity:
ConcurrentLimitenables pooled resources (e.g., 5 seats, 10 devices).Idempotency: use deterministic
Keyfor upserts from schedule/appointment events.
This table complements CalendarSummaryIndex: the former enables fast operational validation and rendering; the latter offers pre-aggregated analytics.
🧾 Accounts and Accountability Integration (Composition, Roles via Accountability)
To align with your real design and the pattern used in the recruitment document, we model roles through the Accountability table. There is a single Account root; roles are not a separate RoleMembership table but Accountability edges with AccountabilityType representing the role or relationship.
Key concepts
Account (root): identity for a person or organization.
UserAccount (facet): profile for a human; optional login via ApplicationUser.
OrgAccount (facet): profile for an organization.
Accountability: edges between Accounts with
CommissionerAccountId,ResponsibleAccountId,AccountabilityType, effectivity dates.AccountabilityType: includes both role semantics and relationship semantics, e.g.,
Resource,Customer,StaffMember,WorksFor,BooksOnBehalf, etc.
Canonical tables
How scheduling entities reference Accounts
Replace role-specific FKs with generic
AccountIdfields; roles are validated viaAccountabilityTypeat runtime.
Updated entities (fields only)
Validation examples (composition with Accountability)
Calendar unification
CalendarIndex.ResourceType = "Account"andResourceId = Account.Idsupports calendars for people and orgs uniformly.Assets can remain separate (
ResourceType = "Asset") or be modeled as Accounts if you want single-resource abstraction across the platform.
Migration note
Where the document previously used
ServiceProviderId/ClientId, adoptResponsibleAccountId/CommissionerAccountIdand validate role via AccountabilityType (Resource/Customer).Where
TimeSlothadServiceProviderId, adoptResourceAccountId.
EF mapping deltas (facets)
This keeps UserAccount intact and models roles/relationships via Accountability (composition over inheritance), matching the recruitment design while preserving a single, elegant FK (AccountId) throughout scheduling, calendars, and assets.
▶️ Refinement: UserAccount as Party Root, ApplicationUser as Login (Per Your Design)
To match your clarified model: UserAccount is the universal party for all actors (login or not). ApplicationUser represents the login identity and links 1:1 (optionally) to UserAccount. Roles are expressed via Accountability with AccountabilityType.
Updated canonical tables
Entity references (use UserAccountId everywhere)
Validation (roles via AccountabilityType)
Calendar alignment
Use
CalendarIndex.ResourceType = "UserAccount"andResourceId = UserAccount.Idfor people/org calendars.Assets remain
ResourceType = "Asset"(or can be unified later if desired).
EF mappings (key facets)
This keeps UserAccount as the single FK used across scheduling (slots, appointments, schedules, calendars), while ApplicationUser remains the optional login identity. Roles and on‑behalf authority come from Accountability rows (AccountabilityType).
▶️ Correction: AccountableEntity Root with TPH (UserAccount, OrgAccount)
Per your model, both UserAccount and OrgAccount derive from a common AccountableEntity. We’ll use TPH (table-per-hierarchy) with a discriminator and reference AccountableEntityId everywhere. ApplicationUser links (optionally) to a UserAccount row via the same key.
Canonical model (TPH)
EF Core mapping
Accountability using AccountableEntityId
Entity references (replace UserAccountId with AccountableEntityId)
Validation with AccountabilityType
Calendar alignment
CalendarIndex.ResourceType = "AccountableEntity",ResourceId = AccountableEntity.IdWorks for both people (UserAccount) and organizations (OrgAccount) uniformly; assets remain separate unless unified later
This update matches your hierarchy precisely: UserAccount and OrgAccount are concrete subtypes of a single AccountableEntity root; all foreign keys and accountabilities use AccountableEntityId consistently.
Last updated