Staff & Event Linking (PL-T194)
At a glance
Staff scheduling for chain venues with role-aware shifts, certification tracking with expiry validation (alcohol_license, first_aid), per-location assignments, ShiftTemplates that expand into concrete shifts via role quotas and time offsets, and bidirectional linking between shifts and bookings — including booking-cancellation auto-unlink with audit trail, cross-midnight shift support, and a drag-drop staff calendar in admin with SSE-live updates.
How it works
StaffMember is the per-tenant staff record — roles (guide, server, kitchen, bartender, manager, host), free-form skills (petanque_intro, dj, sommelier), certifications with expiry (alcohol_license, first_aid, food_hygiene), location assignments, hourly rate with currency, and active/inactive status. A duplicate-guard ensures one StaffMember per user per tenant. Shifts have start and end datetimes (cross-midnight supported), a conflict detector that 409s on overlap, certification validation (a bartender shift requires a valid alcohol_license), and location-access validation (you can't assign a staff member to a location they're not on).
The status flow runs scheduled → confirmed → in_progress → completed. Soft-delete on staff warns if active shifts exist. ShiftTemplate captures recurring patterns — role quotas ("a corporate event needs 2 guides + 1 host + 1 bartender") with time offsets ("start 30 min before booking, end 1 h after") and an applicable_event_types filter.
Apply-template expands the template into concrete shifts. Booking ↔ shift links are bidirectional: POST /chain/bookings/{id}/staff links a shift, GET lists, DELETE unlinks; applying a template to a booking auto-links every created shift. NEW: when a booking is cancelled, linked shifts are automatically unlinked with an audit-trail entry — operators don't have to chase orphan shifts.
The admin UI provides a staff calendar with drag-drop scheduling. SSE on a shift channel keeps the schedule live across managers' screens.
Key capabilities
- StaffMember with roles, skills, certifications (with expiry), location assignments
- Shift conflict detection + cross-midnight + certification validation
- ShiftTemplate with role quotas + time offsets + event-type filter
- Bidirectional booking ↔ shift links (link / unlink / list)
- Apply-template to booking auto-links all created shifts
- Booking cancellation auto-unlinks shifts with audit trail
- Drag-drop staff calendar + soft-delete with active-shift warning
In practice
A corporate client books out the venue for a Friday-night team event for 40 people. The operator clicks Apply Shift Template on the booking and picks the "Corporate event 30+" template — it requires 2 guides (start −30 min, end +60 min), 1 host (booking duration), 1 bartender (booking duration), and 1 kitchen runner (start −60 min, end +30 min). The system expands the template into five concrete shifts, validates each candidate against certifications and location access (the bartender slot rejects two candidates lacking alcohol_license), and writes the shifts linked to the booking.
The shift calendar updates live via SSE and each assigned staff member sees the new shift on their phone. Two weeks later the client cancels — the booking flips to CANCELLED and all five linked shifts are automatically unlinked with audit entries explaining why, freeing those staff slots for redeployment.
Features in this subsystem
27| ID | Status | Features |
|---|---|---|
| F22.12.01 | Shipped | StaffMember model (roles, skills, certifications, hourly rate) ✅ |
| F22.12.02 | Shipped | Roles: guide, server, kitchen, bartender, manager, host ✅ |
| F22.12.03 | Shipped | Free-form skills tagging (petanque_intro, dj, etc.) ✅ |
| F22.12.04 | Shipped | Certifications with expiry (alcohol_license, first_aid) ✅ |
| F22.12.05 | Shipped | Per-staff location assignments ✅ |
| F22.12.06 | Shipped | Hourly rate + currency tracking ✅ |
| F22.12.07 | Shipped | Active/inactive status ✅ |
| F22.12.08 | Shipped | Duplicate-guard: one StaffMember per user per tenant ✅ |
| F22.12.09 | Shipped | Shift model with start/end datetimes ✅ |
| F22.12.10 | Shipped | Shift conflict detection (409 on overlap) ✅ |
| F22.12.11 | Shipped | Cross-midnight shift support ✅ |
| F22.12.12 | Shipped | Certification expiry validation (bartender → alcohol_license) ✅ |
| F22.12.13 | Shipped | Location-access validation per shift ✅ |
| F22.12.14 | Shipped | Shift status flow (scheduled → confirmed → in_progress → completed) ✅ |
| F22.12.15 | Shipped | Soft-delete with active-shift warning on staff delete ✅ |
| F22.12.16 | Shipped | Staff schedule view (GET /chain/staff/{id}/shifts) ✅ |
| F22.12.17 | Shipped | SSE stream for real-time shift events ✅ |
| F22.12.18 | Shipped | ShiftTemplate with role quotas + time offsets ✅ |
| F22.12.19 | Shipped | Template apply → expand into concrete shifts ✅ |
| F22.12.20 | Shipped | Applicable event-type filter on templates ✅ |
| F22.12.21 | Shipped | Template active/inactive management ✅ |
| F22.12.22 | Shipped | Link shift to booking (POST /chain/bookings/{id}/staff) ✅ |
| F22.12.23 | Shipped | Unlink shift from booking ✅ |
| F22.12.24 | Shipped | List booking staff (GET /chain/bookings/{id}/staff) ✅ |
| F22.12.25 | Shipped | Apply template to booking (auto-link all created shifts) ✅ |
| F22.12.26 | Shipped | Booking cancellation → unlink shifts with audit trail ✅ — BookingService.cancel calls unlink_shifts_for_booking() and records unlinked_shifts_count in booking audit (PL-T211) |
| F22.12.27 | Shipped | Drag-drop on staff calendar (admin UI) ✅ |