fix: enforce lifecycle gate on staff shift self-withdrawal #291

Merged
owlburtoe merged 1 commit from fix/shift-withdrawal-lifecycle-gate into main 2026-06-03 20:47:35 -04:00
Owner

Summary

  • Staff self-withdrawal from the Shift Signup page is now blocked once the schedule's signup window closes. Allowed phases: open, open_self_signup, open_supplemental, open_call, and published with an active supplemental window. Scheduler/admin unassigns are unaffected.
  • isStaffWithdrawable() helper added to backend schedule-lifecycle.ts; lifecycle check added to the unassign handler before the engine is invoked.
  • scheduleStatus + canWithdraw threaded through EnrichedShiftMySignedUpEntry via useMySchedule / useShiftSignupCalendar / buildShiftSignupCalendarView. SignedUpShiftAccordionItem renders a "signup window has closed" message instead of the Withdraw button when canWithdraw is false.
  • Published schedules with an active supplemental window now correctly surface in the signed-up list (previously hidden by the blanket isPublished filter) with canWithdraw: true.

Test plan

  • Run backend integration tests with Docker: pnpm test:backend:db — 8 new cases covering all lifecycle states, published+supplemental active/expired, scheduler bypass
  • Run frontend unit tests: pnpm vitest run src/lib/shiftSignup/ — 3 new cases covering visibility and canWithdraw computation
  • Smoke: open a schedule in open_self_signup, sign up for a shift, verify Withdraw button shows
  • Smoke: advance schedule to admin_adjustment, revisit Shift Signup → verify button replaced by "signup window has closed" message
  • Smoke: open supplemental window on a published schedule, sign up, verify Withdraw available; let window expire, verify it disappears
## Summary - Staff self-withdrawal from the Shift Signup page is now blocked once the schedule's signup window closes. Allowed phases: `open`, `open_self_signup`, `open_supplemental`, `open_call`, and `published` with an active supplemental window. Scheduler/admin unassigns are unaffected. - `isStaffWithdrawable()` helper added to backend `schedule-lifecycle.ts`; lifecycle check added to the `unassign` handler before the engine is invoked. - `scheduleStatus` + `canWithdraw` threaded through `EnrichedShift` → `MySignedUpEntry` via `useMySchedule` / `useShiftSignupCalendar` / `buildShiftSignupCalendarView`. `SignedUpShiftAccordionItem` renders a "signup window has closed" message instead of the Withdraw button when `canWithdraw` is false. - Published schedules with an active supplemental window now correctly surface in the signed-up list (previously hidden by the blanket `isPublished` filter) with `canWithdraw: true`. ## Test plan - [ ] Run backend integration tests with Docker: `pnpm test:backend:db` — 8 new cases covering all lifecycle states, published+supplemental active/expired, scheduler bypass - [ ] Run frontend unit tests: `pnpm vitest run src/lib/shiftSignup/` — 3 new cases covering visibility and `canWithdraw` computation - [ ] Smoke: open a schedule in `open_self_signup`, sign up for a shift, verify Withdraw button shows - [ ] Smoke: advance schedule to `admin_adjustment`, revisit Shift Signup → verify button replaced by "signup window has closed" message - [ ] Smoke: open supplemental window on a published schedule, sign up, verify Withdraw available; let window expire, verify it disappears
fix: enforce lifecycle gate on staff shift self-withdrawal
Some checks failed
Code Scanning / Gitleaks secret scan (pull_request) Successful in 6s
Code Scanning / Semgrep OSS source scan (pull_request) Successful in 34s
Security, Type Check & Runtime / Dependency Audit (pull_request) Failing after 5m3s
Security, Type Check & Runtime / Migration Guardrails (pull_request) Successful in 9m38s
Security, Type Check & Runtime / Backend Runtime Smoke (pull_request) Successful in 10m15s
Security, Type Check & Runtime / Type Check (pull_request) Successful in 10m20s
Release Artifacts / Validate release candidate (pull_request) Successful in 10m53s
Release Artifacts / Build and push Docker release images (pull_request) Has been skipped
Release Artifacts / Deploy to staging (pull_request) Has been skipped
E2E Tests / e2e (pull_request) Successful in 14m31s
a0d04d2229
Staff can now only withdraw from shifts they signed up for while the
relevant signup window is still open. Once the schedule transitions out
of the window phase (e.g. to admin_adjustment), the unassign endpoint
returns 409 for staff actors and the Shift Signup UI replaces the
Withdraw button with an informational message.

Allowed withdrawal states: open, open_self_signup, open_supplemental,
open_call, and published schedules with an active supplemental window.
Scheduler/admin unassigns bypass the lifecycle check entirely.

Backend: isStaffWithdrawable() helper in schedule-lifecycle.ts;
lifecycle check added to the unassign handler in scheduleController.ts.

Frontend: scheduleStatus + canWithdraw threaded through EnrichedShift →
MySignedUpEntry via useMySchedule/useShiftSignupCalendar/
buildShiftSignupCalendarView. Published-supplemental assignments are
now included in the signed-up list (previously hidden by the
isPublished filter) and correctly show canWithdraw=true while the
window is open. SignedUpShiftAccordionItem renders the message when
canWithdraw is false.

Tests: 8 new backend integration tests (blocks in admin_adjustment,
allows in open_self_signup/open_supplemental/open_call, published+
active/expired supplemental, scheduler unblocked); 3 new frontend
unit tests (published shift visibility with active supplemental,
canWithdraw computation across all lifecycle states).
owlburtoe force-pushed fix/shift-withdrawal-lifecycle-gate from a0d04d2229
Some checks failed
Code Scanning / Gitleaks secret scan (pull_request) Successful in 6s
Code Scanning / Semgrep OSS source scan (pull_request) Successful in 34s
Security, Type Check & Runtime / Dependency Audit (pull_request) Failing after 5m3s
Security, Type Check & Runtime / Migration Guardrails (pull_request) Successful in 9m38s
Security, Type Check & Runtime / Backend Runtime Smoke (pull_request) Successful in 10m15s
Security, Type Check & Runtime / Type Check (pull_request) Successful in 10m20s
Release Artifacts / Validate release candidate (pull_request) Successful in 10m53s
Release Artifacts / Build and push Docker release images (pull_request) Has been skipped
Release Artifacts / Deploy to staging (pull_request) Has been skipped
E2E Tests / e2e (pull_request) Successful in 14m31s
to 6581848a55
All checks were successful
Code Scanning / Gitleaks secret scan (pull_request) Successful in 18s
Code Scanning / Semgrep OSS source scan (pull_request) Successful in 51s
Security, Type Check & Runtime / Migration Guardrails (pull_request) Successful in 9m49s
Security, Type Check & Runtime / Dependency Audit (pull_request) Successful in 9m54s
Security, Type Check & Runtime / Backend Runtime Smoke (pull_request) Successful in 10m30s
Security, Type Check & Runtime / Type Check (pull_request) Successful in 11m8s
Release Artifacts / Validate release candidate (pull_request) Successful in 11m12s
E2E Tests / e2e (pull_request) Successful in 15m18s
Release Artifacts / Build and push Docker release images (pull_request) Has been skipped
Release Artifacts / Deploy to staging (pull_request) Has been skipped
2026-06-03 20:28:01 -04:00
Compare
owlburtoe deleted branch fix/shift-withdrawal-lifecycle-gate 2026-06-03 20:47:36 -04:00
Sign in to join this conversation.
No description provided.