03 — User Journeys (Timetable Module)
- Journey 1 — Create a timetable entry (Coordinator / Admin)
- Journey 2 — View a class's weekly grid (all personas, read)
- Journey 3 — View a teacher's weekly grid (teacher self / coordinator)
- Journey 4 — Resolve a conflict (Coordinator / Admin)
- Journey 5 — Room schedule view (Facilities Manager) —
(planned) - Failure exits (shared)
Five journeys mapped 1:1 to the implemented API. Endpoint citations:
timetable.controller.ts:14-29,timetable.service.ts:16-69.(planned)= roadmap/documented but not in code.
Journey 1 — Create a timetable entry (Coordinator / Admin)
flowchart TD
A[Class grid → tap empty slot] --> B[Entry editor sheet]
B --> C[Pick class, subject, teacher, day, start/end time, room?, academic year]
C --> D[Submit POST /timetable]
D --> E{Server conflict check}
E -->|409 Schedule conflict detected| F[Inline AppBanner: teacher/room already\n booked in overlapping period]
E -->|400 VALIDATION_ERROR| G[Field errors per DTO]
E -->|200| H[TimetableEntryCreated emitted]
H --> I[Slot appears in grid, server-sorted]
F --> J[Fix time/teacher/room → resubmit]
- Body:
CreateTimetableEntryDto(create-timetable-entry.dto.ts:5-38). - Conflict rule: query matches same-day entries sharing
teacherIdorroomId(timetable.service.ts:17-22); overlapping time (start1 < end2 && start2 < end1,timetable.service.ts:68) → 409. - Not checked: same-class overlap (OQ-1) — client may warn but server accepts.
- Side-effect:
TimetableEntryCreated(timetable.service.ts:33-44) — no queue routing exists (01 §6).
Journey 2 — View a class's weekly grid (all personas, read)
flowchart TD
A[Nav 'Timetable' → pick class] --> B[GET /timetable?classId=<id>]
B --> C[Entries sorted dayOfWeek, startTime]
C --> D[Render 6-day grid Mon..Sat × time rows]
D --> E[Tap slot → detail popover]
D --> F[Empty day → dashed empty cell]
- Server-sorted
{dayOfWeek:1, startTime:1}(timetable.service.ts:48-53) — client renders, never re-sorts. - Day headers from
DayOfWeekenum (timetable.schema.ts:7-14): Monday–Saturday.
Journey 3 — View a teacher's weekly grid (teacher self / coordinator)
flowchart TD
A[Teacher detail → Schedule tab] --> B[GET /timetable?teacherId=<id>]
B --> C[Grid sorted dayOfWeek, startTime]
C --> D{Teacher role?}
D -->|teacher self| E[Read-only grid, no editor affordances]
D -->|coordinator/admin| F[Long-press slot → edit / duplicate entry]
- Same sort contract (
timetable.service.ts:55-60). - A teacher resolving "me" has no
/timetable/meendpoint — client must know the teacher id (OQ-2, teachers03 §5).
Journey 4 — Resolve a conflict (Coordinator / Admin)
flowchart TD
A[Submit entry] --> B{409?}
B -->|yes| C[Conflict banner: 'Teacher already booked\n 10:00-11:00 on Tuesday'\n or 'Room already in use']
C --> D[Options: change time · change teacher ·\n change room · cancel]
D --> E[Resubmit POST /timetable]
E -->|200| F[Grid updates]
E -->|409 again| C
- Client can pre-flight conflicts locally: loaded grids per teacher/room let the editor warn before submit; server remains the source of truth (races possible).
- No partial write: a 409 leaves the grid untouched.
Journey 5 — Room schedule view (Facilities Manager) — (planned)
flowchart TD
A[Rooms module → room detail] --> B{RoomId filter exists?}
B -->|no today| C[Client fetches GET /timetable?classId=\n for every class using the room\n and merges slots]
B -->|planned| D[GET /timetable?roomId=<id>]
C --> E[Render merged room grid, read-only]
D --> E
- No
roomIdquery param onGET /timetabletoday (timetable.controller.ts:22-25) — the merged client view is a documented composition, not an invention of API surface; native filter is(planned)perIMPLEMENTATION_PLAN.md:226("conflicts… export" phase).
Failure exits (shared)
401 → silent refresh → sessionExpired; 429 → "try again in a moment", no auto-retry;
5xx → generic + requestId + retry (per 00-shared/07 §11).