08 — Form Specifications (Attendance Module)
- 1. Mark form (single) —
POST /attendance - 2. Batch form —
POST /attendance/bulk - 3. Update form (correction) —
PATCH /attendance/:id - 4. Report trigger —
POST /reports/generate - 5. Biometric ingest (machine→API, not a user form)
- 6. Validation error UX mapping (server → fields)
Exact field-level specs for the three write surfaces (mark, batch, update) plus the report trigger. Every field maps to a DTO — no client-invented fields. Validation: server
class-validatorrules as written in the DTOs; client mirrors them for pre-submit feedback (server stays authoritative, 00-shared/06 §5).
1. Mark form (single) — POST /attendance
DTO: MarkAttendanceDto (mark-attendance.dto.ts:11-49).
| Field | Type | Required | Server rule | Client control |
|---|---|---|---|---|
studentId | MongoId string | ✅ | @IsMongoId() (mark-attendance.dto.ts:13-14) | hidden (from selected row) |
classId | MongoId string | ✅ | @IsMongoId() (mark-attendance.dto.ts:16-17) | hidden (from class context) |
date | YYYY-MM-DD string | ✅ | @IsDateString() (mark-attendance.dto.ts:20-22) | AppDatePicker; serialized yyyy-MM-dd in tenant-local date (see 14_QA_Checklist.md §timezone) |
status | enum | ✅ | @IsEnum(AttendanceStatus) — one of present,absent,late,half_day,leave,holiday (mark-attendance.dto.ts:24-28; enum attendance.schema.ts:7-14) | StatusChip/popover; invalid → client-blocked; server 400 VALIDATION_ERROR w/ details (e2e p1-operations.e2e-spec.ts:222-228) |
checkIn | ISO date-time string | optional | @IsOptional() + @IsDateString() (mark-attendance.dto.ts:30-33) | AppTimePicker default now; sent as full ISO (new Date(...), attendance.service.ts:32) |
checkOut | ISO date-time string | optional | @IsOptional() + @IsDateString() (mark-attendance.dto.ts:35-38) | AppTimePicker |
source | string | optional | @IsOptional() @IsString() (mark-attendance.dto.ts:40-43) — client never sends (server default manual, attendance.schema.ts:46-51); invalid value → schema enum rejection risk (OQ) | hidden |
remarks | string | optional | @IsOptional() @IsString() (mark-attendance.dto.ts:45-48) | AppTextField in popover, ≤ 500 chars |
Success: 201 → data = full attendance doc (_id, studentId, classId, date,
status, source, checkIn/checkOut?, remarks?, markedBy?, version, timestamps —
attendance.schema.ts:24-55 + base.schema.ts).
Errors: 400 details per field; 404 if referenced entities invalid (upstream modules);
409 DUPLICATE_RESOURCE only if upsert races a concurrent create on the unique index
(attendance.schema.ts:59-62) — client: refresh grid; 429/5xx per shared mapping.
Idempotency: re-submit is safe by design (upsert findOrCreate, attendance.repository.ts:17-37);
still send Idempotency-Key on offline flush to dedupe events.
2. Batch form — POST /attendance/bulk
DTO: BulkMarkAttendanceDto { records: MarkAttendanceDto[] } (mark-attendance.dto.ts:51-54).
- Client build: selection rows →
recordsarray, each a full mark payload (studentId/classId/date/status; optional checkIn/checkOut/remarks only if set). - Validation (client-side, mirrors server per-record): every record must pass §1 rules;
pre-validate all records before submit — the server loops sequentially
(
attendance.service.ts:48-54) and a single invalid record throws, leaving earlier records saved (partial success — OQ-2). Client blocks submit if any record invalid. - Size: no server cap in DTO; practical limit = one class day (≤ ~150 records). Offline flush batches ≤ 100 (see 15).
- Success: 200/201 →
data= array of docs (same order as records). Client reconciles grid per doc bystudentId. - Failures: map per-record where possible; snackbar "9 of 34 failed" + retry of failed subset (safe — upsert).
3. Update form (correction) — PATCH /attendance/:id
DTO: UpdateAttendanceDto (update-attendance.dto.ts:4-22). Partial replace: only sent
fields are $set (base.repository.ts:57-66).
| Field | Type | Server rule | Notes |
|---|---|---|---|
status | string | @IsString() only — no enum validation (update-attendance.dto.ts:8) | OQ-6: server may persist invalid value via $set (no runValidators). Client MUST restrict to the 6-status enum; keep server-mirror validation on |
checkIn | Date | @IsOptional() (raw Date, no validator) (update-attendance.dto.ts:12) | ISO string → Date |
checkOut | Date | @IsOptional() (update-attendance.dto.ts:16) | as above |
remarks | string | @IsOptional() @IsString() (update-attendance.dto.ts:21) | clearing = send "" |
- Semantics: doc must exist → 404 "Attendance record not found." if not
(
attendance.service.ts:75-79,85-87). Success →data= updated doc (version +1,base.repository.ts:63). - Event:
AttendanceUpdated {attendanceId, studentId, changes:[keys]}—changes=Object.keys(dto)(attendance.service.ts:94-98). - UI: from S7 day detail / S4 read grid (teacher,
attendance.mark+attendance.edit). Prefill current values from the fetched doc. Undo via re-PATCH (not supported server-side as an action — client re-sends previous values).
4. Report trigger — POST /reports/generate
DTO: GenerateReportDto (reports module; type from ReportType, report-job.schema.ts:8-9).
For attendance: {type: 'attendance_summary', classId?, startDate?, endDate?} →
{jobId, status:'queued'} (reports.service.ts:28-44). Poll GET /reports/:jobId.
5. Biometric ingest (machine→API, not a user form)
POST /biometric/ingest {studentId (MongoId), deviceId (string), timestamp (date-string), mode? (string)} (create-biometric-log.dto.ts:4-21). Displayed read-only in S8; no client
form (device pushes). deviceId is a free string today — no device-registry validation
(device biometric-device.schema.ts:15-31 exists; association OQ-3).
6. Validation error UX mapping (server → fields)
Server 400 details array {field, message} (00-shared/07 §3). Client maps:
| Server field | Client target |
|---|---|
studentId/classId | hidden fields — treat as 422-class failure (snackbar, refresh roster) |
date | date picker error text "Enter a valid date" |
status | chip popover error; re-open with current value |
checkIn/checkOut | time field error "Enter a valid time" |
remarks | remarks field error |
Envelope-level errors (409/422/429/5xx) render per 00-shared/06 §5 mapping, never raw server internals (00-shared/07 §11).