01 — Product Overview (Leave Module)
- 1. What the module is
- 2. Core domain facts (from source)
- 3. Who uses it (roles from
role.schema.ts:8-65) - 4. Scope in / out
- 5. PRD native-app exclusion (flagged)
- 6. Client surface (screens) — see 05/06
Employee leave requests, balance tracking, and substitute-teacher assignment. Derived from
src/modules/leave/**(controller, service, schemas, DTOs, events, repositories),src/modules/rbac/schemas/role.schema.ts,src/modules/rbac/permissions.constants.ts,src/modules/users/schemas/user.schema.ts,studylyon-blueprint/03-Database/COLLECTIONS.md,studylyon-blueprint/04-Modules/Attendance.md, anddocs/IMPLEMENTATION_PLAN.md. Nothing here is invented; plan-only capability is marked(planned), client-only or roadmap-only(forward-looking), analytics(proposed). Shared platform context: 00-shared/01.
1. What the module is
The Leave module lets an authenticated user of a tenant request time off, an
org admin decide (approve/reject) those requests, and admins assign a
substitute teacher for an approved absence. Balances are computed live
from leave types plus approved requests — there is no stored balance
collection (leave.service.ts:87-88 — "live-computed balance from leave
types + approved requests; no stored balance collection to drift from ground
truth").
The blueprint anticipated this module but does not specify it: 04-Modules/ has
no Leave doc (only Attendance.md:58 lists "Leave" as an attendance status
value), and COLLECTIONS.md:3643 places leave_requests under "27. Future
Collections" → Human Resources. The implementation exists and is ahead of the
blueprint; docs/IMPLEMENTATION_PLAN.md:121-164 (Phase 2.2, "Leave & Substitution
Module", 7.5 days) is the authoritative plan.
2. Core domain facts (from source)
- Request model —
schemas/leave-request.schema.ts:16-48:userId(ref User, required),leaveTypeId(ref LeaveType, required),startDate,endDate(required Date),daysRequested(number,min: 1, server-computed),reason?,status(defaultpending),decidedBy?,decidedAt?,decisionNote?. Collectionleave_requests. Indexes(tenantId, userId, startDate)and(tenantId, status)(:52-53). - Status enum —
leave-request.schema.ts:7-12:pending | approved | rejected | cancelled. Note:cancelledis defined but no cancel endpoint exists — the status is unreachable today (gap). - Leave type model —
schemas/leave-type.schema.ts:9-25:code,name,daysPerYear(min: 0),carryForward(default false),maxCarryForward?,isDefault. Unique(tenantId, code)(:29). - Default types —
leave.service.ts:30-64:CLCasual 12 (no carry),SLSick 10 (carry, max 30),ELEarned 15 (carry, max 60),MLMaternity 180,PLPaternity 15. Seeded lazily and idempotently on first access viabulkWrite$setOnInsertupserts (ensureDefaultTypes,leave.service.ts:299-306). - Balance math —
leave.service.ts:89-124: carried forward =carryForward ? min(maxCarryForward, max(0, daysPerYear − usedPrevYear)) : 0(:107-112);daysRemaining = daysPerYear + carriedForward − daysUsed(:121). Balances use calendar years, lookback to Jan 1 of the previous year (:92-97). - Day counting —
leave.service.ts:310-312:countDaysis an inclusive calendar-day count ((end − start)/86 400 000 + 1); weekends and holidays are counted as leave days (ponytail comment: "switch to working-day count if the school policy needs it"). - Approval semantics —
leave.service.ts:171-212: onlypendingrequests can be decided (:175-178, else 409); a user cannot decide their own request (:179-180, 409); approval re-checks live balance and rejects with 409 "Insufficient leave balance." whendaysRemaining < daysRequested(:183-190). Decision writesstatus,decidedBy,decidedAt,decisionNote(:195-200) and emitsLeaveApproved/LeaveRejected. - Substitutions —
schemas/substitution.schema.ts:14-47:leaveRequestId,absentTeacherId,substituteTeacherId,classId,subjectId,date,startTime,endTime,status(assigned | completed | cancelled,:7-11),notes?. Only approved requests can get a substitution (leave.service.ts:227-230, 409); the requester must have a Teacher record (:232-238, 404); time-slot clash for the substitute is rejected (:241-250, 409,timeOverlaps:314-321). - Scope rules — non-admins see only their own requests
(
leave.service.ts:166-167); org admins see all, optionally filtered byuserId(:163-167). Requester identity always comes from the tenant context, never the body (:138).
3. Who uses it (roles from role.schema.ts:8-65)
| Role | In leave module |
|---|---|
org_admin (priority 100, ALL_PERMISSIONS role.schema.ts:22-24) | Requests, lists all, decides, creates types, assigns substitutions |
teacher (priority 50, role.schema.ts:26-32) | Requests leave; receives substitutions (needs a Teacher record) |
staff (priority 40, role.schema.ts:33-40) | Can request leave (any authenticated user can) |
student / parent | No leave surface; schema allows but nothing gates it (gap) |
No leave.* permissions exist — permissions.constants.ts:1-97 contains no
leave entries, and leave endpoints carry only JwtAuthGuard
(leave.controller.ts:27), with the org-admin check done inline via
ctx.roles (leave.service.ts:163). This is a documented security gap.
4. Scope in / out
| In scope (implemented) | Out of scope / gaps |
|---|---|
Create request (POST /leave/requests) | Cancel request (cancelled status unreachable — no endpoint) |
List requests (own / all for admin, status+userId filters) | Edit/withdraw a pending request |
| Approve / reject (balance-checked) | leave.* RBAC permissions (permissions.constants.ts has none) |
| Live-computed balance | Stored leave-balance.schema.ts — (planned) (IMPLEMENTATION_PLAN.md:130, not in code) |
| Leave types CRUD (create + list; seeded defaults) | Update/delete leave type |
| Substitutions (assign, list for teacher, clash check) | Substitution accept/decline by substitute, status transitions completed/cancelled |
| Calendar (approved only, month range) | Working-day counting (weekends/holidays) — see leave.service.ts:310-312 |
Events: LeaveRequested, LeaveApproved/Rejected, SubstitutionAssigned (events/leave-events.ts:1-26) | Substitution → Timetable entry + notifications — (planned) (IMPLEMENTATION_PLAN.md:163) |
5. PRD native-app exclusion (flagged)
Per 00-shared/01 §9: PRODUCT_REQUIREMENTS_DOCUMENT.md:144 puts native mobile
apps out of Phase 1 scope (roadmap Phase 3 plans a read-only companion). The
decision recorded with the product owner is that these docs specify a
full-featured Flutter client now, to the complete leave API surface; any
conflict with the web-first roadmap resolves in favor of these docs unless the
roadmap is amended.
6. Client surface (screens) — see 05/06
Request form · My requests list · Approvals queue (org admin) · Balance card · Leave types admin (org admin) · Substitutions (list + assign) · Calendar.