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 (Academics Module)

StudyLyon — multi-tenant ERP / School Management API. This package designs the Academics module client (Flutter, forward-looking spec) against the implemented NestJS backend. All endpoints, DTO fields, schemas, indexes, business rules, and wire contracts are derived directly from src/modules/academics/**, src/database/base.*, src/common/dto/pagination-query.dto.ts, studylyon-blueprint/03-Database/*, 04-Modules/*, PLAN.md, and docs/IMPLEMENTATION_PLAN.md. No feature is invented; gaps are flagged in the Assumptions & Open Questions section.


1. Purpose

Academics is the structural backbone of StudyLyon. It defines the five reference entities every operational module hangs off:

EntityCollectionMeaningSource
Academic Yearacademic_yearsA session (e.g. "2026–2027") with dates, status, isCurrent flagschemas/academic-year.schema.ts:13
GradegradesA year level ("Grade 1", "Grade 10"), optional per-year, displayOrderschemas/grade.schema.ts:7
SectionsectionsA division of a grade ("A", "B", "C")schemas/section.schema.ts:7
ClassclassesThe concrete teaching unit: year + grade + section (e.g. "Grade 10 - A")schemas/class.schema.ts:7
SubjectsubjectsMaster catalog (code, marks configuration, credits)schemas/subject.schema.ts:7
Subject Assignmentsubject_assignmentsTeacher ↔ Subject ↔ Class ↔ Year bindingschemas/subject-assignment.schema.ts:7

1.1 Why it is the backbone

Downstream modules reference these entities by ObjectId and are meaningless without them (verified in source):

  • Timetable requires classId, subjectId, teacherId, academicYearId (timetable/schemas/timetable.schema.ts:18-40).
  • Attendance requires classId (attendance/schemas/attendance.schema.ts:28-29).
  • Students enrollment binds to classId per year (blueprint 04-Modules/Students.md:31).
  • Homework / Exams / Results / Fees consume classes and subjects as reference data (blueprint 04-Modules/Exams.md:51, 03-Database/RELATIONSHIPS.md §4, §6).

The canonical hierarchy (blueprint 03-Database/RELATIONSHIPS.md:50-61):

organizations
  └─ academic_years (many, per tenant)
        └─ grades (many)
              └─ sections (many)
                    └─ classes (many)   via gradeId + sectionId
                          ├─ students (many via class_enrollments)
                          ├─ timetables (many)
                          └─ homework (many)

subject_assignments binds the teaching staff into this tree (03-Database/RELATIONSHIPS.md:84-92):

teachers (1)
  └─ subject_assignments (many)   via teacherId
        ├─ subjects (via subjectId)
        └─ classes (via classId)

2. Business goals

GoalMeasureEvidence
One-click year rolloverPATCH /academic-years/:id/set-current toggles exactly one isCurrent per tenant (services/academic-year.service.ts:69-83)blueprint rule: "Only one academic year can be active per organization" (COLLECTIONS.md:1023)
Zero name collisions on master dataunique-per-tenant checks: academic-year name (academic-year.service.ts:25-29), grade name (grade.service.ts:21-23), subject code + DB unique index (subject.service.ts:21-25, subject.schema.ts:38)409 DUPLICATE_RESOURCE
Ordered, predictable grade ladderdisplayOrder sort default on grades (grade.service.ts:39); index {tenantId, displayOrder} (grade.schema.ts:27)
Full multi-tenant isolationevery query tenant-scoped by BaseRepository.scopedFilter() (database/base.repository.ts:20-30)
Downstream consistencytimetable/attendance filter by classId/academicYearId; changes here propagate to their lists (timetable.service.ts, attendance.schema.ts:63)

3. User goals

  • Organization admin: set up the year, grade ladder, sections, subjects once; flip the current year at rollover; see the whole structure at a glance.
  • Academic coordinator: build grades → sections → classes, assign subjects and teachers per class per year, catch conflicts before timetabling starts.
  • Teacher: see which classes/subjects they teach in the current year; use class lists to find their sections.
  • Student / parent (read-only): browse the current year's structure — grade, section, class name, subject list — to understand where they sit.

4. Stakeholders

Platform operator, institution admins, academic coordinators, teachers, students, parents, timetable/attendance/homework/exam feature owners (consumers of this reference data), QA + design + engineering.

5. Why this exists

Every school runs on a fixed academic calendar and ladder. StudyLyon is multi-tenant with heterogeneous school structures (per blueprint COLLECTIONS.md:925-969); a tenant-defined year/grade/section/class tree is the precondition for enrollment, attendance, timetable, exams, and fees. Without this module those modules cannot target a student's actual teaching unit.

6. Dependencies

DependencyRoleSource
Auth (JWT)every endpoint guarded by JwtAuthGuardacademics.module.ts:4,46; all controllers @UseGuards(JwtAuthGuard)
RBAC permissionsgap: no academics.* permission exists in ALL_PERMISSIONS — only JWT presence is enforced todayrbac/permissions.constants.ts:1-97 (see OQ-1)
TenantContextServicetenant scoping + platform-admin bypassrepositories/*.ts, database/base.repository.ts:20-30
Mongo indexes{tenantId, code} unique on subjects; per-entity tenant+ref indexessubject.schema.ts:38, class.schema.ts:38-40
Timetable / Attendance / Students / Homework / Examsconsumers of class/subject/year referencestimetable/schemas/timetable.schema.ts:18-40, attendance/schemas/attendance.schema.ts:28-29

7. Success metrics

  • Year setup (year → grade → section → class → subject → assignment) completed in < 15 min for a 12-grade school on a mid-range device.
  • Zero duplicate master records per tenant (server 409s surfaced correctly, never silently overwritten).
  • Hierarchy browse renders 12 grades × 4 sections × 40 classes with no jank (long-list budgets per 00-shared/10 §1).
  • Conflict surface (duplicate class name, teacher double-booked) visible before timetable creation — flagged (planned) since the API has no conflict endpoint today (OQ-4).

8. Edge cases

  • Duplicate academic-year name → 409 "Academic year "X" already exists." (academic-year.service.ts:27-28); duplicate grade name → 409 (grade.service.ts:23); duplicate subject code → 409 + unique index fallback (subject.service.ts:23-24, subject.schema.ts:38).
  • Classes/sections/assignments have no duplicate guard in code — duplicate (gradeId, sectionId, name) classes and duplicate assignments are possible (OQ-4): class.service.ts:16-18, subject-assignment.service.ts:13-17.
  • Soft delete everywhere (BaseRepository.softDelete, base.schema.ts:20-27) — lists hide deleted docs automatically; no cascade — deleting a grade leaves orphan sections/classes behind (OQ-3, RELATIONSHIPS.md:134-138).
  • Grade/section/class status is a free string (schema default 'active'), not an enum — client must treat any non-'active' value as inactive (grade.schema.ts:21-22, section.schema.ts:24-25, class.schema.ts:33-34).
  • set-current un-flags every isCurrent then flags the target and sets status: active (academic-year.service.ts:72-79); the target may have had status: upcoming or archived.
  • Grade academicYearId is optional in both schema and DTO — grades can exist outside any year (grade.schema.ts:9-10, create-grade.dto.ts:5-8); class academicYearId is required (class.schema.ts:9-10).
  • Subject marks are independent ints — no cross-field validation (theory+practical vs maximum, passing ≤ maximum) in DTO or service (create-subject.dto.ts:18-46, OQ-5).
  • UpdateSubjectDto cannot change theoryMarks/practicalMarks (update-subject.dto.ts:4-42).
  • Assignments have no GET /subject-assignments/:id and no update endpoint — remove + recreate is the only correction path (subject-assignment.controller.ts:19-39).
  • class.campusId has ref: 'Class' in the schema (likely intended Campus) (class.schema.ts:12-13) and roomId is a free string, not a Room ref (class.schema.ts:30-31) — client renders these as opaque IDs/strings (OQ-6).
  • Academic years list default sort createdAt: -1 (newest first) (academic-year.service.ts:47); grades default displayOrder: 1 (grade.service.ts:39); classes/sections/subjects/assignments are unsorted (insertion order) (class.service.ts:44-48, section.service.ts:41-48, subject.service.ts:35-47).

9. Assumptions (module)

  • Mobile client is forward-looking: the backend is complete; this package is the UI-side spec against real endpoints.
  • PRD / roadmap context: native mobile apps are out of Phase 1 scope (PRODUCT_REQUIREMENTS_DOCUMENT.md:144); roadmap Phase 3 = read-only companion — these docs still spec the full client per global decision A1 (00-shared/12 A1). Academics admin/coordinator editing is speced for tablet/desktop-first; phone gets read + quick actions.
  • The coaching-extension plan (docs/IMPLEMENTATION_PLAN.md §6.2.1: sections → batches, subjectCategory, classType, batchId fields) is not in code; all of it is marked (planned) and never shown as implemented.
  • The wire contract follows the fixed envelope; academics endpoints return raw Mongoose documents inside data (no field whitelisting/renaming at the API layer) — the client maps _id, __v, timestamps explicitly (base.schema.ts:7-35).
  • No pagination helper exists on assignments/by-class/by-teacher lookups — they return bare arrays (subject-assignment.service.ts:19-31).

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

#ItemImpact
OQ-1No academics.* permissions in ALL_PERMISSIONS (permissions.constants.ts:1-97) and no RbacGuard on any academics controller — any authenticated user (student/parent) can create/update/delete structure. When are perms + guard added?Screen gating, hidden CTAs, client permission map
OQ-2No tenant-level unique constraint on academic-year name / grade name (service check only, race-prone); subjects have a DB unique index. Add compound unique indexes?409 UX, concurrency
OQ-3Deleting a grade/section/class is a bare soft-delete with no cascade and no integrity check — orphan sections/classes/assignments/attendance refs are possible. Backend cascade job (planned)?Delete confirm copy, orphan handling
OQ-4No duplicate-class / duplicate-assignment validation; no conflict endpoint for teacher double-booking or class-name reuse per year. Front-end only, or backend validation?Conflict UI, error copy
OQ-5Subject marks fields have no cross-field validation (theory+practical ≤ maximum, passing ≤ maximum); update cannot edit theory/practical marks. Backend fix planned?Form validation messaging
OQ-6class.campusId ref points at Class (typo for Campus?) and roomId is a free string — no campus/room modules exist yet in code. Resolve before linking campus/room pickers.Campus/room pickers (planned)
OQ-7PaginationQueryDto.q (global search) is parsed but never used by any academics service (services filter {} only) — server-side q search (planned).Client search UX

11. Glossary (this module)

TermMeaning
Academic Yearacademic_years doc; status ∈ `upcoming
Gradegrades doc; level name + optional code + displayOrder; optional per-year binding
Sectionsections doc; division of a grade (name A, B, …), own capacity/teacher/room
Classclasses doc; the teaching unit year + grade + section + name; required refs to year/grade/section
Subjectsubjects doc; catalog entry with code (unique per tenant) and marks config (max/passing/theory/practical)
Subject Assignmentsubject_assignments doc; teacher×subject×class×year tuple
set-currentPATCH /academic-years/:id/set-current — promotes one year; demotes all others
Envelope{success,message,data,meta?,timestamp,requestId} (00-shared/07 §2)