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:

Business Scenario
Resource
Customer
Offering
Availability

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: OfferingWorkflowState

  • States: 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: ResourceWorkflowState

  • States: 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 CalendarIndex overlaps; capacity vs ConcurrentLimit for the slot

Booking flow (end-to-end):

  • Discover: client queries available TimeSlots for a Resource/Offering.

  • Validate: server checks CalendarIndex for overlaps and capacity.

  • Reserve: create Appointment and a matching CalendarIndex Appointment row.

  • Confirm: optional state move; reminders/notifications scheduled.

  • Reschedule: create new slot mapping and update corresponding CalendarIndex rows (old row deleted, new row inserted).

  • Cancel/Complete: update appointment state and delete/mark CalendarIndex Appointment 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 CalendarEntry and CalendarIndex with StartUtc, EndUtc, TimeZoneId

API pattern (accepts viewTz):

Rendering rules:

  • Availability entries: EntryType = "Availability" (from TimeSlot)

  • Appointments: EntryType = "Appointment", include AppointmentId

  • Blocked: show as non-bookable

  • Respect DST by always converting StartUtc/EndUtc to viewTz at 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() (creates Appointment and 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 info

  • Visibility: 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 CalendarIndex overlap/capacity validation when converting to booking.

  • On conversion, create Appointment and corresponding CalendarIndex Appointment 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 CalendarIndex in UTC

  • For 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 TimeSlot rows (as you already do), then project those into CalendarIndex with Kind = Availability.

  • On Appointment create/update/cancel, write/update CalendarIndex rows with Kind = Appointment and IsExclusive = true unless the Offering is concurrent.

  • For blocks/maintenance, upsert Kind = Block with IsExclusive = true.

Linking rules:

  • When a TimeSlot is created from a schedule, set TimeSlot.ScheduleId = schedule.Id and set CalendarIndex.AvailabilityScheduleId = schedule.Id and TimeSlot.CalendarIndexId = CalendarIndex.Id for that slot.

  • When an Appointment is created on a slot, create a CalendarIndex row (Kind = Appointment) and set CalendarIndex.TimeSlotId = slot.Id and, 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 TimeZoneId so local Date can be computed deterministically.

  • Capacity: ConcurrentLimit enables pooled resources (e.g., 5 seats, 10 devices).

  • Idempotency: use deterministic Key for 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 AccountId fields; roles are validated via AccountabilityType at runtime.

Updated entities (fields only)

Validation examples (composition with Accountability)

Calendar unification

  • CalendarIndex.ResourceType = "Account" and ResourceId = Account.Id supports 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, adopt ResponsibleAccountId/CommissionerAccountId and validate role via AccountabilityType (Resource/Customer).

  • Where TimeSlot had ServiceProviderId, adopt ResourceAccountId.

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" and ResourceId = UserAccount.Id for 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.Id

  • Works 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