40227-vm/backend/docs/campus-attendance.md
2026-06-10 18:27:19 +02:00

55 lines
6.3 KiB
Markdown

# Campus Attendance Backend
## Purpose
The campus attendance slice owns campus attendance system links (`campus_attendance_config`) and manually entered daily campus attendance summaries (`campus_attendance_summaries`), both scoped per organization. The backend is the source of truth for these records. The UI works with daily aggregate totals, not student-level attendance sessions; student-level data remains in the separate generated `attendance_sessions` / `attendance_records` models and is not handled here.
## Slice Files (by layer)
- Route: `src/routes/campus_attendance.ts` (thin wiring; `GET /configs`, `PUT /configs/:campusKey`, `GET /summaries`, `PUT /summaries/:campusKey/:date`). Mounted at `/api/campus_attendance` behind the `authenticated` middleware in `src/index.ts`.
- Controller: `src/api/controllers/campus_attendance.controller.ts` (custom — `listConfigs`, `upsertConfig`, `listSummaries`, `upsertSummary`).
- Service (BLL): `src/services/campus_attendance.ts` (+ `src/services/campus_attendance.types.ts`). Contains its own validation, scope resolution, and DTO mappers.
- Repository (DAL): queries run through `db.campus_attendance_config` and `db.campus_attendance_summaries` inside the service (no separate `db/api` file).
- Models: `src/db/models/campus_attendance_config.ts`, `src/db/models/campus_attendance_summaries.ts`.
- Shared used: `db/with-transaction.ts`, `services/shared/access.ts`, `services/shared/validate.ts` (`clampLimit`, `requiredIsoDate`), `shared/constants/campus-attendance.ts` (role lists, limits, `normalizeCampusKey`, `getProductRole`), `shared/constants/pagination.ts` (`resolvePagination`), `shared/errors/forbidden.ts`, `shared/errors/validation.ts`.
## API
All routes require JWT authentication.
- `GET /api/campus_attendance/configs` -> `200` `{ rows, count }`. `rows` are config DTOs. Optional query `campusKey`; supports `limit` and `page` via `resolvePagination`.
- `PUT /api/campus_attendance/configs/:campusKey` -> `200` the upserted config DTO. Body is `req.body.data` with optional `attendance_link`.
- `GET /api/campus_attendance/summaries` -> `200` `{ rows, count }`. `rows` are summary DTOs. Optional query `campusKey`, `startDate`, `endDate` (ISO `YYYY-MM-DD`), `limit`.
- `PUT /api/campus_attendance/summaries/:campusKey/:date` -> `200` the upserted summary DTO. `:date` must be an ISO date. Body is `req.body.data`.
Config DTO fields: `id`, `campus_key`, `attendance_link`, `updated_by_label`, `organizationId`, `campusId`, `createdById`, `updatedById`, `createdAt`, `updatedAt`.
Summary DTO fields: `id`, `campus_key`, `date` (from `attendance_date`), `total_enrolled`, `total_present`, `total_absent`, `total_tardy`, `attendance_percentage` (number), `recorded_by_label`, `notes`, `organizationId`, `campusId`, `createdById`, `updatedById`, `createdAt`, `updatedAt`.
## Access Rules
- All endpoints require an authenticated tenant user (`assertAuthenticatedTenantUser`).
- Mutations (`PUT` config / summary) additionally require manage access (`assertCanManageCampusAttendance`): the user must either hold one of `CAMPUS_ATTENDANCE_MANAGER_ROLE_NAMES` (super admin, admin, platform owner, tenant director, campus manager, finance officer) or have a derived product role in `CAMPUS_ATTENDANCE_MANAGE_PRODUCT_ROLES` (office, director, superintendent). Global-access roles pass `hasRoleAccess`.
- Campus-key access (`assertCanAccessCampusKey`): tenant-wide roles (`CAMPUS_ATTENDANCE_TENANT_WIDE_ROLE_NAMES` — super admin, admin, platform owner) may access any campus key. Other users may only access the campus key derived from their own profile (campus code/name, or staff profile campus code/name, normalized via `normalizeCampusKey`); a mismatch or missing campus key throws `ForbiddenError`.
- The frontend does not send organization, campus UUID, creator, updater, or label fields. The backend derives them from the authenticated user (`requireOrganizationId`, `getCampusId`, `getDisplayName`, `currentUser.id`).
## Tenant Scope
- Every read and write filters by `organizationId: requireOrganizationId(currentUser)`.
- `campusScope` resolves the `campus_key` filter: a requested `campusKey` is access-checked then applied; tenant-wide roles with no requested key see all campus keys (no `campus_key` filter); other users are restricted to their own derived `campus_key`, and users with no derivable campus key are rejected with `ForbiddenError`.
- On upsert, the existing-row lookup keys on `organizationId` + `campus_key` (config) or `organizationId` + `campus_key` + `attendance_date` (summary).
## Data Contract
- Config mutation input (`ConfigInput`): optional `attendance_link` (stored as trimmed text or `null`).
- Summary mutation input (`SummaryInput`): `total_enrolled`, `total_present`, `total_absent` are required non-negative integers; `total_tardy` is optional (defaults to `0`); `notes` optional text. Validation requires `total_enrolled > 0` and that present/absent/tardy each do not exceed enrolled.
- `attendance_percentage` is computed by the backend as `(total_present / total_enrolled) * 100`, stored as `DECIMAL(5,2)` and returned as a number.
- Models: both tables carry `organizationId` (not null), nullable `campusId`, `createdById` (not null), nullable `updatedById`, `paranoid` soft deletes, and `belongsTo` associations to `organizations`, `campuses`, and `users` (createdBy, updatedBy). `campus_attendance_config` adds `attendance_link` and `updated_by_label`; `campus_attendance_summaries` adds `attendance_date` (DATEONLY), the four totals, `attendance_percentage`, `recorded_by_label`, and `notes`.
- List pagination: configs use `resolvePagination(limit, page)`; summaries use `clampLimit(limit, CAMPUS_ATTENDANCE_DEFAULT_LIMIT=120, CAMPUS_ATTENDANCE_MAX_LIMIT=366)` and have no offset paging.
## Behavior / Notes
- Both upserts run inside `withTransaction`: find existing row, then `update` it or `create` a new one (setting `createdById` on create).
- Config list orders by `campus_key asc`; summary list orders by `attendance_date desc`, then `campus_key asc`.
- Summary date range filtering uses `requiredIsoDate` on `startDate`/`endDate` and applies `Op.gte` / `Op.lte` on `attendance_date`.
- Invalid campus keys, dates, or summary payloads throw `ValidationError`; access failures throw `ForbiddenError`.
## Tests
None yet (no `*.test.ts` under `backend/src` references this slice).
## Related
- Frontend: `frontend/docs/campus-attendance-integration.md`.