08 — Form Specifications (Timetable Module)
Field-by-field specs. Server column = exact DTO contract (
create-timetable-entry.dto.ts). Weak-validation warnings are derived facts (the DTO validates types/ids but not time format or ordering).
F1 — Create Timetable Entry (POST /timetable)
| # | Field | Type | Required | Server validation | Client input | Default |
|---|---|---|---|---|---|---|
| 1 | classId | string (ObjectId) | ✅ | @IsMongoId (create-timetable-entry.dto.ts:6-8) | Searchable picker from GET /classes (or prefilled from grid scope) | — |
| 2 | subjectId | string (ObjectId) | ✅ | @IsMongoId (:10-12) | Searchable picker from GET /subjects | — |
| 3 | teacherId | string (ObjectId) | ✅ | @IsMongoId (:14-16) | Searchable picker from GET /teachers | — |
| 4 | roomId | string (ObjectId) | — | @IsOptional @IsMongoId (:18-21) | Picker from GET /rooms (code + name) | unset (schema optional, timetable.schema.ts:27-28) |
| 5 | dayOfWeek | string enum | ✅ | @IsEnum(DayOfWeek) (:23-25) | 6 chips Mon–Sat from DayOfWeek (timetable.schema.ts:7-14) | current day of week (proposed) |
| 6 | startTime | string | ✅ | @IsString (:27-29) — format NOT validated | AppTimePicker 24 h → HH:MM zero-padded (08:00); regex `^([01]\d | 2[0-3]):[0-5]\d$ (proposed)` |
| 7 | endTime | string | ✅ | @IsString (:31-33) — no > start check | AppTimePicker; client enforces end > start (proposed) | 09:00 |
| 8 | academicYearId | string (ObjectId) | ✅ | @IsMongoId (:35-37) | AppDropdown from GET /academic-years, default isCurrent (academic-year.schema.ts:31-32) | current year |
Client-side rules (server gaps — derived)
- Time format is load-bearing: overlap detection compares strings lexically
(
start1 < end2 && start2 < end1,timetable.service.ts:62-69). It is correct only for zero-paddedHH:MM. The server never validates format/order, so the client:- forces 24 h
HH:MMinput (picker-based, no free text), - blocks
end <= startbefore submit (a non-ordered pair would maketimeOverlapsmath meaningless), - never sends
"8:00"/"9:00"(would silently break conflict detection).
- forces 24 h
end == startis allowed as a boundary (back-to-back slots legal —timeOverlapsreturns false,timetable.service.ts:68); client should still prefer ≥ 1-row gaps for readability(proposed).- Class-level overlap is not a server error (OQ-1) — the editor must NOT show a class-conflict error; server accepts.
- Room-less entries: conflict detection reduces to teacher-only (the
roomId: undefinedquery clause is stripped by Mongoose;timetable.service.ts:20). - Pre-flight: on picker change, consult cached per-day grids; warn for teacher/room clashes (server 409 remains authoritative — races possible).
Submit payload (exact)
{ "classId": "…", "subjectId": "…", "teacherId": "…", "roomId": "…",
"dayOfWeek": "monday", "startTime": "08:00", "endTime": "09:00",
"academicYearId": "…" }
(roomId omitted when unset — never send null/""; DTO whitelist +
forbidNonWhitelisted pipes reject unknown keys, main.ts:50-57.)
Server responses
- 200
data= created entry doc (envelope-unwrapped; timestamps auto,timetable.schema.ts:16). - 409
ConflictException"Schedule conflict detected" (timetable.service.ts:28) →ConflictBanner, form values kept. No structured error code — client matches on 409 status. - 400
VALIDATION_ERROR: field details mapped (00-shared/07 §3); invaliddayOfWeek(e.g."sunday") fails@IsEnum(:23-25). - Notes:
tenantIdnever sent (from token,base.repository.ts:33-35);forbidNonWhitelistedmeans the editor sends exactly the 8 fields above.
F2 — Update / Delete Timetable Entry — (planned)
- No endpoints exist — no
PATCH /timetable/:id, noDELETE /timetable/:id(timetable.controller.ts:10exposes onlyPOST+GET). Any edit/delete UI is(planned)pending backend (roadmap row "Timetable: Bulk create, conflicts, substitution, workload, export",IMPLEMENTATION_PLAN.md:226). - Interim UX: "Duplicate & correct" — open F1 prefilled from the slot; original stays until a delete surface exists (OQ-2).