144 lines
8.2 KiB
Markdown
144 lines
8.2 KiB
Markdown
# Policy Documents & Acknowledgments
|
|
|
|
Workstream 11 — persistence of staff acknowledgment of policy/safety documents.
|
|
|
|
## Purpose
|
|
|
|
Campus staff must acknowledge two categories of documents — **Safety Protocols**
|
|
(official/government) and **Handbook & Policies** (internal). `director` and
|
|
`office_manager` author the documents; users with explicit `ACK_POLICY`
|
|
acknowledge them. Acknowledgment is
|
|
**per document version**: editing a document bumps its `version`, which requires
|
|
re-acknowledgment.
|
|
|
|
## Frontend wiring
|
|
|
|
The two existing pages consume this single store (the old generated `documents`
|
|
entity it replaced has been removed):
|
|
|
|
- **Handbook & Policies** (`business/policies`) lists `policy_documents` of
|
|
`category = handbook_policy`, mapping the handbook's sub-category to/from `tag`
|
|
(`toPolicyViewModel` / `toPolicyDocumentMutationDto`). Management affordances
|
|
are gated by effective policy-document permissions. Acknowledgment is **persisted** via `policy_acknowledgments`
|
|
(`usePolicyAcknowledgments` / `useAcknowledgePolicy`), replacing the former
|
|
local-state set.
|
|
- **Safety Protocols** (`business/safety-protocols`) consumes
|
|
`category = safety_protocol`, rendering author-filled `steps` + autism
|
|
considerations with a static per-`tag` card icon (fire/shield/heart). It shares
|
|
the persistent acknowledgment hooks. Both pages are seeded from
|
|
`20260611050000-policy-documents-seed.ts` (safety protocols reuse the former
|
|
content-catalog `safetyProtocols` payload; a few handbook policies seed the
|
|
handbook page). The Safety Protocols page also has a manager-gated
|
|
**structured-authoring** flow (mirrors the F.R.A.M.E. module: header
|
|
*New Protocol* button → create form, per-card *Edit*/*Delete*) with
|
|
**dynamic** `steps` + `autismConsiderations` rows that add/remove
|
|
independently, so each protocol carries its own count
|
|
(`useSafetyProtocolsModule` + `SafetyProtocolForm` /
|
|
`SafetyDynamicListEditor`; gated by effective policy-document permissions).
|
|
Title/body/steps/considerations changes bump `version` and
|
|
require re-acknowledgment.
|
|
- **Leadership dashboard acknowledgments** (`business/director-dashboard`,
|
|
`components/director-dashboard/DirectorAcknowledgmentTrackingPanel`) render the
|
|
manager report from `GET /api/policy_acknowledgments/report`. Documents are
|
|
grouped by category, each current-version row can expand to show staff who
|
|
have not acknowledged it, and unresolved rows also feed dashboard risk areas.
|
|
- **Profile document checklist** (`business/profile`, `pages/ProfilePage`)
|
|
renders the current user's visible current-version safety protocols and
|
|
handbook policies with acknowledged / not acknowledged status from
|
|
`GET /api/policy_acknowledgments`. Missing acknowledgments are sorted first,
|
|
the status appears as a badge beside the document title, and the
|
|
`Acknowledged on` column is reserved for the timestamp only.
|
|
|
|
## Entities
|
|
|
|
- `policy_documents` (generic-CRUD entity): `title`, `body`, `category`
|
|
(`safety_protocol` | `handbook_policy` — selects the page), `tag` (nullable
|
|
finer **sub-category**; the Handbook page maps its
|
|
Operations/Behavior/Safety/Communication/Legal categorisation onto it, and the
|
|
Safety page uses it to pick the static card icon), `author` (display name of
|
|
the **creating user**, set server-side at creation and not changed on update),
|
|
`steps` + `autism_considerations` (JSONB string arrays — **author-filled
|
|
structured content** for safety protocols; null for handbook policies),
|
|
`version` (bumped when `title`/`body`/`steps`/`autism_considerations` change),
|
|
`active`, tenant `organizationId` + nullable `campusId`. This is the **single
|
|
unified store** for both the Safety Protocols and Handbook & Policies pages
|
|
(filter by `?category=` and optionally `?tag=`). The category **list** + icons
|
|
are static frontend config; each document's category assignment (`tag`) is DB
|
|
data. `author` is derived from the current user's name —
|
|
`${name_prefix} firstName lastName` (the honorific title from `users.name_prefix`,
|
|
e.g. "Dr. Sarah Williams"), else email.
|
|
- `policy_acknowledgments` (per-user): one row per (`userId`, `policyDocumentId`,
|
|
`version`), with `acknowledgedAt`. Unique index on those three columns;
|
|
acknowledging is idempotent for a given version.
|
|
|
|
## Routes
|
|
|
|
- `GET/POST /api/policy_documents`, `PUT/DELETE /api/policy_documents/:id`,
|
|
plus the standard generic-CRUD extras — guarded by
|
|
`checkCrudPermissions('policy_documents')` (`${METHOD}_POLICY_DOCUMENTS`).
|
|
- `GET /api/policy_acknowledgments` (the caller's own acknowledgments) and
|
|
`POST /api/policy_acknowledgments` (`{ data: { policyDocumentId } }` →
|
|
acknowledges the document's **current** version, or returns `null` as a no-op when the caller is
|
|
acting through a drilled child scope that is not their own scope) — both guarded by
|
|
`checkPermissions('ACK_POLICY')`.
|
|
- `GET /api/policy_acknowledgments/report` — manager-facing acknowledgment
|
|
report for the current tenant scope. Returns summary totals, per-document
|
|
completion rows, and per-staff document statuses.
|
|
|
|
## Authorization
|
|
|
|
- `READ_POLICY_DOCUMENTS` — granted to the four campus roles (director via full
|
|
access; office_manager/teacher/support via the read-only entity grant).
|
|
`student`/`guardian` get no policy-document access.
|
|
- `CREATE/UPDATE/DELETE_POLICY_DOCUMENTS` — `director` (full access) and
|
|
`office_manager` (explicit grant in the role seeder). `teacher`/`support_staff`
|
|
are read-only.
|
|
- `ACK_POLICY` — seeded for `director`, `office_manager`, `teacher`, and
|
|
`support_staff`. It is a personal workflow permission, is not implied by
|
|
`globalAccess`, and can be extended or removed per user via effective
|
|
permissions. Acknowledgments persist only when the active scope is the user's own scope; parent
|
|
users drilled into a child school/campus/classroom do not see personal acknowledgment badges or
|
|
acknowledgment actions there, and the backend no-ops any attempted write so no reportable rows are
|
|
created for the child scope.
|
|
|
|
Tenant/campus scoping is applied in the data layer (`tenantWhere` /
|
|
`findOwnedByPk`); acknowledgment reads are additionally restricted to the
|
|
caller's own `userId`. The manager report is scoped to the current tenant:
|
|
organization for owner/superintendent, school for principal/registrar, campus
|
|
for director, and the active drilled scope for platform admins. Report access
|
|
requires `READ_POLICY_ACKNOWLEDGMENT_REPORTS`; owner, superintendent,
|
|
principal, registrar, and director receive it through seeded baseline permissions.
|
|
super_admin/system_admin can read it only while drilled into a tenant.
|
|
`custom_permissions` can grant the report permission to tenant users and
|
|
`custom_permissions_filter` can remove it. The report population is active
|
|
staff accounts in the current scope holding one of
|
|
director/office_manager/teacher/support_staff roles. Header notifications use
|
|
the same current-version acknowledgment rows, so a user is reminded until each
|
|
visible active document version is acknowledged.
|
|
|
|
## Tests
|
|
|
|
- **Unit** (`backend/src/shared/constants/policy-documents.test.ts` +
|
|
`users.test.ts`, `npm test`): the pure domain rules —
|
|
`isPolicyDocumentCategory` validation, the `nextPolicyDocumentVersion`
|
|
re-acknowledgment bump, and `formatPersonName` (author rendering).
|
|
- **Backend service** (`backend/src/services/policy_acknowledgments.test.ts`):
|
|
acknowledgment listing plus the drilled-child no-op rule for parent users.
|
|
- **Frontend unit** (`vitest`): `business/policies/mappers.test.ts` (handbook;
|
|
tag↔category, author), `business/safety-protocols/mappers.test.ts` (steps +
|
|
autism considerations), `business/safety-protocols/selectors.test.ts`
|
|
(management grant + draft validation for the authoring form),
|
|
`business/director-dashboard/selectors.test.ts` (dashboard acknowledgment rows
|
|
and risk areas), and `business/profile/selectors.test.ts` (current-version
|
|
profile checklist rows, missing-first sorting, and old-version acknowledgment
|
|
exclusion).
|
|
- **Seeded e2e** (`frontend/tests/e2e/policy-acknowledgments.seeded.e2e.ts`,
|
|
`npm run test:e2e:content`): document create/persist, manage-vs-read RBAC
|
|
(director/office_manager manage; teacher reads but cannot create), idempotent
|
|
per-version acknowledgment, version-bump re-acknowledgment, and external-role
|
|
lockout.
|
|
|
|
## Open / deferred
|
|
|
|
None.
|