Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

03 — User Journeys (Teachers Module)

Five journeys mapped 1:1 to the implemented API. Endpoint citations: teacher.controller.ts:24-38, subject-assignment.controller.ts:21-38, timetable.controller.ts:21-27. (planned) = roadmap/documented but not in code.


Journey 1 — Create a teacher (Org Admin / HR)

flowchart TD
    A[Start: Users list] --> B[Find/create User account]
    B --> C[Open Teachers list]
    C --> D[FAB 'Add teacher' → Create form]
    D --> E{Fill form: userId, employeeNumber, department,\n designation, joiningDate, status, subjects, classes}
    E --> F[Submit POST /teachers]
    F --> G{Server}
    G -->|409 DUPLICATE_RESOURCE| H[Inline error: user already has profile\n OR employeeNumber exists]
    G -->|400 VALIDATION_ERROR| I[Field errors per DTO]
    G -->|200| J[TeacherCreated event]
    J --> K[In-app notification: teacher-created]
    J --> L[Search index: Teacher]
    J --> M[Success snackbar → navigate to detail]
  • Steps: POST /teachers with CreateTeacherDto (create-teacher.dto.ts:4-52).
  • Business rules: findByUserId → 409 (teacher.service.ts:27-31); findByEmployeeNumber → 409 (teacher.service.ts:32-38).
  • Side-effects: TeacherCreatedin-app queue job teacher-created (event-queue-map.ts:31) → notification (inapp.worker.ts:46-53); search index upsert (search-indexer.service.ts:10,90-96).
  • Note: PLAN.md:36 says "→ email queue → welcome email" — the code routes to in-app, not emails (see 01 §6, QA-12).
  • Failure exits: 401 (expired token → refresh), 429 (backoff), 5xx (generic + requestId).

Journey 2 — Assign subjects/classes (Org Admin / Academic Coordinator)

flowchart TD
    A[Teacher detail → Assignments tab] --> B[Pick academic year\ndefault = current]
    B --> C[Load matrix GET /subject-assignments/by-teacher/:id?academicYearId=]
    C --> D[Empty state: 'No assignments this year'\n CTA 'Add assignment']
    D --> E[Assignment editor: subject × class × year]
    E --> F{Duplicate check client-side\n teacher+subject+class+year exists?}
    F -->|yes| G[Block: inline conflict message]
    F -->|no| H[POST /subject-assignments]
    H --> I{Server}
    I -->|200| J[Append row to matrix]
    I -->|400/404| K[Error banner, no state change]
    I -->|500| L[Generic error + requestId]
    J --> M[Optional: DELETE /subject-assignments/:id\n to remove mis-assignment]
  • Sources: subject-assignment.service.ts:13-17 (create — no server dup check), subject-assignment.service.ts:26-31 (byTeacher, requires academicYearId), subject-assignment.schema.ts:24-25 (non-unique indexes).
  • academicYearId comes from GET /academic-years (academic-year.controller.ts:27-30) with isCurrent flag (academic-year.schema.ts:31-32).
  • Class picker: GET /classes?academicYearId (class.controller.ts:27-33); subject picker: GET /subjects (subject.controller.ts:27-30).

Journey 3 — Edit teacher profile (HR / Admin)

flowchart TD
    A[Teachers list → row] --> B[Teacher detail → Profile tab]
    B --> C[Edit button → Edit form prefilled from GET /teachers/:id]
    C --> D[Change fields: department, designation, joiningDate,\n status, qualification, experienceYears, subjects, classes]
    D --> E[Submit PATCH /teachers/:id]
    E --> F{Server}
    F -->|200| G[TeacherUpdated → audit-write log-teacher-updated]
    F -->|404| H['Teacher not found' → return to list]
    F -->|400| I[Field errors]
    G --> J[Snackbar saved → detail refresh]
  • UpdateTeacherDto (update-teacher.dto.ts:4-53) — userId immutable, metadata added (only in update DTO, update-teacher.dto.ts:51-53).
  • Side-effect: TeacherUpdatedaudit-write (event-queue-map.ts:32) + search re-index (search-indexer.service.ts:17).
  • Server pre-check findById before $set (teacher.service.ts:80-83) → PATCH of a soft-deleted teacher returns 404 automatically (soft-delete scope).

Journey 4 — Deactivate a teacher (HR / Admin)

flowchart TD
    A[Teacher detail → overflow menu 'Deactivate'] --> B[Confirm dialog:\n 'Deactivate {name}?\n Profile will be hidden from lists.\n Assignments remain in history.']
    B --> C[Confirm → DELETE /teachers/:id]
    C --> D{Server}
    D -->|200| E[TeacherDeleted → audit-write log-teacher-deleted\n + search index removal]
    D -->|404| F['Teacher not found' → treat as already deactivated\n → return to list]
    E --> G[Snackbar 'Teacher deactivated'\n → navigate back to list (row gone)]
  • Soft-delete mechanics: base.repository.ts:68-74 sets isDeleted/deletedAt/deletedBy, all reads exclude (base.repository.ts:20-30).
  • No server-side guard on active assignments/timetable/substitutions (OQ-5) — dialog copy must warn that schedule data is not cleaned up.
  • No re-activation endpoint exists → deactivate is irreversible via API (manual DB restore only). Dialog states this.

Journey 5 — Teacher self-view: profile, assignments, schedule (Teacher)

flowchart TD
    A[Login as teacher] --> B[Nav 'My Teaching' (client-side surface)]
    B --> C[GET /subject-assignments/by-teacher/:teacherId?academicYearId=]
    B --> D[GET /timetable?teacherId=<me>]
    B --> E[GET /teachers/:id (own record)]
    C --> F[Term overview: subject × class cards]
    D --> G[Weekly grid: dayOfWeek × startTime, sorted\n timetable.service.ts:55-60]
    E --> H[Profile summary: department, designation,\n status, qualification]
    F --> I[If status on_leave → banner 'On leave'\n emphasis off]
    G --> J[Tap period → context: attendance/homework\n (other modules, no navigation from backend)]
  • teacherId for self: client resolves from GET /teachers/:id? There is no /teachers/me — client needs the teacher record linked to the logged-in user (OQ-2). Options: resolve via GET /teachers list scan (O(n) — bad), or a GET /teachers/:id per recorded id; backend support for "me" is (planned).
  • Schedule source: GET /timetable?teacherId= (timetable.controller.ts:21-27) — note the controller's query param is teacherId, so teacherId=me string from END_TO_END_USER_FLOWS.md:266 is doc-fiction; the client must send the real id.
  • Sort is server-side: {dayOfWeek:1, startTime:1} (timetable.service.ts:56-59).