Auburn MyGov Docs Gitea

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 LayerLocation · Calendar & Time · Distribution & Publishing
PlatformMicrosoft Platform · Workflow Engine
ModulePrograms & 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.

Current state. A reservation form prototype exists and is wired to the workflow engine, but targets two hardcoded spaces with hardcoded rules. It is not connected to FacilitySpace records or a configuration model. The design here supersedes it — the prototype is a proof of concept, not a starting point for the data model.

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.

Visibility

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
Occupancy Model

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
Availability Rules

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
Required Fields

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
Approval Flow

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
Payment Notice

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

Model 1

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.

Model 2

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.

Model 3

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.

Why one mailbox per space, not one per division. A per-division mailbox topology would require writing to multiple mailboxes for combination bookings, checking multiple mailboxes for availability, and staff monitoring multiple calendars per space. A single mailbox with division metadata in event tags keeps Exchange simple: staff see one calendar per space with clearly labeled entries, and availability is always one API call — a calendarView query rather than a simple free/busy check.
Connection to program registration. The divisions model closely mirrors program session selection — a citizen selects Session A, Session B, or both. A waitlist model, when built, should be designed to serve both reservations (division or occupancy-limited spaces) and program registrations (sessions at capacity) from the same underlying mechanism. This is deferred from v1 but the data model should not preclude it.

The Reservation Record

Field Type Notes
reservationIdguidPrimary key
facilitySpaceIdguidFK to FacilitySpace
divisionIdsguid[]Optional. Populated for division-model spaces only
statusenumpending | confirmed | cancelled | denied
startdatetimeTimezone-aware
enddatetimeTimezone-aware
requestedByobjectName, email, phone. Employee reservations also carry employeeId.
fieldValueskey-valueValues for the space's configured required fields (patron number, purpose, group size, etc.)
cancellationTokenstringSingle-use token included in confirmation email; used for citizen self-cancellation via email link
notesstringOptional. Staff notes added post-submission
workflowInstanceIdguidOptional. Reference to the running or completed workflow instance
calendarEntryIdguidOptional. MyGov calendar entry written on confirmation
exchangeEventIdstringOptional. Exchange event ID written to resource mailbox at submission — blocks the room immediately regardless of approval state
createdAtdatetimeSubmission timestamp
updatedAtdatetimeLast 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.

  1. Lead time The requested start must be at least leadTime minutes from now. Prevents last-minute bookings that staff cannot prepare for. Configured per space.
  2. 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.
  3. Padding No confirmed reservation may end within paddingMinutes before the requested start, and the requested end may not fall within paddingMinutes before another confirmed reservation's start. Gives staff time to prepare and reset the space between bookings.
  4. Occupancy / availability Sole-use: Exchange getSchedule call 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: Exchange calendarView query 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.

1

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.

2

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.

3

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.

Auto-approve path
  • 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
Staff approval path
  • 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
4

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.
Approval via Outlook — not v1. Staff approving or denying a pending reservation from Outlook (e.g. via an actionable email or a calendar event accept/decline) is desirable but requires careful design of the Exchange → MyGov sync path. V1 approval happens in the MyGov admin portal. Outlook surfaces confirmed reservations only.

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 calendarView can 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

FieldTypeNotes
facilitySpaceIdint FKLinks to organization.FacilitySpace
reservationDate / startTime / endTimedate + TimeSpanDate indexed; time stored as separate columns
purpose, organization?, contactName, email, phonestringAll required except organization
statusstringPending / Confirmed / Cancelled / Rejected
cancellationGuidGuid (unique)Cancellation URL token
exchangeEventIdstring?Graph API event ID for cancel-via-Exchange
deletedDateDateTime?Soft-delete; set on cancellation
barcode [NotMapped]string?Library patron barcode — used during submit only, not persisted

Feature status

FeatureStatusNotes
Submit reservation + field validationBUILTRequired fields, BookingRules, idempotency guard (5-min window)
Library barcode lookup (Polaris ILS)BUILTDragonstoneContext queries Polaris; validates expiration; pre-populates contact info
BookingRules validationBUILTBookingRulesValidator class; requiresLibraryCard confirmed; other rules need class audit
Exchange event creationBUILTIFacilityBookingService.CreateBookingAsync; ExchangeEventId stored
Cancellation via GUID email linkBUILTCancels Exchange event + soft-deletes reservation
Confirmation workflow triggerBUILTPosts FormResponse to "FacilitySpaceReservation" form; runs WorkflowActionRunner; configurable via admin UI
Admin list + status patchBUILTPaginated admin list; PATCH /admin/{id}/status for override
Per-space approval flow (Pending → Confirmed)BUILTController 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 modelNOT STARTEDNo divisionIds; maxConcurrentBookings in BookingRules is the only occupancy control
CalendarEvent for public calendarNOT STARTEDExchange event created; no CalendarEvent row; reservations invisible on citizen calendar
WaitlistNOT 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

TaskDescription
Collapse FacilitySpaceContact → FacilitySpace (two-step) Step 1 — migration A (additive): add to organization.FacilitySpacename, 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

TaskDescription
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

TaskDescription
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)

TaskDescription
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

TaskDescription
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

TaskDescription
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

TaskDescription
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

TaskDescription
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:
  • Status:New — citizen pending acknowledgement + staff notification
  • Status:Confirmed — citizen confirmation email with cancellation link
  • Status:Rejected — citizen denial email
  • Status:Cancelled — citizen cancellation confirmation
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:
  • Pending acknowledgement (Status:New) — tells citizen their request is under staff review
  • Confirmation (Status:Confirmed) — reservation details, date/time, space name, payment notice if any, and the cancellation link built from {{cancellationGuid}} and the Track B page URL
  • Denial (Status:Rejected) — informs citizen the request was not approved; should allow an optional staff-authored reason via a context field
  • Cancellation confirmation (Status:Cancelled) — confirms the reservation has been cancelled
The confirmation template is the most critical — without the correct cancellation URL it will contain a broken link. The Track B page URL must be hardcoded or injected as a context value before this template is used in production.
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)