12 — API Mapping (Leave Module)
- 1. Endpoint table
- 2. Request bodies
- 3. Response shapes
- 4. Error contract (all 4xx/5xx in standard envelope, 00-shared/07)
- 5. Events emitted (side effects)
- 6. Query semantics
- 7. Gaps (no endpoint today)
Exact endpoint contract for the Leave module. Source:
leave.controller.ts(routes),leave.service.ts(semantics), DTOs (bodies), schemas (fields),docs/IMPLEMENTATION_PLAN.md((planned)). Envelope and error conventions: 00-shared/07. All endpoints are under@Controller('leave')withJwtAuthGuard+ApiBearerAuth(leave.controller.ts:25-28); URI prefix/api/v1permain.tsversioning.
1. Endpoint table
| # | Method | Path | Summary | Source | Access |
|---|---|---|---|---|---|
| 1 | POST | /api/v1/leave/requests | Create leave request | leave.controller.ts:32-36 | any authenticated user |
| 2 | GET | /api/v1/leave/requests | List requests (status, userId filters) | leave.controller.ts:38-47 | own only; all for org_admin (leave.service.ts:163-167) |
| 3 | PATCH | /api/v1/leave/requests/:id/approve | Approve / reject | leave.controller.ts:49-53 | any (server blocks self-decision :179-180); client: admin |
| 4 | GET | /api/v1/leave/balance/:userId | Live balance for user | leave.controller.ts:55-59 | any (no server restriction — gap) |
| 5 | POST | /api/v1/leave/types | Create leave type | leave.controller.ts:61-65 | org admin (client) |
| 6 | GET | /api/v1/leave/types | List leave types | leave.controller.ts:67-71 | any |
| 7 | POST | /api/v1/leave/substitutions | Assign substitute | leave.controller.ts:73-77 | org admin (client) |
| 8 | GET | /api/v1/leave/substitutions/teacher/:id | Substitutions for teacher | leave.controller.ts:79-83 | substitute teacher |
| 9 | GET | /api/v1/leave/calendar | Approved leave in range | leave.controller.ts:85-91 | any |
(planned) — IMPLEMENTATION_PLAN.md:153-161 lists the same nine endpoints;
no additional leave endpoints are planned in that doc.
2. Request bodies
| Endpoint | DTO | Fields |
|---|---|---|
| 1 | CreateLeaveRequestDto (create-leave-request.dto.ts:4-21) | leaveTypeId (mongoId, req), startDate (ISO date, req), endDate (ISO date, req), reason? |
| 3 | LeaveDecisionDto (leave-decision.dto.ts:9-17) | action (approve|reject, enum :4-7, req), note? |
| 5 | CreateLeaveTypeDto (create-leave-type.dto.ts:4-28) | code (str, req), name (str, req), daysPerYear (int ≥ 1, req), carryForward? (bool, default false), maxCarryForward? (int ≥ 0) |
| 7 | AssignSubstitutionDto (assign-substitution.dto.ts:4-37) | leaveRequestId, substituteTeacherId, classId, subjectId (mongoIds, req), date (ISO, req), startTime, endTime (str HH:mm, req), notes? |
3. Response shapes
- 2/3 → created/updated
LeaveRequestdocument:_id, tenantId, userId, leaveTypeId, startDate, endDate, daysRequested, reason?, status, decidedBy?, decidedAt?, decisionNote?, createdAt, updatedAt, version(schemaleave-request.schema.ts:16-48+BaseSchemaaudit fields). - 2 (list) →
LeaveRequest[]sortedcreatedAtdesc (leave.service.ts:168). - 4 →
LeaveBalanceEntry[]:{leaveTypeId, code, name, daysPerYear, carriedForward, daysUsed, daysRemaining}(leave.service.ts:66-74). - 6 →
LeaveType[]sortedcodeasc (leave.service.ts:216). - 8 →
Substitution[]sorteddateasc (leave.service.ts:275-280). - 9 →
LeaveRequest[](approved only) sortedstartDateasc (leave.service.ts:288-295); default range = current month (:283-287).
4. Error contract (all 4xx/5xx in standard envelope, 00-shared/07)
| HTTP | Trigger | Message | Source |
|---|---|---|---|
| 400 | endDate < startDate | endDate must be on or after startDate. | leave.service.ts:133-134 |
| 401 | missing/invalid JWT | — | leave.controller.ts:27 |
| 404 | unknown leave type | Leave type not found. | leave.service.ts:129 |
| 404 | unknown request | Leave request not found. | :174 |
| 404 | requester has no teacher record | No teacher record found for the leave requester. | :236-237 |
| 409 | request not pending | Leave request is already <status>. | :175-178 |
| 409 | self-decision | You cannot decide your own leave request. | :179-180 |
| 409 | approve when balance insufficient | Insufficient leave balance. | :188-189 |
| 409 | substitution on non-approved request | Leave request must be approved before assigning a substitution. | :227-230 |
| 409 | substitute slot clash | Substitute teacher already assigned in this time slot. | :247-248 |
| 11000* | duplicate leave-type code (Mongo unique (tenantId, code)) | not mapped server-side — gap | leave-type.schema.ts:29 |
*Surfaces as a generic 500-class error today; client maps duplicate code from
the raw key error until server adds a mapper.
5. Events emitted (side effects)
| Event | Payload (events/leave-events.ts) | Emitted at |
|---|---|---|
LeaveRequested | leaveRequestId, userId, leaveTypeCode, startDate, endDate, daysRequested (:1-8) | leave.service.ts:154 |
LeaveApproved / LeaveRejected | leaveRequestId, userId, leaveTypeCode, startDate, endDate, action, note? (:10-18) | :210 |
SubstitutionAssigned | substitutionId, absentTeacherId, substituteTeacherId, classId, date (:20-26) | :271 |
Consumers: Notifications (planned, IMPLEMENTATION_PLAN.md:163); audit.
6. Query semantics
- List (2):
status∈ enum (pending\|approved\|rejected\|cancelled,leave-request.schema.ts:7-12);userIdonly effective for admins (leave.service.ts:167). Non-admin always receives own rows only. - Calendar (9):
from/toISO dates; overlap querystartDate ≤ to AND endDate ≥ from(:291-292).
7. Gaps (no endpoint today)
Cancel request (cancelled status unreachable) · edit/withdraw pending
request · request detail GET /:id · leave-type update/delete · substitution
status transitions (completed/cancelled) · balance for "self" convenience
(requires own userId) · leave.* RBAC permissions
(permissions.constants.ts:1-97 has none).