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

StudyLyon — multi-tenant ERP / School Management API. This package designs the Teachers module client (Flutter, forward-looking spec) against the implemented NestJS backend. All endpoints, DTO fields, schemas, events, and business rules are derived directly from src/modules/teachers/**, src/modules/academics/** (subject assignments), src/modules/staff/**, src/modules/rbac/**, and src/infrastructure/**. No feature is invented; gaps are flagged in Assumptions & Open Questions and marked (planned) / (proposed).


1. Purpose

Teachers is the teaching-profile domain. It answers "who teaches here" for an institution: the employment record of every instructor (linked to a User identity), their department/designation, employment status, qualifications, the subjects they teach and the classes they are class-teacher for, plus — via the subject_assignments collection owned by the Academics module — the per-academic-year matrix of which teacher teaches which subject in which class.

ResponsibilitySource
Teacher profile CRUD (create/read/update/deactivate)teacher.controller.ts:24-38
Duplicate guards (userId, employeeNumber) on createteacher.service.ts:27-38
Soft-delete (deactivate) with isDeleted flagbase.repository.ts:68-74, teacher.service.ts:95-106
Employment status enum (active/inactive/on_leave/terminated)teacher.schema.ts:7-12
Subject ↔ class ↔ academic-year teaching matrixsubject-assignment.controller.ts:21-38, subject-assignment.schema.ts:9-19
Denormalized subject/class-teacher references on profileteacher.schema.ts:44-48
Domain events → in-app notification + audit + search indexteacher.service.ts:40-50,84-91,98-105; event-queue-map.ts:31-33; search-indexer.service.ts:10,17,24
Teacher count KPI for dashboarddashboard.service.ts:32,52

2. Module boundary: Teachers vs Staff

The backend splits "people" into two parallel profile documents that share the same shape for employment basics (userId, employeeNumber, departmentId, designationId, joiningDate) but diverge on domain specifics:

AspectTeacher (teachers)Staff (staff)
Blueprint identity"Teaching profile." (COLLECTIONS.md:1418-1422)"Non-teaching employees." (blueprint 04-Modules/Staff.md:3)
Employment fieldsemploymentStatus, qualification, experienceYears (teacher.schema.ts:28-42)employmentType, salaryGrade (staff.schema.ts:35-47)
Academic linkagesubjects[], classTeacherFor[] (teacher.schema.ts:44-48)none
Unique constraintstenantId+userId, tenantId+employeeNumber (teacher.schema.ts:56-57)tenantId+userId, tenantId+employeeNumber (staff.schema.ts:57-58)
CRUD surfaceteacher.controller.ts:24-38staff.controller.ts:24-38
EventsTeacherCreated/Updated/DeletedStaffCreated/Updated/Deleted
Reference catalogsdepartments/designations owned by Staff module (Staff.md:59)departments/designations owned here
Status field nameemploymentStatus (enum EmploymentStatus)status (enum StaffStatus) — same 4 values

A single users record may own a teacher profile and a staff profile (e.g. a teacher who also works the front office): RELATIONSHIPS.md:32-33 ("A single users record may own multiple profiles"). Both can even coexist with the same userId because they are separate collections with separate uniqueness scopes. The client must treat them as two different records — the Teachers screen never renders Staff records, and vice versa.

3. Teacher lifecycle (end-to-end)

User account created (Users module)
   → Teacher profile created   POST /teachers          [TeacherCreated → in-app]
      → Subjects + classes assigned (two mechanisms, §4)
      → Day-to-day teaching (timetable, attendance, homework — other modules)
      → Profile edited          PATCH /teachers/:id    [TeacherUpdated → audit]
      → Deactivated             DELETE /teachers/:id   [TeacherDeleted → audit]
         (soft-delete: isDeleted=true, queries exclude; PLAN.md:146)

Deactivation is permanent from the client's perspective — there is no re-activation endpoint. DELETE sets isDeleted: true + deletedAt + deletedBy (base.repository.ts:68-74); every subsequent query excludes the record (base.repository.ts:20-30).

4. Subject & class assignment — two mechanisms (important)

The backend has two parallel, non-synchronized ways to link a teacher to subjects/classes:

  1. Profile arraysteacher.subjects[] (ref Subject) and teacher.classTeacherFor[] (ref Class), set via POST /teachers or PATCH /teachers/:id (create-teacher.dto.ts:44-52, teacher.schema.ts:44-48). These are denormalized profile data — there is no server logic that reads them for scheduling.
  2. subject_assignments collection — one document per (teacher, subject, class, academicYear) triple: subject-assignment.schema.ts:9-19, managed by POST /subject-assignments, GET /subject-assignments/by-class/:classId, GET /subject-assignments/by-teacher/:teacherId, DELETE /subject-assignments/:id (subject-assignment.controller.ts:21-38). This is the authoritative teaching matrix per academic year (RELATIONSHIPS.md:82-92).

Additionally classes.classTeacherId exists (class.schema.ts:28) as a third class-teacher reference. The three stores are not kept in sync by any code — the client must decide which is canonical for which surface (see OQ-4).

5. Permissions reality (read carefully)

  • permissions.constants.ts contains no teacher.* permissions — the teachers endpoints carry none of staff.*, department.manage, designation.manage either (permissions.constants.ts:19-24).
  • TeacherController is guarded only by @UseGuards(JwtAuthGuard) (teacher.controller.ts:18-21). The global RbacGuard (app.module.ts:131) passes any request that carries no @Roles/@Permissions metadata (rbac.guard.ts:29).
  • The default teacher role ships with ['student.read','attendance.mark','attendance.edit'] (role.schema.ts:26-32) — nothing that lets a teacher read their own profile via GET /teachers/:id is gated server-side; the client must enforce "teacher sees own record only" in UI/routing (OQ-1).
  • docs/user-flows/END_TO_END_USER_FLOWS.md:751 anticipates teacher.* permissions ("✅ Admin, Read own — Teacher") — this is a doc-only contract, not implemented.

6. Events & side-effects

EventEmitted atQueue routeSide-effect
TeacherCreatedteacher.service.ts:40-50 (payload {teacherId, employeeNumber})in-app / job teacher-created (event-queue-map.ts:31)In-app notification (inapp.worker.ts:46-53); search index Teacher (search-indexer.service.ts:10)
TeacherUpdatedteacher.service.ts:84-91 (payload {teacherId})audit-write / log-teacher-updated (event-queue-map.ts:32)Audit log; search re-index
TeacherDeletedteacher.service.ts:98-105 (payload {teacherId})audit-write / log-teacher-deleted (event-queue-map.ts:33)Audit log; search index removal (search-indexer.service.ts:52-59)

Two derivable gaps (see 14-QA): PLAN.md:36 promises "→ email queue → welcome email" but the actual mapping sends TeacherCreated to the in-app queue, not emails; and inapp.worker.ts:47 stores type: 'TeacherCreated' into a notification whose NotificationType enum only admits email_verified|password_reset|verification_resent|welcome (notification.schema.ts:7-12) — an enum-mismatch risk.

7. Dependencies

DependencyRoleSource
Users moduleidentity link (userId → firstName/lastName/email)user.schema.ts:17-29; RELATIONSHIPS.md:22-30
Staff module (Academics-owned)departments, designations catalogsstaff.controller.ts (departments/designations controllers), department.schema.ts:9-19
Academics modulesubjects, classes, academic_years, subject_assignmentssubject.schema.ts:9-34, class.schema.ts:9-34, academic-year.schema.ts:14-32
Academics: class teacherclasses.classTeacherIdclass.schema.ts:28
Timetable moduleteacher schedule (GET /timetable?teacherId=)timetable.controller.ts:21-27, timetable.service.ts:55-60
Dashboard moduleteacher count KPIdashboard.service.ts:32,52
Leave modulesubstitute-teacher flows reference teacherRepoleave.service.ts:232-277
BullMQ in-app / audit-writeevent side-effectsqueue.constants.ts:5,11, event-queue-map.ts:31-33
Mongo collectionsteachers, subject_assignments, subjects, classes, academic_years, departments, designationsCOLLECTIONS.md:1418,1644

8. Business goals

GoalMeasure
Create a teacher in < 1 min of form time2 server calls max (user exists → create profile)
No duplicate teachers409 on duplicate userId / employeeNumber (teacher.service.ts:27-38)
Assignment matrix accurate per academic yearsubject_assignments scoped by academicYearId in every query (subject-assignment.service.ts:19-31)
Deactivation is safe & reversible by opssoft-delete only; nothing is physically removed
Tenant isolation structuralevery repo call injects tenantId + isDeleted:false (base.repository.ts:20-30)

9. Success metrics

  • Teacher creation → visible in list + dashboard count ≤ 3 s after submit.
  • 0 duplicate employeeNumber errors for > 99% of form flows (client validates against existing list + server 409 handles races).
  • Assignment editor: no silent conflicts — server has no conflict check on POST /subject-assignments (subject-assignment.service.ts:13-17) so the client must surface duplicates itself (OQ-2).
  • Deactivate flow: confirmation dialog → success in 1 call, teacher disappears from all lists (soft-delete) but audit/search retention honored.

10. Edge cases (derived)

  • Duplicate userId → 409 "Teacher profile already exists for this user." (teacher.service.ts:29-31).
  • Duplicate employeeNumber → 409 "Employee number "X" already exists." (teacher.service.ts:35-38).
  • GET /teachers/:id unknown id → 404 "Teacher not found." (teacher.service.ts:56).
  • DELETE unknown id → 404 (softDelete returns false → throw) (teacher.service.ts:96-97).
  • PATCH unknown id → 404 via findById pre-check (teacher.service.ts:81).
  • GET /subject-assignments/by-teacher/:teacherId without academicYearId → filter is {teacherId, academicYearId: undefined} → empty result (query param is required in practice though not validated) (subject-assignment.service.ts:26-31).
  • DELETE /subject-assignments/:id unknown id → 404 "Assignment not found." (subject-assignment.service.ts:34-35).
  • PATCH /teachers/:id cannot change userId (not in UpdateTeacherDto) — identity link is immutable via this API.
  • sort and q params are accepted by PaginationQueryDto but not applied in TeacherService.find (teacher.service.ts:66-78) — client-side search/sort needed or backend enhancement (OQ-3).

11. Assumptions (module)

  • Client is forward-looking: backend is complete for this module; these docs are the UI-side spec, consistent with shared ledger A1 (00-shared/12) — PRD lists native mobile as a later-phase, read-only companion; this package specs a full client per user instruction.
  • subjects[]/classTeacherFor[] arrays and subject_assignments are treated by the client as complementary: profile arrays render as badges/profile data; the assignments matrix drives the per-year schedule/assignment surfaces (OQ-4 for sync).
  • No bulk-import surface exists for teachers (BulkModule exists at app.module.ts:59 but teacher import is not implemented) → bulk import is (planned), not specced as v1.
  • Teacher photo/avatar: no photo field exists on Teacher or User in scope — avatar rendering uses initials fallback (AppAvatar, 03_Component_Library.md).

12. Open questions (module-level)

#ItemImpact
OQ-1No teacher.* permissions exist and no endpoint carries RBAC metadata — who may view/edit which teacher? Is a self-service "my profile" endpoint (GET /teachers/me) planned?Screen access rules, permission UI
OQ-2POST /subject-assignments has no duplicate/conflict guard (subject-assignment.service.ts:13-17) — duplicate (teacher,subject,class,year) is storable. Client-side dedup only, or backend unique index?Assignment editor UX
OQ-3sort/q ignored by TeacherService.find (teacher.service.ts:66-78) — implement client-side filter/sort or extend backend?List screen
OQ-4Three class-teacher sources (teacher.classTeacherFor[], classes.classTeacherId, none in assignments) — which is canonical for "class teacher" display?Detail tabs
OQ-5Deactivation doesn't check active assignments/timetable/substitutions — deactivating a teacher with a live schedule is allowed. Guard added where?Deactivate dialog copy
OQ-6TeacherCreated in-app notification writes a type outside the NotificationType enum (notification.schema.ts:7-12 vs inapp.worker.ts:47) — likely DB validation failure; needs backend fix before notifications render.Notification UX