40227-vm/backend/docs/zone-checkin.md

4.3 KiB

Daily Zone Check-in

Workstream 16 — staff log a daily self-regulation "zone" (Zones of Regulation: blue/green/yellow/red). History is retained per day, and an eligible user who has not checked in today is nudged on the dashboard, the /zones-of-regulation page, and in the notification dropdown.

"Today" is server-computed in the campus timezone

The check-in date is not decided by the client. Each campus carries a required IANA timezone (campuses.timezone); the service computes the campus-local date (localDateInTimezone, native Intl, DST-correct). Users without a campus use UTC for the daily key, so organization- and school-scope staff can still complete the same daily workflow.

Storage (reuses user_progress)

No new table. A check-in is a user_progress row with progress_type = zone_checkin and item_id = the campus-local date (YYYY-MM-DD), value = the zone color. Because the per-user upsert key is (userId, progress_type, item_id), this yields one row per user per day, and the set of rows is the history (item_id sorts chronologically). The thin services/zone-checkin.ts wraps UserProgressService and owns the timezone/date logic, keeping the generic user_progress endpoint generic.

Routes (/api/zone_checkins)

Personal routes require explicit ZONE_CHECKIN; globalAccess alone does not imply this workflow permission. The scoped completion route requires READ_ZONE_CHECKIN_REPORTS.

  • GET /today{ date, zone, isCheckedInToday } (campus-local date).
  • POST / → record today's zone. Body { data: { zone } } (blue|green|yellow|red). Parent-scope users acting through a drilled child scope receive 403 Forbidden instead of creating reportable rows in the child scope.
  • DELETE /today → clear today's check-in.
  • GET /?from=&to= → the caller's history ({ rows: [{ date, zone }], count }).
  • GET /completion → scoped staff report for leaders with READ_ZONE_CHECKIN_REPORTS; returns { summary, rows } for today's server computed dates per staff member.

Authorization

  • ZONE_CHECKIN is seeded for staff roles that must complete the daily workflow, including organization, school, campus, and class scope staff (owner, superintendent, principal, registrar, director, office_manager, teacher, and support_staff). Users can receive or lose it only through effective permissions (custom_permissions / custom_permissions_filter). The frontend and backend both gate this workflow by permission, not by role name. Reads/writes are scoped to the caller's own userId by UserProgressService.
  • READ_ZONE_CHECKIN_REPORTS is seeded for scope leaders and report readers. The completion report applies the caller's effective organization/school/campus/class scope and includes pending rows for staff without today's check-in.
  • Check-ins persist only when the active scope is the user's own scope. Parent users drilled into a child school/campus/classroom do not see the personal check-in UI there, and the backend does not create reportable user_progress rows for that child scope.
  • Users with no campus use UTC for "today"; campus users use their campus timezone.

Tests

  • Unit (npm test): shared/constants/timezone.test.ts (campus-local date incl. Phoenix no-DST + a DST zone; isValidIanaTimezone) and shared/constants/zone-checkin.test.ts (isZoneCheckinColor).
  • Frontend unit (vitest): business/zone-checkin/selectors.test.ts (eligibility + nudge), top-bar notification builder (incl. the zones href), profile unified result rows, and director-dashboard unified result/risk rows.
  • Seeded e2e (frontend/tests/e2e/zone-checkins.seeded.e2e.ts, npm run test:e2e:content): a campus-staff record/read-back/clear of today's zone, invalid-zone rejection, and external-role lockout.

Content

Regulation zone cards and page copy are organization-preset content catalog records (regulation-zones and zones-of-regulation-page-content). New organizations receive those records from the content-catalog seeder/backfill; the daily check-in result history remains in user_progress.

Existing deployments receive report grants through 20260619070000-grant-zone-checkin-report-permission.ts and the registrar personal workflow grant through 20260619071000-grant-registrar-zone-checkin-permission.ts; both migrations are idempotent.