Skip to content

feat(leaves): add student leave request and approval workflow (fixes #253) - #258

Open
MOHITKOURAV01 wants to merge 2 commits into
Sitaram8472:mainfrom
MOHITKOURAV01:feat/issue-253-leave-management
Open

feat(leaves): add student leave request and approval workflow (fixes #253)#258
MOHITKOURAV01 wants to merge 2 commits into
Sitaram8472:mainfrom
MOHITKOURAV01:feat/issue-253-leave-management

Conversation

@MOHITKOURAV01

Copy link
Copy Markdown
Contributor

What this does

Closes #253.

Attendance in EduStream was one-directional: a teacher marked a student Present or Absent and that was the end of it. A planned absence was indistinguishable from truancy, requests happened over the phone with no record, and teachers had no queue of what was waiting on them. This PR adds a proper leave request and approval workflow.

Backend

File What it adds
backend/models/LeaveRequest.js Type/reason/date range, a server-derived inclusive totalDays, half-day handling, a back-dating grace window, attachments, audit fields, and an ALLOWED_TRANSITIONS table driving canTransition() / decide()
backend/controllers/leaveController.js Submission, own-requests listing with a days summary, withdrawal, reviewer queue, approve/reject, admin cancellation, per-student summary, class leave calendar
backend/routes/leaveRoutes.js /api/leaves with multer for supporting documents

Endpoints

GET    /api/leaves/me                             the caller's own requests + summary
POST   /api/leaves                                submit (with optional attachments)
PATCH  /api/leaves/:id/withdraw                   withdraw your own pending request
GET    /api/leaves/:id                            one request (owner or reviewer)
GET    /api/leaves                                approval queue          (reviewer)
PATCH  /api/leaves/:id/decision                   approve or reject       (reviewer)
PATCH  /api/leaves/:id/cancel                     revoke an approved leave (admin)
GET    /api/leaves/calendar                       who is away, by class   (reviewer)
GET    /api/leaves/summary/student/:studentId     one student's history   (reviewer)

Frontend

  • pages/LeaveRequests.jsx/leaves. Students apply with a date-range picker that shows a live day count, choose a leave type, attach a doctor's note, and see status badges, the reviewer's comment and a withdraw button on anything still pending. A summary strip shows requests filed, approved days, days awaiting approval and rejections.
  • components/teacher/LeaveApprovalPanel.jsx — a new Leave Requests tab on the Teacher Dashboard: the pending queue with filters by status, type, class and student name; expand a request to read it in full, see its attachments, and — usefully — that student's leave history alongside the decision. Approve or reject with a comment.

Design notes

  • One state machine, declared as data. ALLOWED_TRANSITIONS is a table, and every status change goes through canTransition(). The lifecycle rules are in one readable place instead of scattered if statements across the controller, so adding a state later cannot silently open a hole.
  • totalDays is derived server-side in a pre-validate hook and never accepted from the client, so a request cannot claim a three-day absence is half a day. A half-day request is forced to 0.5 and must sit inside a single date.
  • Overlapping live requests are refused with a 409 naming the existing request's status, so a student cannot double-book the same days.
  • Back-dating has a grace window (7 days) rather than being banned outright — a student needs to log a sudden illness the morning after, but should not be able to rewrite last term.
  • A rejection requires a comment. Enforced in decide() on the model, so it holds no matter which endpoint calls it.
  • Every decision records reviewedBy and reviewedAt, and deciding an already-decided request returns a 409 rather than quietly overwriting the first decision.
  • A student can only read and withdraw their own requests/leaves/me takes no id, and GET /leaves/:id checks ownership before returning anything.
  • Errors are tagged. Date and lifecycle failures carry a userFacing flag so the controller returns 400 for those and keeps 500 for genuine bugs.

Verification

The rules were exercised directly:

PASS totalDays 3 (inclusive)         PASS single day = 1
PASS half day = 0.5                  PASS multi-day half day blocked
PASS reversed dates blocked          PASS back-dating beyond 7 days blocked
PASS recent back-date within grace accepted
PASS short reason blocked            PASS pending transitions correct
PASS rejection without a comment blocked
PASS approval records reviewer and timestamp
PASS second decision blocked         PASS approved leave can still be cancelled

Plus node --check on every file, routers mounting on a real Express app, a clean vite build, and no new eslint errors.

Note on Mongoose 9

The project is on mongoose@^9.4.1, which no longer passes a next callback to document middleware — a callback-style pre('validate') hook resolves without running its body, which would have left totalDays unwritten and accepted reversed date ranges. The hook is an async function; the second commit fixes this after it was caught in testing. Worth knowing for any other PR adding a model to this repo.

Compatibility

Purely additive — the existing Attendance model is untouched. server.js gains two lines, App.jsx one route, TeacherDashboard.jsx one tab.

Attendance was one-directional: a teacher marked a student absent and
that was the end of it. A planned absence was indistinguishable from
truancy, and requests happened over the phone with no record.

- Add LeaveRequest model with a derived inclusive day count, a
  back-dating grace window, and an ALLOWED_TRANSITIONS table driving a
  single canTransition guard so the lifecycle cannot be bypassed
- Add leaveController with submission, own-requests listing plus a days
  summary, withdrawal, the reviewer queue, approve/reject with a
  mandatory rejection comment, admin cancellation, a per-student summary
  and a class leave calendar
- Reject overlapping live requests for the same student with a 409
- Add /api/leaves routes with multer for supporting documents
- Add /leaves page: date-range picker with a live day count, half-day
  handling, attachments, status badges and withdrawal
- Add LeaveApprovalPanel dashboard tab with filters, an expandable
  detail view and the student's leave history alongside the decision

Fixes Sitaram8472#253
Mongoose 9 no longer passes a `next` callback to document middleware, so
the callback-style pre('validate') hook resolved without running — the
derived day count was never written and reversed or back-dated ranges
were silently accepted.

Convert the hook to an async function that throws.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: Student Leave / Absence Request workflow with teacher approval

1 participant