geocrop-platform./apps/nextgen/.harness/changelogs/2026-06-16-teacher-take-att...

65 lines
5.6 KiB
Markdown

# Deliverable — 2026-06-16 — fix(teacher-take-attendance-wiring)
## VERDICT: PASS
Scope: wire the teacher dashboard → take-attendance flow end to end. Previous attempts (`feature/attendance-and-front-office-ui`, merged onto `dev` as commit `bf26801`) shipped controllers, store, page, and routes, but the wiring was broken so teachers still couldn't take attendance.
## What was broken
1. **Seed data missing**: demo teacher `teacher@school.com` was not `class_teacher_id` of any class; no subjects had `teacher_id`; no `enrollments` linked the demo student to a class. Result: teacher dashboard showed 0 classes / 0 students.
2. **Missing endpoint**: `GET /api/classes/:id/students` returned 404, so the `AttendanceTake` page could not load its roster.
3. **Teacher scope not applied**: `GET /api/classes` returned every class to a teacher, but `GET /api/dashboard/teacher` filtered to owned classes → mismatch.
4. **No ownership check on `POST /api/attendance/bulk`**: any teacher could write attendance for any class.
5. **Dashboard quick action wrong target**: "Take Attendance" navigated to `/attendance` (overview) instead of `/attendance/take` (the page that actually marks).
6. **Pre-existing schema drift in `transport.controller.js`**: SELECTs referenced `v.vehicle_number` / `r.route_title` / `ta.user_id`, none of which exist in the current schema — the Transport tab crashed with 500.
## Changes
| File | Change |
|---|---|
| `server/src/database/init.js` | Made seed fully idempotent; assign `class_teacher_id` for all 5 classes; assign `subjects.teacher_id`; enroll 5 demo students into Grade 1 A; add 4 more demo students; seed hostel/rooms/room_assignments (Boys Hostel A, 4 students); seed transport (1 route + 4 allocations); seed clubs (Chess Club + Football Team) and 4 memberships; backfill `parent_students` to all 5 students. |
| `server/src/controllers/classes.controller.js` | Added teacher scope to `GET /api/classes` (auto-filters to `class_teacher_id = me OR subjects.teacher_id = me` for the teacher role when `scope=mine` is passed or no `class_teacher_id` filter is given). Added `GET /api/classes/:id/students` (flat array, with teacher-ownership check). |
| `server/src/controllers/attendance.controller.js` | `POST /api/attendance/bulk` now (a) requires and validates `class_id` for the teacher role, (b) verifies the teacher owns or teaches the class, (c) uses the body's `class_id`/`subject_id` for the new INSERT (the previous code only used `record.class_id` which was always undefined). |
| `server/src/controllers/transport.controller.js` | Fixed pre-existing column drift in `GET /api/transport/routes` and `GET /api/transport/allocations` (`v.vehicle_number` → `v.registration_number`, `r.route_title``r.name`, `ta.user_id``ta.student_id`). |
| `client/src/pages/dashboard/TeacherDashboard.tsx` | Quick action "Take Attendance" now navigates to `/attendance/take` (was `/attendance`). |
## Evidence (smoke test as `teacher@school.com / teacher123`)
```
GET /api/auth/login → JWT (role=teacher, id=3)
GET /api/classes → [Grade 1 A] (1 class, student_count=5, subject_count=3)
GET /api/classes/1/students → 5 students (roll 01-05)
GET /api/attendance?class_id=1&date=2026-06-16 → [] (no rows yet, expected)
GET /api/dashboard/teacher → myClasses=1, mySubjects=2, totalStudents=5, todaysAttendance={0,0,0}
GET /api/hostels → [Boys Hostel A] (capacity 100, occupied 5)
GET /api/clubs → [Chess Club (5 members), Football Team]
GET /api/transport/routes → [Avondale Route] (after transport controller fix)
GET /api/club-attendance/roster?club_id=1&date=2026-06-16 → 5 students
GET /api/hostel-attendance/roster?hostel_id=1&date=2026-06-16 → 4 students
GET /api/transport-attendance/roster?route_id=1&date=2026-06-16 → 4 students
POST /api/attendance/bulk → {success:true, inserted:5, updated:0, total:5} (class context)
```
## How to verify in the browser
1. Reset DB: `cd server && rm -f data/school.db data/school.db-shm data/school.db-wal && node src/database/init.js`
2. Start server: `node src/index.js` (port 3001)
3. Build client: `cd ../client && npm run build` (Vite serves the dist via the server's SPA fallback)
4. Log in at `http://localhost:3000` as `teacher@school.com / teacher123`
5. On `/dashboard/teacher`: greeting, "5 students", "Grade 1 A" in My Classes, 0/0/0 in Today's Overview (clean state).
6. Click "Take Attendance" → land on `/attendance/take` (no more 404).
7. Pick "Class" tab → select "Grade 1 A" → see 5 students → mark P/A/E → Submit → success banner.
## Out of scope (deliberately not touched)
- `transport.controller.js` legacy POST routes (pre-existing schema drift in writes). The new `transport-attendance.controller.js` is what the page uses, and it works.
- `hostel.controller.js` legacy `POST /hostels/:id/rooms/attendance/bulk` (admin-only, also uses `room_id` schema correctly). Page uses the new `hostel-attendance.controller.js`.
- `clubs.controller.js` legacy `POST /clubs/attendance/bulk` — admin/teacher/clubs_head only; uses old column `user_id` (but bulk endpoint itself isn't called by the take-attendance page).
- The `Mock data` injector in `attendance.controller.js` line 138 — kept as-is (only fires for `?student_id=` lookups on student/parent dashboards).
## Risk
- Seed is idempotent, so re-running `npm run db:init` is safe. Existing data is untouched.
- New students created in this seed (`student2-5@school.com`) all use password `student123` to match the existing convention.
- The 3 new teacher accounts (`teacher2@school.com`, `teacher3@school.com`) also use `teacher123` and are class_teacher of Grade 2/3, 4/5 respectively.