Eleanor Whitfield — Kearney, closed
A full three-event arc: viewing, funeral service, cremation disposition, all in the past. Good for showing what a completed case's calendar history looks like.
Four slices, one branch, one table modified. Everything the Step 3.5 minimum-viable calendar UI needs to read, create, edit, cancel, and conflict-check events already exists in the API. This page is a map of what's there so the frontend build can reach for it directly.
All four slices of the Step 3.5 Calendar Backend plan landed on main via PR #106 (merge commit dade462) — including the optional Slice 4 stretch work. It will go out with the next deploy to Railway's shared mvp environment.
That means: full read/list with filtering, manual create, manual edit, soft-cancel, and a pre-flight conflict check are all backend-complete. What's missing is entirely on the frontend side — there is no calendar grid, no event form, no conflict-warning UI. That's issue #92, and it's the actual remaining work for the July demo.
Case-scoped bookings, non-case-scoped blocks (maintenance, training), and workflow-created events are all the same scheduled_events row. Category is now an explicit, database-enforced column — not something the frontend has to infer from whether case_id is null.
| Field | Type | What it's for |
|---|---|---|
category | enum new | case_scoped / non_case_scoped. A DB check constraint enforces the pairing with case_id — a case-scoped row can't have a null case, a non-case-scoped row can't have one set. This can't drift. |
event_type | enum | 11 existing values plus new graveside_service and reception. setup / cleanup are still there deliberately — their removal is deferred pending the Megan and Jeanette walkthroughs. |
status | enum | tentative, confirmed, plus new in_progress and completed, and cancelled. |
starts_at / ends_at | timestamp | Validated: end must be strictly after start. |
buffer_before_mins / buffer_after_mins | integer (default 30/30) | Turnaround time around the event. Metadata on the row, not a separate event — conflict detection reads these directly. |
workflow_module_instance_id | uuid, FK | Set when a workflow module created the event; null for manual events. This is how the frontend can tell "the engine booked this" from "a director booked this by hand." (ADR-028.) |
created_by_id / updated_by_id / cancelled_by_id | uuid, FK new | On-row forensic columns for query-friendly UI display ("cancelled by Sophie at 3:40pm") without joining into the audit trail. Null for seed-created rows. |
cancelled_at / cancellation_reason | timestamp / text new | Populated on soft-cancel. The row is never deleted. |
title, notes, procedure, expected_attendees, tasks | mixed | Display and prep-list content — unchanged from before this plan. |
One controller, six actions. All under /api/v1/scheduled_events.
| Endpoint | Does |
|---|---|
GET /scheduled_events |
List, filterable by case_id, event_type, status, starts_after / starts_before, resource_id, and new branch_id (joins through resource assignments to resources.branch_id — this is what the branch switcher filters on). Resources and director are now included on this default response, not just on show — a week view can render without a second round-trip per event. |
GET /scheduled_events/:id |
Single event, detail view. |
POST /scheduled_events |
Create. Takes case_id (optional), event_type, starts_at/ends_at, title/notes/director/buffers, and resource_ids[]. Category is derived automatically from whether case_id is present — the frontend never sends it. |
PATCH /scheduled_events/:id |
new Edit any field. Pass resource_ids[] to wholesale-replace resource assignments, or omit it to leave them untouched. case_id can't be changed through this endpoint — an event can't be moved between cases. |
DELETE /scheduled_events/:id |
new Soft-cancel. Sets status to cancelled, stamps who/when, accepts an optional cancellation_reason. The row stays — nothing is destroyed, resource claims are simply freed. |
GET /scheduled_events/conflicts |
Pre-flight check. Pass a proposed starts_at/ends_at and a set of resource_ids[]; get back the events that would conflict. See below. |
A resource isn't just busy during the event — it's busy during the event plus its turnaround buffers. The conflicts endpoint checks the effective window, not the raw one.
Even though the two events' raw start/end times don't overlap, the proposed booking starts inside the existing event's after buffer — the crew hasn't turned the room over yet. Scheduling::DetectConflicts catches this by construction; it's not new logic, but the /conflicts endpoint (already routed) is what makes it callable from a create/edit form before the frontend commits a booking.
This is a yes/no check against resources you name. It is not the Automatic Scheduling engine — there's no slot suggestion, no "next available time," no path-to-yes explanation, no priority-aware ordering across competing bookings. That's a separate PRD and a separate future build. What ships here is exactly enough for a form to say "that room's not free" before the director hits save.
Nothing extra for the frontend to call. Create, edit, and cancel all write their own trail automatically, at two levels.
audited gem)scheduled_events write — unchanged by this plan.scheduled_event_created, scheduled_event_modified, scheduled_event_cancelled — case-scoped events only.The seed narrative was rewritten to route every event through the same Scheduling::CreateEvent call the API uses — so what you see after rails db:seed is exactly what a director would produce by hand. It's worth walking through before the demo, because two of these are deliberate.
A full three-event arc: viewing, funeral service, cremation disposition, all in the past. Good for showing what a completed case's calendar history looks like.
Thornton's tentative memorial is seeded to overlap Liu's family viewing in Chapel A, on purpose (it's flagged in the event's own notes). This is the one to demo against the /conflicts endpoint — it's a live, unresolved double-booking sitting in the data right now.
A three-event chain: chapel service, then a reception (Chapel B, repurposed) immediately after, then a graveside committal the following week. This is the one to show if you want graveside_service and reception on screen.
Arrangement, viewing, chapel service, all in Victoria Chapel. Good for demoing the branch filter — switch branches and the calendar should show only this arc.
Category non_case_scoped, type blocked, no director. This is the one to show if you want to demonstrate that not everything on the calendar belongs to a case.
That branch doesn't have seeded resources or staff yet, so no events were authored on it — surfaced deliberately rather than faked. If the demo needs that branch populated, it's a seed-data ask, not a backend one.
Short version: nothing on the backend blocks the demo. Everything below the line is frontend — issue #92, Matt and Zareef's call on how much of it makes the cut.
setup/cleanup type removal