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_attendancebehind theauthenticatedmiddleware insrc/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_configanddb.campus_attendance_summariesinside the service (no separatedb/apifile). - 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 }.rowsare config DTOs. Optional querycampusKey; supportslimitandpageviaresolvePagination.PUT /api/campus_attendance/configs/:campusKey->200the upserted config DTO. Body isreq.body.datawith optionalattendance_link.GET /api/campus_attendance/summaries->200{ rows, count }.rowsare summary DTOs. Optional querycampusKey,startDate,endDate(ISOYYYY-MM-DD),limit.PUT /api/campus_attendance/summaries/:campusKey/:date->200the upserted summary DTO.:datemust be an ISO date. Body isreq.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 (
PUTconfig / summary) additionally require manage access (assertCanManageCampusAttendance): the user must either hold one ofCAMPUS_ATTENDANCE_MANAGER_ROLE_NAMES(super admin, admin, platform owner, tenant director, campus manager, finance officer) or have a derived product role inCAMPUS_ATTENDANCE_MANAGE_PRODUCT_ROLES(office, director, superintendent). Global-access roles passhasRoleAccess. - 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 vianormalizeCampusKey); a mismatch or missing campus key throwsForbiddenError. - 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). campusScoperesolves thecampus_keyfilter: a requestedcampusKeyis access-checked then applied; tenant-wide roles with no requested key see all campus keys (nocampus_keyfilter); other users are restricted to their own derivedcampus_key, and users with no derivable campus key are rejected withForbiddenError.- On upsert, the existing-row lookup keys on
organizationId+campus_key(config) ororganizationId+campus_key+attendance_date(summary).
Data Contract
- Config mutation input (
ConfigInput): optionalattendance_link(stored as trimmed text ornull). - Summary mutation input (
SummaryInput):total_enrolled,total_present,total_absentare required non-negative integers;total_tardyis optional (defaults to0);notesoptional text. Validation requirestotal_enrolled > 0and that present/absent/tardy each do not exceed enrolled. attendance_percentageis computed by the backend as(total_present / total_enrolled) * 100, stored asDECIMAL(5,2)and returned as a number.- Models: both tables carry
organizationId(not null), nullablecampusId,createdById(not null), nullableupdatedById,paranoidsoft deletes, andbelongsToassociations toorganizations,campuses, andusers(createdBy, updatedBy).campus_attendance_configaddsattendance_linkandupdated_by_label;campus_attendance_summariesaddsattendance_date(DATEONLY), the four totals,attendance_percentage,recorded_by_label, andnotes. - List pagination: configs use
resolvePagination(limit, page); summaries useclampLimit(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, thenupdateit orcreatea new one (settingcreatedByIdon create). - Config list orders by
campus_key asc; summary list orders byattendance_date desc, thencampus_key asc. - Summary date range filtering uses
requiredIsoDateonstartDate/endDateand appliesOp.gte/Op.lteonattendance_date. - Invalid campus keys, dates, or summary payloads throw
ValidationError; access failures throwForbiddenError.
Tests
None yet (no *.test.ts under backend/src references this slice).
Related
- Frontend:
frontend/docs/campus-attendance-integration.md.