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 Journey (Exams Module)

End-to-end journeys mapped to exact API calls (examination.controller.ts, result.controller.ts). Journey steps that rely on endpoints not yet implemented are marked (planned). Error paths quote real service messages (examination.service.ts, result.service.ts).


1. J1 — Plan an exam (Coordinator)

Goal: stand up a term exam with subject slots in under 3 minutes.

  1. Open Exams tab → list loads from GET /api/v1/examinations?page=1&limit=20 (examination.controller.ts:30-32).
  2. Tap "New exam" → form (see 08_Form_Specifications.md §1):
    • academicYearId (MongoId), name, type (midterm|final|unit_test|quarterly|other, examination.schema.ts:15-20), startDate, endDate (CreateExaminationDto, examination.dto.ts:4-26).
  3. Submit → POST /api/v1/examinations → 201 doc with status: 'draft' (examination.service.ts:48) → navigates to exam detail.
  4. Add slots: for each class+subject → POST /api/v1/examinations/:id/subjects with {examinationId, subjectId, classId, date, startTime ('09:00'), endTime, maximumMarks, passingMarks} (CreateExaminationSubjectDto, examination-subject.dto.ts:11-45).
    • Client warns if another slot overlaps date+time (no server check — OQ-2, 01_Product_Overview.md §10) and if the same subject is already added (no server dedupe — OQ-3).
    • Client blocks passingMarks > maximumMarks and startTime ≥ endTime (OQ-6).
  5. List refresh shows the exam; badge draft.

Success signal: detail screen lists N slots; list badge draft. Failure paths: 400 VALIDATION_ERROR (bad ids/dates) → inline field errors; 409 not applicable today (no unique constraints on exam name/slot — OQ-3).

2. J2 — Enter marks (Teacher)

Goal: mark a full class in one sitting; correct errors in place.

  1. Open exam detail → tap a slot → marks screen (GET /api/v1/examinations/:id/subjects for slot list, then GET /api/v1/results/exam-subject/:examSubjectId for existing marks, result.controller.ts:21-25).
  2. Roster = students of the slot's classId (Students module); each row prefilled from the marks response (absent result = unmarked).
  3. Type marksObtained (numeric keyboard, @Min(0)examination-subject.dto.ts:51-55), optional grade + remarks.
  4. Save row → POST /api/v1/results/exam-subject/:examSubjectId/marks with {studentId, marksObtained, grade?, remarks?} (EnterMarksDto, examination-subject.dto.ts:47-66) — upsert: existing (studentId, examinationSubjectId) result is updated in place (examination.service.ts:147-175).
  5. Client pre-validates marksObtained ≤ maximumMarks; if the server still returns 404 "Marks cannot exceed maximum." (examination.service.ts:145-146), treat as stale maximum → refresh slot, re-enter.
  6. Coverage bar shows marked/total; teacher exits at 100%.

Success signal: every roster row has a saved mark; coverage bar 100%. Failure paths: offline → row queued (module offline queue, 13_State_Management.md); 404 "Exam subject not found." (examination.service.ts:144) if slot deleted → return to exam detail; 429 → backoff + retry.

3. J3 — Publish results (Principal / Coordinator)

Goal: release results for one exam; verify the published state.

  1. Exam detail → coverage check (per-slot GET /api/v1/results/exam-subject/:id marked/total counts).
  2. Tap Publish → confirm dialog (explicit, never optimistic — publish has side effects: results become visible downstream).
  3. POST /api/v1/examinations/:id/publish (examination.controller.ts:54-56): server stamps publishedAt = now on every result of the exam's slots (examination-result.repository.ts:42-51) and sets exam status: 'published' (examination.service.ts:209-211); emits ExamResultsPublished (examination.service.ts:212-219) → in-app queue, job results-published (event-queue-map.ts:27).
  4. Detail updates: status badge published; slot marks show publishedAt.
  5. Students/parents notified (planned) — only the in-app job route exists.

