Skip to main content

Attendance Service

This page covers lumie-backend/modules/attendance.

Responsibility

The attendance module owns:

  • attendance sessions for a date or class;
  • per-student attendance records;
  • code-based student check-in;
  • class, student, and dashboard attendance statistics;
  • CSV export of attendance history.

Unlike the class and student modules, attendance does not expose an internal libs/internal-api contract today. Other modules integrate through class lookups and shared tenant-scoped tables, not through an attendance-side service API.

Source Paths

PathRole
lumie-backend/modules/attendance/src/main/java/com/lumie/attendance/adapter/in/webPublic HTTP controllers
lumie-backend/modules/attendance/src/main/java/com/lumie/attendance/application/serviceSession, record, statistics, and export services
lumie-backend/modules/attendance/src/main/java/com/lumie/attendance/domain/entityAttendanceSession, AttendanceRecord, and StudentReadModel
lumie-backend/modules/attendance/src/main/java/com/lumie/attendance/domain/repositorySession, record, and student read repositories
lumie-backend/app/src/main/resources/db/migration/public/V18__rls_baseline.sqlBaseline attendance_sessions and attendance_records tables
lumie-backend/app/src/main/resources/db/migration/public/V20__rename_qna_is_it_answered.sqlAdds optimistic-lock version columns to attendance tables

Public Surface

SurfaceEntrypoints
Session CRUDPOST /v1/attendance/sessions, GET /v1/attendance/sessions, GET /v1/attendance/sessions/{id}, POST /v1/attendance/sessions/{id}/regenerate-code, DELETE /v1/attendance/sessions/{id}
Session recordsGET /v1/attendance/sessions/{sessionId}/records, PATCH /v1/attendance/sessions/{sessionId}/records/{recordId}, POST /v1/attendance/sessions/{sessionId}/records/bulk-update
Class-oriented helpersPOST /v1/attendance/classes/{classId}/sessions/ensure, GET /v1/attendance/classes/{classId}/statistics, GET /v1/attendance/classes/{classId}/students
Student self check-inPOST /v1/attendance/check-in
Student historyGET /v1/attendance/students/{studentId}/records, GET /v1/attendance/students/{studentId}/statistics
Dashboard and exportGET /v1/attendance/statistics/dashboard, GET /v1/attendance/records/export.csv

Aggregates And Read Models

TypeTableNotes
AttendanceSessionattendance_sessionsStores session date, optional class linkage, attendance code, late threshold, and record collection
AttendanceRecordattendance_recordsUnique on (session_id, student_id)
StudentReadModelstudentsImmutable read-only projection over the student table

StudentReadModel is a deliberate cross-module read model. The attendance module reads the students table directly for immutable name and user linkage rather than exporting its own student cache.

Runtime Flows

Ensure-class-session flow

Student check-in flow

Key Behaviors

  • ensureSessionForClass(...) is idempotent on (classId, today in KST) and returns the existing session when one already exists.
  • Session creation for a class pre-creates one record per currently enrolled student using ClassService.getEnrolledStudentIds(...).
  • Standalone session creation without a class uses every active student from StudentReadRepository.findByIsActiveTrue().
  • Check-in resolves the student by authenticated userId, not by payload-supplied student ID.
  • Dashboard statistics exclude PENDING records so undecided rows do not dilute the attendance rate.
  • CSV export pages through sessions, then joins records and student names in-memory per page.

Representative Contract Example

These examples match ClassAttendanceController, StudentCheckInController, AttendanceSessionResponse, CheckInResponse, AttendanceSessionCommandService, AttendanceRecordCommandService, and the corresponding controller or service tests.

Ensure today's class session

POST /v1/attendance/classes/9/sessions/ensure

{
"id": 77,
"name": "수학반 A",
"sessionDate": "2026-06-14",
"classId": 9,
"className": "수학반 A",
"attendanceCode": "654321",
"codeExpiresAt": null,
"lateThresholdMinutes": 10,
"createdAt": "2026-06-14T00:00:00Z",
"updatedAt": "2026-06-14T00:00:00Z",
"presentCount": 0,
"absentCount": 0,
"lateCount": 0,
"excusedCount": 0,
"totalStudents": 0
}

Two details are easy to miss:

  • codeExpiresAt is present in the response contract but stays null because AttendanceSession.create(...) and regenerateCode() never set it.
  • ensureSessionForClass(...) returns fromWithoutRecords(...), so the response shows zero counts even though attendance records were created for enrolled students.

Student self check-in

POST /v1/attendance/check-in

{
"code": "654321"
}
{
"message": "지각으로 처리되었습니다.",
"status": "LATE"
}

CheckInRequest carries only the six-digit code. The service resolves the student from the authenticated user context and rejects expired or unknown codes with CODE_EXPIRED or INVALID_CODE.

Dependencies And Boundaries

DependencyWhy it exists
ClassServiceResolve class name and enrolled student IDs for class-bound sessions
students read modelResolve student name and authenticated user linkage for check-in and CSV export

The attendance module stores class_id, class_name, and student_id, but the class and student modules remain the source of truth for those domains.

Failure Modes

  • Creating or ensuring a class session fails with CLASS_NOT_FOUND when the class module cannot resolve the target class.
  • Check-in fails with STUDENT_NOT_FOUND, INVALID_CODE, CODE_EXPIRED, or RECORD_NOT_FOUND.
  • Session and record edits fail with SESSION_NOT_FOUND or RECORD_NOT_FOUND.
  • Record uniqueness is enforced at the table level with (session_id, student_id).

Known Contract Drift

AttendanceSession exposes codeExpiresAt, AttendanceRecordCommandService.checkInByCode(...) rejects expired codes, and AttendanceSessionResponse includes the field. However, the current create and regenerate paths never assign codeExpiresAt, so newly created codes do not expire unless some external process writes the column. The docs reflect the code as-is, not an intended-but-unimplemented expiry policy.

Observability

  • Session creation, ensure, regeneration, check-in, bulk update, and export all log through the application services.
  • There is no queue, retry loop, or background sweeper in this module.

Verification

./gradlew :modules:attendance:test
./gradlew :app:test --tests '*Attendance*'
cd lumie-document/docusaurus && npm run build

Expected success signals:

  • Gradle finishes with BUILD SUCCESSFUL.
  • AttendanceSessionCommandServiceTest, AttendanceRecordCommandServiceTest, and StudentCheckInControllerTest pass without failures.
  • Docusaurus finishes without MDX or broken-link errors for backend/attendance-svc.