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

6.3 KiB

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).

  • Frontend: frontend/docs/campus-attendance-integration.md.