Success signal: badge published; marks rows show published timestamp. Failure paths: 404 "Examination not found." (deleted/cross-tenant, examination.service.ts:209 uses updateById on a missing id — no explicit re-check; treat as refresh+return). Re-publish is possible server-side (no guard, OQ-5) — UI hides the button after publish.

4. J4 — View own results (Student)

Goal: see marks per subject and the report card after publish.

  1. Results tab → GET /api/v1/results/student/:studentId (result.controller.ts:16-20) → list of mark docs.
  2. Open report card → GET /api/v1/results/report-card/:studentId/:examId (result.controller.ts:32-37) → subject rows (name resolved client-side from subjectId — OQ-9), totals, percentage (2 dp), overallGrade (A+/A/B+/B/C/D/F, result.service.ts:130-138).
  3. Missing marks render as 0 (result.service.ts:70) with an explicit "not marked" hint — the payload cannot distinguish "0" from "absent".

Success signal: card renders with totals + grade. Failure paths: 404 "No subjects found for this examination." (result.service.ts:51-53) → empty state "No subjects scheduled yet."

5. J5 — Correct a wrong mark (Teacher)

Goal: fix a typo after saving.

  1. Marks screen → tap row → edit cell → save → same POST /results/exam-subject/:examSubjectId/marks endpoint (upsert updates marksObtained/grade/remarks, examination.service.ts:151-161); doc version increments (base.repository.ts:57-66).
  2. MarksEntered event re-emitted (examination.service.ts:163-174).
  3. Snackbar "Mark updated"; no history shown (no per-field change log in payload — only the event carries examinationSubjectId+studentId).

Success signal: corrected value persists across re-fetch. Failure paths: same as J2; after publish, still editable server-side (OQ-5) — UI may allow but flag "published exam".

6. J6 — Edit / delete an exam (Coordinator)

  1. Exam detail → Edit → PATCH /api/v1/examinations/:id with partial {name?, type?, startDate?, endDate?, status?} (UpdateExaminationDto, examination.dto.ts:28-53). Client never sends status except draft→active (OQ-1); disables status editing once published (OQ-5).
  2. Delete → confirm dialog → DELETE /api/v1/examinations/:idsoft delete (examination.service.ts:99-110, base.repository.ts:68-74); subjects/results are not cascaded — subject reads 404, but report cards keep working off the results collection.
  3. Return to list; exam gone (filtered by isDeleted: false, base.repository.ts:20-30).

Success signal: list no longer shows the exam; snackbar "Exam deleted". Failure paths: 404 "Examination not found." (examination.service.ts:101, 87).

7. J7 — Exam lifecycle glance (any role)

  1. List (GET /examinations, paginated page/limit, meta from buildPaginationMeta, pagination-query.dto.ts:41-55); sort/q are ignored by the service (OQ-7) → client sorts by startDate locally.
  2. Statuses map to badges: draft (neutral), active (primary), completed (secondary), published (success) — schema enum examination.schema.ts:28-33.
  3. Row tap → detail (GET /examinations/:id) → slots (GET /examinations/:id/subjects).

8. Journey → endpoint cheat sheet

JourneyPrimary endpoints
J1 Plan examPOST /examinations, POST /examinations/:id/subjects, GET /examinations
J2 Enter marksGET /examinations/:id/subjects, GET /results/exam-subject/:id, POST /results/exam-subject/:id/marks
J3 PublishPOST /examinations/:id/publish, GET /examinations/:id
J4 Own resultsGET /results/student/:studentId, GET /results/report-card/:studentId/:examId
J5 Correct markPOST /results/exam-subject/:id/marks (upsert)
J6 Edit/deletePATCH /examinations/:id, DELETE /examinations/:id
J7 Lifecycle glanceGET /examinations, GET /examinations/:id, GET /examinations/:id/subjects