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

4.6 KiB

Attendance Records Backend

Purpose

attendance_records is the per-student attendance entry within an attendance session — the present/absent/late/excused mark, optional minutes late, and remarks. It is a generic-CRUD slice assembled from the shared factories and belongs to one attendance_sessions row and one row.

This is the generic student/session attendance entity; staff attendance is the separate staff_attendance slice (documented elsewhere).

Slice Files (by layer)

  • Route: src/routes/attendance_records.tscreateCrudRouter(controller, { permission: 'attendance_records' }).
  • Controller: src/api/controllers/attendance_records.controller.tscreateCrudController(service, { csvFields }).
  • Service (BLL): src/services/attendance_records.tscreateCrudService(DbApi, { notFoundCode: 'attendance_recordsNotFound' }).
  • Repository (DAL): src/db/api/attendance_records.ts (Attendance_recordsDBApi) — entity-specific create/bulkImport/update/findBy/findAll; remove/deleteByIds/findAllAutocomplete delegate to db/api/shared/repository.ts.
  • Model: src/db/models/attendance_records.ts.
  • Shared used: CRUD factories (services/shared/crud-service.ts, api/controllers/shared/crud-controller.ts, api/http/crud-router.ts), repository helpers (db/api/shared/repository.ts), shared/constants/pagination.ts (resolvePagination), db/utils.ts (Utils.uuid/Utils.ilike), shared/constants/database.ts (BULK_IMPORT_TIMESTAMP_STEP_MS).

API

The standard generic-CRUD surface (all under /api/attendance_records, JWT + ${METHOD}_ATTENDANCE_RECORDS permission, all 200) — see backend-architecture.md for the shared 9-endpoint contract:

  • POST / — body { data }, returns true.
  • POST /bulk-import — multipart CSV file, returns true.
  • PUT /:id — body { data, id } (the service reads the id from the body), returns true.
  • DELETE /:id — returns true.
  • POST /deleteByIds — body { data: string[] }, returns true.
  • GET / — query filters, returns { rows, count }; ?filetype=csv streams a CSV of csvFields.
  • GET /count — returns { rows: [], count }.
  • GET /autocomplete?query&limit&offset, returns [{ id, label }] where label is status.
  • GET /:id — returns the record with eager associations (see Data Contract).

csvFields: id, remarks, minutes_late.

Access Rules

  • JWT required; the whole router is guarded by checkCrudPermissions('attendance_records'), deriving READ_ATTENDANCE_RECORDS / CREATE_ATTENDANCE_RECORDS / UPDATE_ATTENDANCE_RECORDS / DELETE_ATTENDANCE_RECORDS per HTTP method.
  • Access is granted by role permission or per-user custom_permissions (see permissions.md).

Tenant Scope

  • findAll scopes where.organizationId to currentUser.organizationId; a globalAccess role clears the org filter (sees all tenants).
  • create assigns the organization from currentUser.organizationId; update only reassigns organization for globalAccess users (otherwise it stays the caller's org).

Data Contract

Model columns (paranoid, soft-delete via deletedAt):

  • id (UUID PK).
  • status — ENUM present | absent | late | excused.
  • minutes_late — INTEGER.
  • remarks — TEXT.
  • importHash (STRING(255), unique), organizationId, attendance_sessionId, studentId, createdById, updatedById, timestamps (all UUID FKs nullable).

Associations: belongsTo organization, attendance_session (attendance_sessions), student (), createdBy/updatedBy (users). findBy/GET /:id eager-load organization, attendance_session, and student in a single Promise.all.

List filters (AttendanceRecordsFilter): id, remarks (iLike), minutes_lateRange, status, attendance_session (id or session_type, |-separated), student (id or student_number), organization, createdAtRange, plus field/sort ordering and limit/page pagination.

Behavior / Notes

  • create/bulkImport/update set associations via the Sequelize set* mixins (no file relations on this entity).
  • bulkImport offsets createdAt per row by BULK_IMPORT_TIMESTAMP_STEP_MS to preserve order.
  • List pagination uses the shared resolvePagination defaults (page size 10, capped at 100).
  • Note: AttendanceRecordsFilter accepts an active flag the model has no column for; it is applied to where.active but, with no such column, is currently inert (kept for source accuracy).

Tests

None yet.

  • Generic-CRUD contract: backend-architecture.md; related slices: attendance_sessions, permissions.md.