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
| Path | Role |
|---|---|
lumie-backend/modules/attendance/src/main/java/com/lumie/attendance/adapter/in/web | Public HTTP controllers |
lumie-backend/modules/attendance/src/main/java/com/lumie/attendance/application/service | Session, record, statistics, and export services |
lumie-backend/modules/attendance/src/main/java/com/lumie/attendance/domain/entity | AttendanceSession, AttendanceRecord, and StudentReadModel |
lumie-backend/modules/attendance/src/main/java/com/lumie/attendance/domain/repository | Session, record, and student read repositories |
lumie-backend/app/src/main/resources/db/migration/public/V18__rls_baseline.sql | Baseline attendance_sessions and attendance_records tables |
lumie-backend/app/src/main/resources/db/migration/public/V20__rename_qna_is_it_answered.sql | Adds optimistic-lock version columns to attendance tables |
Public Surface
| Surface | Entrypoints |
|---|---|
| Session CRUD | POST /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 records | GET /v1/attendance/sessions/{sessionId}/records, PATCH /v1/attendance/sessions/{sessionId}/records/{recordId}, POST /v1/attendance/sessions/{sessionId}/records/bulk-update |
| Class-oriented helpers | POST /v1/attendance/classes/{classId}/sessions/ensure, GET /v1/attendance/classes/{classId}/statistics, GET /v1/attendance/classes/{classId}/students |
| Student self check-in | POST /v1/attendance/check-in |
| Student history | GET /v1/attendance/students/{studentId}/records, GET /v1/attendance/students/{studentId}/statistics |
| Dashboard and export | GET /v1/attendance/statistics/dashboard, GET /v1/attendance/records/export.csv |
Aggregates And Read Models
| Type | Table | Notes |
|---|---|---|
AttendanceSession | attendance_sessions | Stores session date, optional class linkage, attendance code, late threshold, and record collection |
AttendanceRecord | attendance_records | Unique on (session_id, student_id) |
StudentReadModel | students | Immutable 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
PENDINGrecords 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:
codeExpiresAtis present in the response contract but staysnullbecauseAttendanceSession.create(...)andregenerateCode()never set it.ensureSessionForClass(...)returnsfromWithoutRecords(...), 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
| Dependency | Why it exists |
|---|---|
ClassService | Resolve class name and enrolled student IDs for class-bound sessions |
students read model | Resolve 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_FOUNDwhen the class module cannot resolve the target class. - Check-in fails with
STUDENT_NOT_FOUND,INVALID_CODE,CODE_EXPIRED, orRECORD_NOT_FOUND. - Session and record edits fail with
SESSION_NOT_FOUNDorRECORD_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, andStudentCheckInControllerTestpass without failures.- Docusaurus finishes without MDX or broken-link errors for
backend/attendance-svc.