Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

01 — Product Overview (Students Module)

StudyLyon — multi-tenant ERP / School Management API. This package designs the Students module client (Flutter, forward-looking spec) against the implemented NestJS backend. Every endpoint, DTO field, schema, enum, event, and wire contract is derived from src/modules/students/**, src/modules/parents/**, src/modules/academics/**, src/modules/bulk/**, src/modules/users/**, src/modules/rbac/permissions.constants.ts, src/infrastructure/**, and studylyon-blueprint/04-Modules/Students.md. Nothing is invented; gaps are flagged (planned), (forward-looking), (proposed) or listed in Assumptions & Open Questions.


1. Purpose

Students is the academic heart of a school: it holds every enrolled child's academic profile, their enrollment history per class and academic year, transfers and promotions, graduation/withdrawal, archived records, uploaded documents, and the links to parents/guardians that power family portals. It is the anchor record that attendance, fees, results, library, transport and homework modules hang off.

ResponsibilitySource
Profile creation with automatic first enrollmentstudent.service.ts:55-93
Enrollment into a class for an academic year (history kept, never overwritten)student.service.ts:120-140, class-enrollment.schema.ts:7-44
Transfer between classes (guard: only active)student.service.ts:170-203
Graduate / archive / restore lifecyclestudent.service.ts:205-248
Document upload + listing per studentstudent.service.ts:250-279, student-document.schema.ts
Academic history (all enrollments, newest first)student.service.ts:281-287
Bulk CSV import / export with per-row reportbulk/bulk-import.service.ts:22-71, bulk/adapters/students-import.adapter.ts
Parent/guardian linking (many-to-many, relationship typed)parents/services/student-parent-link.service.ts, parents/schemas/student-parent-link.schema.ts
Domain events → BullMQ (in-app + audit)infrastructure/bullmq/event-queue-map.ts:28-39
Soft-delete + optimistic-version scoping (tenant-safe)database/base.repository.ts:20-74

2. The student lifecycle (backend state machine)

Status values are fixed by StudentStatus (student.schema.ts:7-13): active | inactive | graduated | transferred | archived.

                 ┌────────────────────────────────────────────┐
                 │  POST /students  (create)                   │
                 │  status := active  (always)                 │
                 │  auto-enroll: ACTIVE enrollment created     │
                 └────────────────────────────────────────────┘
                                 │
        ┌────────────────────────┼───────────────────────────┐
        ▼                        ▼                           ▼
   [active] ── transfer ──► enrollment TRANSFERRED + new ACTIVE  ──► stays [active]
   [active] ── graduate ──► status = graduated                  (409 if already graduated)
   [active] ── archive ──► status = archived                    (409 if already archived)
   [archived] ── restore ─► status = active                     (409 if already active)
   any ── DELETE /students/:id ──► soft-delete (isDeleted=true, excluded from all queries)
  • Create forces status: ACTIVE and admissionDate = today when omitted (student.service.ts:65-69), then immediately creates an ACTIVE class_enrollments row for the given classId/academicYearId (student.service.ts:71-78). Admission number is unique per tenant — duplicate → 409 (student.service.ts:56-62, student.schema.ts:68).
  • Enroll deactivates all currently-active enrollments (status: transferred, leftAt: now) and creates a fresh ACTIVE one (student.service.ts:125-140). History is append-only — previous years are never overwritten (blueprint 03-Database/COLLECTIONS.md:1674-1677).
  • Transfer is enroll + profile sync: requires current status active (else 409), then updates classId, academicYearId, optional gradeId/sectionId on the student doc (student.service.ts:170-203).
  • Graduate sets status graduated; idempotency guard → 409 when already graduated (student.service.ts:205-223). Note: graduation does not close the active enrollment — flagged in OQ-5.
  • Archive / restore toggles archivedactive with a same-state 409 guard (student.service.ts:225-248).
  • inactive / transferred statuses exist in the enum but no service method writes them — only legacy data or future use (OQ-6).
  • DELETE soft-deletes the student record only — documents, enrollments and parent links are not cascaded (OQ-7, student.service.ts:157-168).

3. Business goals

GoalMeasure
Zero duplicate admission numbers per tenantunique compound index {tenantId, admissionNumber} (student.schema.ts:68) + pre-check 409
Enrollment history is a never-overwritten audit traildeactivate-then-create pattern (student.service.ts:125-140)
Every status change is observableStudentUpdatedaudit-write queue (event-queue-map.ts:29)
Safe transfersonly active students transfer; 409 otherwise (student.service.ts:175-179)
Tenant isolation structuralBaseRepository.scopedFilter injects tenantId + isDeleted:false on every query (base.repository.ts:20-30)
Admissions in bulkCSV import with per-row error report (1000-row class in 14_QA_Checklist.md)

4. User goals

  • Admission staff: create a student (identity via an existing user), set the class, upload documents, link parents — all from the student profile.
  • Admin: keep the roster accurate (transfer, graduate, archive), import the September batch from a spreadsheet, spot duplicates.
  • Teacher: find a student quickly (search/filter), view profile, documents, academic history.
  • Parent: see linked children via GET /parents/:id/students (parent.controller.ts:38-40) — read-only; profile changes are staff-side.
  • Student (self): read own profile, academic history, documents (planned) — the backend has no student-self endpoints today (OQ-1).

5. Stakeholders

School admins, admission/reception staff, teachers, parents/guardians, students, accounts (enrollment status feeds fee invoicing), transport/hostel coordinators (transportRequired/hostelRequired flags), platform operator (tenant data).

6. Dependencies

DependencyRoleSource
Users moduleidentity (name/email/phone/avatar) — students stores academic data onlyuser.schema.ts:15-79; blueprint COLLECTIONS.md:1283-1291
Academics moduleacademicYearId, gradeId, sectionId, classId refs + dropdown source endpointsacademics/controllers/*.ts, schemas in academics/schemas/
Parents moduleguardian links (student_parent_links M2M)student-parent-link.schema.ts
Bulk moduleCSV import/export (students adapter)bulk/bulk-import.service.ts, students-import.adapter.ts
Storage providerdocument + avatar binariesshared/storage/storage-provider.ts, storage/local-storage.provider.ts:23-51
EventBus → BullMQStudentCreated/StudentUpdated/StudentDeleted → in-app + audit jobsevent-queue-map.ts:28-30
RBAC constantsstudent.read/.create/.update/.delete (note: not yet enforced — OQ-4)permissions.constants.ts:25-28
Collectionsstudents, class_enrollments, student_documents, student_parent_links, parentsblueprint 03-Database/COLLECTIONS.md

7. Success metrics

  • Enrollment round-trip (create + auto-enroll) acknowledged in < 2 s p95.
  • Zero duplicates: 409 on admission number must be prevented client-side by inline pre-check + server 409 fallback.
  • Transfer flow error rate < 1% (guard 409s are user-preventable).
  • CSV import of 1000 rows completes with a per-row report; 0 silent skips.
  • Document uploads land with correct fileId mapping; previews resolvable.
  • Parent links never orphaned (student existence checked at link time — student-parent-link.service.ts:27).

8. Edge cases (server truth)

  • Duplicate admissionNumber → 409 ConflictException (student.service.ts:56-62).
  • findById/enroll/transfer/update/remove/graduate/archive/restore/ uploadDocument on unknown id → 404 "Student not found." (student.service.ts:95-99,124,143-145,159,174,206,226,255).
  • Transfer/graduate/archive on wrong status → 409 with the current status in the message (student.service.ts:175-179,207-209,228-230).
  • Enroll while another enrollment is active → previous becomes transferred (no error; expected workflow) (student.service.ts:125-131).
  • Pagination: sort and q query params are accepted but ignored by StudentService.find (student.service.ts:104-108) — list search/sort is (planned) server-side (OQ-8).
  • CSV malformed/empty → 400; unknown entity → 404; per-row failures reported, never abort the batch (bulk-import.service.ts:31-63).
  • Duplicate email inside one CSV → UsersService.create fails → adapter falls back to the existing user (re-link) (students-import.adapter.ts:66-75).

9. Module notes

  • POST /students requires an existing userId (create-student.dto.ts:6-7) — the UI must create the user (or reuse an existing one) before creating the student. There is no "create user + student" composite endpoint.
  • Document upload has no server-side size/mime validation — only the multipart field must exist; limits are client + proxy level today (OQ-9, student.service.ts:250-271).
  • StudentCreated payload carries studentId, admissionNumber, classId (student.service.ts:85-90); it routes to the in-app queue only — no email (email worker handles only UserRegistered/PasswordResetRequested, email.worker.ts:26-42). PLAN 4.1's "StudentCreated → ParentCreated → email" chain is therefore (planned) (OQ-3).
  • RBAC: student.* permissions exist as constants (permissions.constants.ts:25-28) but the students controller applies JwtAuthGuard only (student.controller.ts:32-35); RbacGuard is not wired on these routes — permission enforcement is (planned) (OQ-4). The client should still gate UI by student.* per 00-shared/05 §9.
  • No parent.* permissions exist in permissions.constants.ts at all — the parents surface has no permission vocabulary yet (OQ-10).

10. Forward-looking & PRD notes

  • PRD: native mobile apps are Phase 3 (read-only companion) — this package is the forward-looking full client spec (shared ledger 00-shared/12 A1).
  • (planned) (server): POST /students/bulk-import under /students (docs/IMPLEMENTATION_PLAN.md:195 — today import lives at /bulk/import/students); promote endpoint + StudentPromoted event (blueprint 04-Modules/Students.md:32,43); student self-service (results/attendance read for the linked userId).
  • (forward-looking): profile photo upload at POST /users/:id/avatar (users.controller.ts:95-102) exists server-side; QR admission cards, push of StudentCreated to parents, WS live roster updates.
  • (proposed): analytics events (students.list.search, students.import.done…) per 00-shared/10 §8.

11. Assumptions (module)

  • The client treats active as the default roster filter; archived students are hidden from lists unless "include archived" is toggled (server find() returns everything not soft-deleted — filtering is client-side today, OQ-8).
  • Class dropdown data comes from GET /classes (+ by-year/:academicYearId), grades from GET /grades, sections from GET /sections/by-grade/:gradeId, academic years from GET /academic-years (academics/controllers/*.ts).
  • Identity edits (name/email/phone/avatar) are Users module screens; the Students UI shows them read-only from the linked user.
  • Multipart uploads use field name file exactly (student.controller.ts:83, bulk.controller.ts:38, users.controller.ts:96).

12. Open questions (module-level; global ledger in 00-shared/12)

#ItemImpact
OQ-1No self-service endpoints for students/parents (no "my profile", no GET /students/me). Parent view exists only via GET /parents/:id/students. When is student self-view added?Student/Parent persona screens (03, 06)
OQ-2StudentService.find accepts sort/q but ignores them — server-side search/sort/status filter planned?List screen filter behaviour (05, 12, 13)
OQ-3PLAN 4.1 chain "StudentCreated → ParentCreated → email" — no auto-parent-creation or student/parent email in code (email.worker.ts:26-42). Intended?Journey "link parent" UX
OQ-4student.* perms defined but RbacGuard not applied on students/parents/bulk controllers — enforcement when?Permission gating in UI (04, 05)
OQ-5graduate sets status but leaves the ACTIVE enrollment open — close it (leftAt) as part of graduation?Academic-history rendering
OQ-6inactive/transferred student statuses unwritable by any service method — legacy or future workflow?Status chip legend
OQ-7DELETE /students/:id soft-deletes the student only — documents/links/enrollments stay. Cascade or keep history?Deletion UX copy
OQ-8List has no status filter param — archiving hides nothing from GET /students. Client-side filtering or new query param?Roster filtering
OQ-9Document upload: no server size/type limits; student_documents has no downloadable-file endpoint (only fileId) — file retrieval via /api/v1/files/... local path (planned)Documents tab + previews
OQ-10No parent.* permission constants — how are parent CRUD routes authorized beyond JWT?Parent link UI gating

13. Glossary (this module)

TermMeaning
Studentstudents doc — academic profile only; identity in linked User
Admission numbertenant-unique identifier (ADM…), student.schema.ts:20-21,68
Enrollmentclass_enrollments row: student ↔ class ↔ academic year, with joinedAt/leftAt/status
Classclasses doc = grade + section + academic year (+capacity) — class.schema.ts
Transferenroll-into-new-class with old enrollments set transferred
Graduate/Archivestatus transitions active → graduated/archived (mutually exclusive guards)
Parent linkstudent_parent_links M2M row with relationship, guardian/priority flags
Import report{entity,totalRows,imported,failed,errors:[{rowNumber,errors}]} (import-adapter.interface.ts:14-25)
Envelope{success,message,data,meta?,timestamp,requestId} (00-shared/07 §2-3)