Module
Reservations
Facility space booking — configuration model, occupancy types, availability rules, conflict detection, citizen and staff flows, and workflow engine integration.
| Status | Active |
| Last updated | — |
| Related |
Layer Location · Calendar & Time · Distribution & Publishing Platform Microsoft Platform · Workflow Engine Module Programs & Registration · Forms |
Overview
Reservations allow citizens and staff to book FacilitySpaces for a defined time window. The system is configuration-driven — a FacilitySpace carries a reservation configuration object that parameterizes the booking form, availability rules, and workflow path. There is one standard reservation workflow; configuration drives which branches it takes. There is one standard booking form; configuration drives which fields appear.
This design replaces the existing prototype (two hardcoded spaces,
no connection to FacilitySpace configuration) with a general,
maintainable system. Departments currently using external systems
(Parks, potentially Library) are not forced to migrate — some facilities
may never use the in-house system. A FacilitySpace (or Facility) that
carries an externalBookingUrl value is treated as externally
managed: the citizen-facing view shows a link to the external system
rather than a MyGov booking form, and no reservation data flows into
MyGov. There is no integration, no booking data, and no communication
obligation — MyGov is a directory entry pointing out.
FacilitySpace Reservation Configuration
Every FacilitySpace that participates in the reservation system carries
a reservationConfig JSON field. Spaces where this field is
null are not citizen-reservable — they may still exist as spatial references
for programs, employees, or collections. The config is set once by an admin
and drives all downstream behavior.
The config is a nullable JSON column on organization.FacilitySpace
(not a separate entity). The API owns the schema definition and validates on
write. Null = not reservable; presence = reservable with the embedded policy.
This replaces both the former isReservable flag and the former
FacilitySpaceReservationConfig separate-entity approach.
Who can see and book
- Public — visible and bookable by any citizen
- Employee-only — visible and bookable only to authenticated staff; hidden from public pages
- Hidden — not bookable; space exists for reference only
How concurrent bookings work
- Sole-use — one reservation at a time
- Occupancy-limited — up to N concurrent reservations
- Divisions — named bookable units; one or more can be selected per reservation
When booking is allowed
- Hours of availability (per day of week; inherits from Facility or overrides)
- Minimum lead time before start
- Maximum advance booking window
- Maximum reservation duration
- Padding between reservations
What the form asks for
- Configurable checklist — patron number, email, purpose, group size, etc.
- Each field can be optional or required
- Fields drive both form rendering and workflow action triggers
What happens on submission
- Auto-approve — confirm immediately if available
- Notify staff — confirm immediately, notify staff after
- Require approval — hold as pending until staff acts
Display only — no processing
- Free-text notice shown on the booking form (e.g. "A $50 deposit is required and will be collected at check-in")
- No payment processing in MyGov v1; external systems or in-person collection handle payment
Occupancy Models
Sole-use
One confirmed reservation at a time. The space is either available or it is not. No concurrent bookings, no divisions.
Conference room, meeting room, recording studio.
Occupancy-limited
Up to N confirmed reservations may overlap. Conflict detection counts confirmed reservations in the window and rejects new ones when the limit is reached. No named units — just a number.
Study room (seats 4), park pavilion (groups up to 3 at once), open computer lab.
Divisions
The space is split into named, independently-reservable units. A reservation targets one or more divisions atomically — either all selected divisions are confirmed or none are. A reservation for any division blocks other reservations from including that division.
Basketball court (Court A, Court B, or Full Court). A Full Court reservation blocks both A and B. Simultaneous Court A and Court B reservations are valid.
Divisions in detail
A FacilitySpace configured with the Divisions model carries a set
of Division records — each with a name, optional
description, and optional capacity. The booking form presents
available divisions as selectable options. A citizen selects one
or more divisions; the system books them as a single atomic reservation.
The parent FacilitySpace has one Exchange resource mailbox (the same as any other space — there is no per-division mailbox). All division bookings write to this single mailbox so staff have one calendar to monitor. Each Exchange event written for a division booking carries the selected division IDs in the event metadata (subject or extended properties), allowing the availability check to interpret which portions of the space are already committed.
Conflict detection for a division reservation uses Exchange as the
authority: query the space's resource mailbox calendarView
for the requested window, read division metadata off each existing event,
and check whether any booked division set intersects the requested
division set. A "Full Gym" request conflicts with any event that includes
Court A or Court B. A "Court B only" request does not conflict with an
event for Court A only.
calendarView
query rather than a simple free/busy check.
The Reservation Record
| Field | Type | Notes |
|---|---|---|
| reservationId | guid | Primary key |
| facilitySpaceId | guid | FK to FacilitySpace |
| divisionIds | guid[] | Optional. Populated for division-model spaces only |
| status | enum | pending | confirmed | cancelled | denied |
| start | datetime | Timezone-aware |
| end | datetime | Timezone-aware |
| requestedBy | object | Name, email, phone. Employee reservations also carry employeeId. |
| fieldValues | key-value | Values for the space's configured required fields (patron number, purpose, group size, etc.) |
| cancellationToken | string | Single-use token included in confirmation email; used for citizen self-cancellation via email link |
| notes | string | Optional. Staff notes added post-submission |
| workflowInstanceId | guid | Optional. Reference to the running or completed workflow instance |
| calendarEntryId | guid | Optional. MyGov calendar entry written on confirmation |
| exchangeEventId | string | Optional. Exchange event ID written to resource mailbox at submission — blocks the room immediately regardless of approval state |
| createdAt | datetime | Submission timestamp |
| updatedAt | datetime | Last status change |
Availability and Conflict Detection
Availability checks run at submission time for all flows. All four must pass before the booking is accepted. For staff-review flows, a re-check runs at approval time to catch any intervening conflict.
-
Lead time The requested start must be at least
leadTimeminutes from now. Prevents last-minute bookings that staff cannot prepare for. Configured per space. -
Hours of availability The requested start and end must fall within the space's configured availability hours for that day of week. If the space has no override, the parent Facility's hours apply. Reservations cannot span midnight.
-
Padding No confirmed reservation may end within
paddingMinutesbefore the requested start, and the requested end may not fall withinpaddingMinutesbefore another confirmed reservation's start. Gives staff time to prepare and reset the space between bookings. -
Occupancy / availability Sole-use: Exchange
getSchedulecall confirms the resource mailbox is free for the window.
Occupancy-limited: the count of confirmed reservations overlapping the window (from MyGov records) is less than the configured limit.
Divisions: ExchangecalendarViewquery for the window; division metadata on existing events is checked for intersection with the requested division set. Exchange is the authority; no MyGov reservation records are consulted for division conflict detection.
The availability calendar shown on the booking form reflects the outcome of these checks in real time — citizens see available windows, not raw calendar data. Confirmed reservations are shown as blocked; the public view never exposes who has booked a slot.
Citizen Booking Flow
The standard citizen flow. Staff booking v1 uses the same public form. Staff-specific features (MyGov admin portal approval, Outlook calendar management) are covered in the staff flow below.
Select space and time
Citizen browses to a Facility or FacilitySpace page. Available spaces are listed with their hours, capacity, payment notice, and a link to book. The availability calendar shows open windows based on the four conflict checks. For division spaces, available divisions are shown per time slot.
Complete the booking form
The reservation form is rendered by a custom booking component
rather than the standard form-presenter web component.
The component reads the space's reservationConfig
to determine which fields to show and handles conditional display
(e.g. patron number field only for library card-required spaces)
in its own logic. On submit it posts to the form response endpoint
with the backing "FacilitySpaceReservation" form ID — the same
endpoint the workflow engine reads. The backing form record carries
an empty elements value; the component owns the display.
Submit — workflow engine takes over
Form submission triggers the standard reservation workflow instance for this space. The workflow reads the space's config to determine which actions to run (field lookups, validations) and which approval path to follow.
- Run configured field actions (e.g. patron lookup)
- Write Exchange event (if mailbox configured) — blocks room
- Set status → confirmed
- Write MyGov calendar entry
- Send confirmation email with cancellation link
- Run configured field actions
- Write Exchange event (if mailbox configured) — blocks room immediately
- Set status → pending
- Send pending acknowledgement to citizen
- Notify staff (email and/or MyGov notification)
- Await staff action (approve or deny)
- On approve: re-check availability, write MyGov calendar entry, send citizen confirmation
- On deny: cancel Exchange event, send denial with optional reason
Confirmation email
Citizen receives an email with reservation details, space
info, date/time, any payment requirements, and a
cancellation link containing a single-use
cancellationToken. Following the link sets
status → cancelled, removes the calendar entry, removes
the Exchange event, and sends a cancellation confirmation.
The token expires with the reservation end time.
Staff Flow
Staff interact with reservations across two surfaces — the MyGov admin portal and Outlook. The design ensures staff can act from either without being forced to context-switch unnecessarily.
Admin portal
- Reservation list per space — filterable by status, date range, and division. Shows full reservation details including all collected field values (patron number, purpose, etc.)
- Approve / deny pending reservations — staff can add a note; denial sends a configurable reason to the citizen.
- Cancel a confirmed reservation — staff-initiated cancellation follows the same calendar/Exchange cleanup as citizen cancellation, with an optional staff-authored message to the citizen.
- Manual booking — v1 staff use the public form. A future staff-specific booking path in the admin portal (with pre-populated employee identity and no patron verification) is a logical v2 enhancement.
Outlook / Exchange
- Confirmed reservations appear in the resource mailbox calendar as events with full details — citizen name, contact info, collected field values. Staff who have added the mailbox to their Outlook see the booking without logging into MyGov.
- Cancel from Outlook — v1 gap: if staff cancel the Exchange event directly from Outlook, MyGov is not updated and the citizen receives no notification. The citizen's cancellation link remains valid but the slot may be re-booked. Staff should cancel in MyGov for any reservation where citizen communication matters. V2 will address this with a scheduled reconciliation ServerJob that checks confirmed reservations against their Exchange events and fires the cancellation workflow for any that are missing. See Microsoft Ecosystem.
- Communicate with citizen via Outlook — the Exchange event for a confirmed reservation carries the citizen's email. Staff can reply directly from Outlook. No MyGov mediation needed for ad-hoc communication.
- Add mailbox to Outlook — admin portal provides a one-click "Add to your Outlook" action per space, using Graph API to add the resource mailbox to the staff member's calendar list. See Microsoft Ecosystem.
Workflow Engine Integration
The workflow engine handles all post-submission logic. The reservation
form is the trigger; the workflow orchestrates field lookups,
availability re-checks, notifications, calendar writes, and Exchange
events. The standard reservation workflow is parameterized by the
space's ReservationConfig — it is one workflow with
branching, not one workflow per space.
How the approval flow works
The workflow engine's FormAction.Schedule field drives
the approval path without any custom async state. FormActions declare
which status transition triggers them:
- Status:New — fires at submission. Sends the pending acknowledgement to the citizen and the staff notification. Runs any field-lookup actions (e.g. patron barcode validation).
- Status:Confirmed — fires when staff (or auto-approve logic) transitions the reservation to Confirmed. Writes the MyGov calendar entry; sends the citizen confirmation email with the cancellation link.
- Status:Rejected — fires when staff deny the reservation. Sends the denial email to the citizen. Controller also cancels the Exchange event.
- Status:Cancelled — fires on citizen or staff cancellation. Sends the cancellation confirmation to the citizen.
Exchange events are written and cancelled by the controller directly, not by workflow actions. The workflow handles notifications and calendar entry writes only.
Notification actions
The following MSGraphHandler + MailHandler action configurations cover all notification paths. All are configurable via the admin workflow UI without code changes:
- Pending acknowledgement — MSGraph/SendTemplate to citizen confirming submission is under review (Status:New)
- Staff notification — MSGraph/SendTemplate or TeamsNotify to space owner (Status:New)
- Confirmation email — MSGraph/SendTemplate to citizen with reservation details and cancellation link (Status:Confirmed)
- Denial email — MSGraph/SendTemplate to citizen with optional staff-authored reason (Status:Rejected)
- Cancellation email — MSGraph/SendTemplate to citizen confirming the cancellation (Status:Cancelled)
Workflow starting point
The workflow engine supports a metadata search and copy mechanism that serves as the template/clone facility. Staff configure the "FacilitySpaceReservation" form's FormActions once for a given approval path, then clone that configuration for each new space. Space-specific values (staff email address, space name in templates, required field lookups) are the only things that need to change per space.
Calendar and Exchange Integration
On confirmation a reservation produces two artifacts, both managed by workflow actions:
- MyGov calendar entry — written to the FacilitySpace's (or Facility's) calendar. Citizen-visible payload only: space name, date/time, "Reserved" label. No identifying information. See Calendar & Time.
-
Exchange event — written to the FacilitySpace's
resource mailbox (if configured) via Graph API. Staff-visible payload:
full reservation details. For division reservations, the division
IDs are embedded in the event metadata (subject and/or extended
properties) so the mailbox's
calendarViewcan be interpreted for availability. There is one mailbox per physical space regardless of how many divisions it carries.
On cancellation (citizen or staff), both artifacts are removed.
The Exchange removal is a Graph API delete on the stored
exchangeEventId.
Mailbox Provisioning Limitation
While the platform's guiding principle is to fully automate processes and eliminate manual IT steps, Exchange resource mailboxes must currently be provisioned manually by IT.
This gap exists because the Microsoft Graph API does not support creating Exchange room mailboxes. Graph can manage existing room metadata (via the Places API) and read/write calendar events, but creating the underlying mailbox requires the Exchange Admin Center or PowerShell (New-Mailbox -Room). Because MyGov relies exclusively on the REST Graph API, it cannot automatically provision these mailboxes, and relies on an administrator to manually paste the provisioned email address into the ExchangeResourceMailbox field.
Add to Outlook Limitation
Another gap in the Microsoft Graph API is the inability to programmatically add a shared or resource calendar to a user's "My Calendars" list in Outlook. Graph API manages calendar data and permissions but cannot modify the Outlook client's UI preferences.
The best solution to this rough problem is the Exchange PowerShell command Add-MailboxPermission -AutoMapping $true. When IT grants permission using this flag, Exchange's AutoMapping feature automatically mounts the resource mailbox in the user's Outlook client without any action on their part. Since MyGov relies exclusively on the REST Graph API, it cannot execute this command. As a fallback, MyGov provides a UI button that launches an Outlook Web Access deep-link (https://outlook.office.com/calendar/view/month?sharedWindow=...) to prompt the user to add the calendar.
V1 Scope
| Feature | V1 | Notes |
|---|---|---|
| All three occupancy models | V1 | Sole-use, occupancy-limited, divisions |
| Availability rules (lead time, padding, hours, max duration, booking window) | V1 | All configured per space |
| Auto-approve and staff-approval workflow paths | V1 | Implemented via FormAction Schedule field — Status:New / Status:Confirmed / Status:Rejected transitions. No engine changes needed. |
| Citizen self-cancellation via email link | V1 | Single-use cancellationToken in confirmation email |
| Staff approve/deny/cancel in admin portal | V1 | |
| Exchange resource mailbox write at submission | V1 | Blocks the room immediately on submission regardless of approval state. Requires mailbox association on space. |
| External booking link (Parks / opt-out departments) | V1 | externalBookingUrl on FacilitySpace — citizen-facing view shows external link; no MyGov booking flow |
| Payment notice display | V1 | Display only; no processing |
| Booked/unavailable display (no waitlist) | V1 | Waitlist deferred; availability calendar shows blocked slots |
| Staff booking via public form | V1 | Staff-specific admin portal booking path is v2 |
| Approval via Outlook (Exchange → MyGov) | V2 | V1 approval is admin portal only |
| Exchange → MyGov cancellation sync | V2 | Scheduled ServerJob reconciles confirmed reservations against Exchange events; fires cancellation workflow for missing events. V1 gap: staff must cancel in MyGov to ensure citizen notification. |
| Waitlist | V2 | Shared mechanism with program registration waitlist |
| Staff booking path in admin portal | V2 |
Audit Findings
Codebase audited: api/Models/organization/FacilitySpaceReservation.cs, api/Controllers/facilities/FacilitySpaceReservationController.cs
The reservation system is substantially further along than the design doc implied. A working end-to-end flow exists — citizen submits, Exchange event created, confirmation workflow triggered, cancellation by GUID. Key gaps are approval flow and Divisions.
Current schema — FacilitySpaceReservation
| Field | Type | Notes |
|---|---|---|
| facilitySpaceId | int FK | Links to organization.FacilitySpace |
| reservationDate / startTime / endTime | date + TimeSpan | Date indexed; time stored as separate columns |
| purpose, organization?, contactName, email, phone | string | All required except organization |
| status | string | Pending / Confirmed / Cancelled / Rejected |
| cancellationGuid | Guid (unique) | Cancellation URL token |
| exchangeEventId | string? | Graph API event ID for cancel-via-Exchange |
| deletedDate | DateTime? | Soft-delete; set on cancellation |
| barcode [NotMapped] | string? | Library patron barcode — used during submit only, not persisted |
Feature status
| Feature | Status | Notes |
|---|---|---|
| Submit reservation + field validation | BUILT | Required fields, BookingRules, idempotency guard (5-min window) |
| Library barcode lookup (Polaris ILS) | BUILT | DragonstoneContext queries Polaris; validates expiration; pre-populates contact info |
| BookingRules validation | BUILT | BookingRulesValidator class; requiresLibraryCard confirmed; other rules need class audit |
| Exchange event creation | BUILT | IFacilityBookingService.CreateBookingAsync; ExchangeEventId stored |
| Cancellation via GUID email link | BUILT | Cancels Exchange event + soft-deletes reservation |
| Confirmation workflow trigger | BUILT | Posts FormResponse to "FacilitySpaceReservation" form; runs WorkflowActionRunner; configurable via admin UI |
| Admin list + status patch | BUILT | Paginated admin list; PATCH /admin/{id}/status for override |
| Per-space approval flow (Pending → Confirmed) | BUILT | Controller reads approvalFlow from reservationConfig; sets Status = Pending or Confirmed at submission. Exchange event written immediately regardless. PATCH /admin/{id}/status fires Status:Confirmed / Status:Rejected / Status:Cancelled FormActions and cancels Exchange event on rejection. |
| Divisions occupancy model | NOT STARTED | No divisionIds; maxConcurrentBookings in BookingRules is the only occupancy control |
| CalendarEvent for public calendar | NOT STARTED | Exchange event created; no CalendarEvent row; reservations invisible on citizen calendar |
| Waitlist | NOT STARTED |
Development Tasks
Organized by dependency order. Milestone 1 is blocking; later milestones can proceed as soon as the schema is stable. Admin portal (M4) and citizen booking component (M5) can run in parallel once M1–M2 are done.
Milestone 1 — Schema Foundation (blocks everything) ✓ Steps 1–2 complete — Step 3 (destructive migration) deferred, developer task
| Task | Description |
|---|---|
| Collapse FacilitySpaceContact → FacilitySpace (two-step) |
Step 1 — migration A (additive): add to
organization.FacilitySpace —
name, nameOverride,
description nvarchar(max), capacity int?,
spaceType varchar(20), geometryType varchar(20),
calendarId int? FK→data.Calendar,
reservationConfig nvarchar(max) (nullable JSON),
externalBookingUrl varchar(500)?.
Old columns and cms.FacilitySpaceContact table remain.
App code updated to read/write the new columns; old endpoints kept
alive in parallel during transition.Step 2 — SQL data script: copy name / description / capacity / BookingRules from each FacilitySpaceContact row into the corresponding
FacilitySpace row. Re-key
ProgramSession.FacilitySpaceContactId to point at
the matching organization.FacilitySpace ID
(collapsing the legacy FacilitySpaceId and
FacilitySpaceContactId columns into one).
Validate data before proceeding.Step 3 — migration B (destructive): drop cms.FacilitySpaceContact table, remove legacy
ProgramSession.FacilitySpaceContactId column,
remove ProgramSession.FacilitySpaceId legacy column
(now unified), remove FacilitySpaceContacts DbSet
and nav properties. Delete
FacilitySpaceContactsController.
|
| Fix FacilitySpace FK: FacilityId → FacilityContactId |
Drop FK constraint to organization.Facility, rename
column to FacilityContactId, add FK to
cms.FacilityContact. Data migration re-links existing
rows via the Facility→FacilityContact relationship. Update all
controller queries from s.FacilityId to
s.FacilityContactId. Unblocks correct space listing
from a CMS facility context.
|
| Add externalBookingUrl to Facility / FacilityContact |
Nullable externalBookingUrl varchar(500) on
cms.FacilityContact for a facility-level opt-out
(all spaces treated as externally managed). The same field is
added to FacilitySpace in the task above for space-level opt-out.
Citizen-facing views check this field and show an outbound link
instead of the booking form when set.
|
Milestone 2 — API Core (after M1; tasks parallelizable) ✓ Complete
| Task | Description |
|---|---|
| Rewrite BookingRulesValidator against reservationConfig |
The existing BookingRulesValidator reads from the old
SpaceContact.BookingRules shape. Rewrite it against
the reservationConfig JSON schema. Must cover: lead
time, max duration, booking window, operating hours (space override
or Exchange mailbox hours), padding before/after, and blackout
dates. Audit current class for any rules that already pass
correctly; carry them forward.
|
| Approval flow in FacilitySpaceReservationController |
Currently all submissions set Status = "Confirmed"
immediately. Add: if reservationConfig.approvalFlow ==
"requiresApproval", set Status = "Pending"
and fire only Status:New FormActions (pending
acknowledgement + staff notification). Exchange event is still
written at submission regardless of approval path.
PATCH /admin/{id}/status to Confirmed must fire
Status:Confirmed FormActions (confirmation email,
MyGov calendar entry); to Rejected must cancel the Exchange
event then fire Status:Rejected FormActions
(denial email).
|
| Division availability — calendarView query path |
Add GetCalendarViewAsync(mailbox, start, end) to
IFacilityBookingService — returns Exchange events
for the window with subject / extended property metadata. The
availability endpoint (GET /facility/{id}/spaces/{spaceId}/availability)
branches on reservationConfig.occupancyModel: if
"divisions", calls calendarView and returns a
division-aware response (which slots are free per division set);
otherwise uses the existing getSchedule free/busy
path. The booking component needs per-division slot availability
to populate the division selector.
|
| Geometry save endpoint |
PATCH /facility/{facilityContactId}/spaces/{spaceId}/geometry
accepting a GeoJSON polygon body. Validates the geometry falls
within the parent facility's envelope. Saves to
FacilitySpace.Location. Required by the floor plan
editor (M4) to persist geometry edits.
|
Milestone 3 — Exchange Config (independent of M2; unblocks mailbox settings UI and correct timezone display) ✓ Complete
| Task | Description |
|---|---|
| Mailbox settings proxy endpoints |
GET /facility/{facilityContactId}/spaces/{spaceId}/mailbox-settings
and PATCH variant. Proxies to
GET/PATCH https://graph.microsoft.com/v1.0/users/{resourceMailbox}/mailboxSettings
using the existing ITokenService credential pattern.
The PATCH translates reservationConfig.operatingHours
(per-day HH:mm–HH:mm strings) to Graph's
workingHours format. Note: Graph expresses a single
startTime/endTime across all configured days — per-day variation
is a limitation to document. The timeZone field in
the GET response is the canonical timezone for the booking
component's display. Endpoints are designed; pending implementation.
|
Milestone 4 — Admin Portal (after M1+M2; M3 needed for mailbox settings UI; parallel with M5) ✓ Complete — floor plan editor deferred (V2)
| Task | Description |
|---|---|
| FacilityEditor — unified space form | Update the space create/edit form to the collapsed FacilitySpace schema. Single form, single API call — no two-step SpaceContact creation. Fields: name, nameOverride, description, capacity, spaceType, geometryType, exchangeResourceMailbox, externalBookingUrl. |
| reservationConfig structured editor | Embedded within the space editor. Renders the JSON config as a real form: approval flow selector, occupancy model selector (sole-use / occupancy-limited / divisions), operating hours day-of-week time pickers, lead time / max duration / padding number inputs, library card checkbox, payment notice textarea, division list (add/remove named divisions with name and capacity). Serializes to JSON on save; parses on load. "Sync to Exchange" button calls the mailbox-settings PATCH endpoint (M3). |
| Floor plan editor component |
Promote the static/temp/svg-editor.html demo to a
proper admin component in my/. Load SVG via
GET /facility/{id}/floor-map?floor={floor} and
envelope via GET /facility/{id}/floor-map/{floor}/envelope.
Pan/zoom navigation, edit mode with drag handles on space vertices,
save via geometry PATCH endpoint (M2). Adding a new room creates
a default polygon, creates a FacilitySpace record, and opens the
space editor inline. Selected space highlights and shows the
metadata panel.
|
| Reservation admin panel |
List view from GET /facility-space-reservation/admin
with filters for facility, space, date range, and status.
Approve action → PATCH to Confirmed (triggers confirmation email
via approval flow task). Deny action with optional reason text →
PATCH to Rejected (triggers Exchange cancel + denial email).
Staff cancel → triggers Exchange cleanup and cancellation email.
Status badge colors: Pending = amber, Confirmed = green,
Cancelled = gray, Rejected = red.
|
| Mailbox settings editor | Admin UI to configure operating hours and timezone per space. Reads from and writes to the mailbox-settings proxy endpoints (M3). Surfaced within the FacilityEditor as a "Exchange Calendar" tab. Includes a "Sync operating hours to Exchange" button that PATCHes the mailbox workingHours. |
Milestone 5 — Citizen Booking Component (needs M1+M2; M3 for timezone; parallel with M4) ✓ Complete — facility-space-booking web component; published to Gitea npm registry
| Task | Description |
|---|---|
| Space selection view |
Citizen lands on a facility or space page. Component fetches
spaces for the facility. If externalBookingUrl is
set on a space or its facility, render an outbound link card —
no booking form. Otherwise render the floor plan via
GET /facility/{id}/floor-map/?selectedId={spaceId}
(highlights selected room) with fallback to a list/card layout
for spaces without geometry. Clicking a space loads its info
panel: name, description, capacity, payment notice from
reservationConfig.
|
| Availability calendar |
Date picker triggers availability fetch. For sole-use spaces:
renders 48 30-minute slots within operating hours from the
getSchedule availabilityView string. For division
spaces: renders per-division availability from the calendarView
response (M2), showing which divisions are free per slot.
Slot click populates start time; end time defaults to minimum
duration or +60 min. Timezone sourced from the Exchange mailbox
settings response (M3), not a hardcoded offset.
|
| Reservation form |
Custom booking web component — not form-presenter.
Fields rendered from reservationConfig.requiredFields.
Library card / patron number field appears only when
requiresLibraryCard: true. Division selector appears
for division-model spaces. Payment notice displayed prominently
when set. On submit: POST to /facility-space-reservation.
Handles two outcomes — Confirmed shows confirmation
view with cancellation link; Pending shows pending
acknowledgement explaining staff review.
|
| Cancellation flow |
Citizen follows the cancellationToken link from
their confirmation email. GET call loads the reservation details
for review; DELETE call cancels. Handles already-cancelled state
gracefully. Token becomes invalid past the reservation end time —
expired link shows an appropriate message without an error page.
|
Milestone 6 — First Live Booking (after M1–M5; code + configuration + data) ⚠ Substantially complete — two gaps remain (see Track A and Track C)
Everything required to accept a real citizen booking end-to-end. Split into three tracks that can run in parallel once M5 is deployed.
Track A — Floor Plan & Visual Editor ⚠ Mostly complete — one gap
| Task | Description |
|---|---|
| Admin portal floor plan editor |
BUILT FacilityFloorPlanPage.js — full ribbon toolbar (select / move / vertex tools), pan/zoom, undo/redo, floor switcher, delete space. Geometry save calls PATCH /facility/{id}/spaces/{spaceId}/geometry (implemented in FacilitySpaceController). Floor plan and envelope loaded from GET /facility/{id}/floor-map/ and GET /facility/{id}/floor-map/{floor}/envelope.
Gap — new space inline creation. When "Add Space" is used, the editor draws a default polygon and switches to vertex mode so the shape can be positioned, but saving is blocked with a toast: "Create the space in Facility Detail first, then assign geometry here." The design called for creating the FacilitySpace record automatically and opening its editor inline. Currently staff must create the space in Facility Detail, then return to the floor plan editor to draw its polygon. This is a workflow friction point, not a blocking gap — existing spaces can be fully edited and saved.
|
| SVG space visual in booking component |
BUILT facility-space-booking fetches and renders the SVG floor plan alongside the space list. Tapping a space polygon selects it. Falls back to list-only layout when no geometry exists.
|
Track B — Component Deployment ✓ Complete
| Task | Description |
|---|---|
Publish facility-space-booking to npm registry |
DONE Published to Gitea npm registry at git.auburnal.gov/api/packages/InfoTech/npm/. |
| Embed component on public page | DONE Component embedded on CMS page. cancel-token wired from ?token={guid} URL parameter via inline script: document.querySelector('facility-space-booking')?.setAttribute('cancel-token', new URLSearchParams(location.search).get('token')). This page URL is what the confirmation email template must reference for its cancellation link. |
Track C — Workflow & Space Configuration (admin tasks) ⚠ Mostly complete — email templates gap
| Task | Description |
|---|---|
Create "FacilitySpaceReservation" form |
DONE Form record exists. FormResponseId is set on the reservation at first submission and reused for all subsequent status-change triggers. |
| Create FormActions for each schedule value |
DONE FormActions configured for all four status transitions:
|
| Create Htmlemail templates |
INCOMPLETE
Gap — a single test template is standing in for all four actions.
All four FormActions currently point at the same placeholder Htmlemail template. Before live bookings go out, four distinct templates are needed:
|
| Configure first live space | DONE At least one FacilitySpace configured with ReservationConfig JSON and ExchangeResourceMailbox. Space is marked IsPublic = true. |
Deferred — V2
- Exchange → MyGov cancellation sync ServerJob (reconciles confirmed reservations against Exchange events; fires cancellation workflow for missing events)
- Staff booking path in admin portal (pre-populated employee identity, no patron verification)
- Approval / denial from Outlook (Exchange → MyGov sync path)
- Polaris ILS → dedicated PolarisHandler (existing raw SQL path works; migrate to HTTP-based handler mirroring CityworksHandler)
- Waitlist (shared mechanism with Programs module)
- Division combination shortcut labels ("Full Court" as a named preset)