40227-vm/backend/docs/policy-documents.md
2026-06-12 06:55:35 +02:00

112 lines
5.9 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; all four campus staff roles (`director`,
`office_manager`, `teacher`, `support_staff`) 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 is gated to
owner/superintendent/director/office_manager (`canManagePolicies`, mirroring the
backend grant). 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 `canManageSafetyProtocols`, which reuses
the policy grant). Title/body/steps/considerations changes bump `version` and
require re-acknowledgment.
## 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) — both guarded by
`checkPermissions('ACK_POLICY')`.
## 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` — the four campus roles (a product-feature action permission;
extendable per user via `custom_permissions`).
Tenant/campus scoping is applied in the data layer (`tenantWhere` /
`findOwnedByPk`); acknowledgment reads are additionally restricted to the
caller's own `userId`. A manager-facing acknowledgment-status report (audience
TBD) is a deferred refinement.
## 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).
- **Frontend unit** (`vitest`): `business/policies/mappers.test.ts` (handbook;
tag↔category, author), `business/safety-protocols/mappers.test.ts` (steps +
autism considerations) and `business/safety-protocols/selectors.test.ts`
(management grant + draft validation for the authoring form).
- **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
- Acknowledgment-status reporting for managers (who-acknowledged-what) — pending
the report-audience decision.
- The acknowledgment + document-management **UI** is design-gated (see
`docs/backlog.md`).