03 — User Journey (Leave Module)
- Journey 1 — Teacher requests casual leave (happy path)
- Journey 2 — Admin assigns a substitute (post-approval)
- Journey 3 — Teacher checks coverage before requesting
- Journey 4 — Rejection and retry
- Journey 5 — Admin configures leave types
(org admin only) - Journey 6 — Edge: leave overlaps and back-dating
- Journey map (condensed)
- Metrics
(proposed)
End-to-end flows mapped to the exact endpoints they exercise. API-enforced branches are cited to source;
(planned)=IMPLEMENTATION_PLAN.md:121-164;(proposed)= product expectation.
Journey 1 — Teacher requests casual leave (happy path)
- Open Balance (
GET /leave/balance/:me—leave.controller.ts:55-59) → seedaysRemainingper type (leave.service.ts:89-124). - Open Request form → pick leave type (
GET /leave/types,:67-71; defaults seeded lazilyleave.service.ts:299-306), start/end dates, reason. - Submit
POST /leave/requests(:32-36) → server computesdaysRequested(leave.service.ts:136), stampsuserIdfrom token (:138), statuspending(:144), emitsLeaveRequested(:146-154).- 400 if
endDate < startDate(:133-134); 404 if leave type unknown (:129).
- 400 if
- Request appears in My requests (
GET /leave/requests— own only,leave.service.ts:166-167; sortedcreatedAtdesc:168), badgepending. - Org admin opens Approvals queue (
GET /leave/requests?status=pending,:38-47), decidesPATCH /leave/requests/:id/approve(:49-53).- 409 if already decided (
leave.service.ts:175-178), 409 if self-decision (:179-180), 409 if balance insufficient (:188-189).
- 409 if already decided (
- Teacher sees status flip to
approved(eventLeaveApproved,:210) → balance now reflects deduction on next fetch.
Journey 2 — Admin assigns a substitute (post-approval)
- From an approved request, admin opens Assign substitution form.
POST /leave/substitutions(leave.controller.ts:73-77) withleaveRequestId, substituteTeacherId, classId, subjectId, date, startTime, endTime, notes?.- 409 if request not
approved(leave.service.ts:227-230). - 404 if requester has no Teacher record (
:232-238). - 409 if substitute already booked in the slot (
:241-250).
- 409 if request not
- Substitution shows in substitute's My substitutions
(
GET /leave/substitutions/teacher/:id,:79-83; sorted by date:275-280). (planned)Timetable entry created + notifications to substitute and students (IMPLEMENTATION_PLAN.md:163).
Journey 3 — Teacher checks coverage before requesting
- Open Calendar (
GET /leave/calendar?from&to,leave.controller.ts:85-91) → approved requests overlapping the range (leave.service.ts:282-296); default = current month (:283-287). - Decide dates avoiding collisions with colleagues (calendar is
tenant-scoped via
BaseRepository, not filtered by user).
Journey 4 — Rejection and retry
- Admin rejects with note (
PATCH /leave/requests/:id/approve, actionreject,leave-decision.dto.ts:4-7) →decisionNotestored (leave.service.ts:199), eventLeaveRejected(:210). - Teacher sees
rejected+ note in My requests. - Teacher edits dates in a new request and resubmits (no edit/retry of the old request — no PATCH-request endpoint; gap).
(planned)notification of the decision reaches the teacher via the event bus (LeaveDecidedEvent,events/leave-events.ts:10-18).
Journey 5 — Admin configures leave types (org admin only)
- Open Leave types → list (
GET /leave/types,:67-71), defaults always present (ensureDefaultTypes,leave.service.ts:299-306). - Create custom type
POST /leave/types(:61-65) —code, name, daysPerYear, carryForward?, maxCarryForward?(create-leave-type.dto.ts:4-28). Unique(tenantId, code)(leave-type.schema.ts:29) → 11000 duplicate-key error surfaces as a conflict (no dedicated handler in service — gap). - Balance screen now includes the new type automatically (computed from
types,
leave.service.ts:89-124).
Journey 6 — Edge: leave overlaps and back-dating
- No same-user overlap validation exists at create time
(
createRequest,leave.service.ts:126-156); two overlappingpendingrequests can both exist. QA treats this as a known gap (see 14_QA_Checklist §2); a future server rule or client-side warning(forward-looking). - Back-dated requests are allowed — no rule prevents
startDatein the past.
Journey map (condensed)
[Balance check] → [Request] → pending → [Admin queue] → approve → [Calendar/status]
↘ reject → note → [Retry as new request]
[Approved] → [Assign substitution] → [Substitute sees assignment] → (planned) timetable+notify
Metrics (proposed)
Request → decision median; reject rate by reason; substitution coverage of approved leave; balance-sufficiency collisions.