Skoolsewa - School Management System Docs
Developer ResourcesSubjects

Subjects Features and Flows

Complete feature list, actor journeys, state flows, business rules, edge cases, and diagrams for the school's subject catalogue and its per-class teaching assignments.

Subjects Features and Flows

1. Documentation Evidence

Source TypeFiles or DocsWhat Was Extracted
Backend doc/docs/developer/subjects/backend and the services it documents (subjects-read.service.ts, subjects-write.service.ts, class-subjects-read.service.ts, class-subjects-write.service.ts)Business behavior, transaction boundaries, cache invalidation, and row-scoping.
API doc/docs/developer/subjects/api and the controllers it documentsRoute surface, actors, permissions, and response-visible behavior.
Schemapackages/db/src/schema/school/subjects.ts, migrations packages/db/src/migrations/0017_subject_module.sql and 0018_subject_permission_grants.sqlConstraints, indexes and permission grants that drive the edge cases and error branches below.
Consumer surfacesapps/api/src/modules/mobile/student/student-portal.controller.ts, student-portal.service.ts, apps/api/src/modules/mobile/teacher/teacher-portal.controller.ts, teacher-portal.service.tsThe pupil's and teacher's own-subjects reads, audience-scoped and outside the admin permission catalogue.
Authorizationapps/api/src/modules/people/shared/people-access.service.ts, apps/api/src/modules/mobile/shared/portal-audience.ts, packages/db/src/seed/seed-auth.tsRow scope for ClassSubjects_READ, portal audience assertions, and per-role permission grants.

2. Feature Summary

FieldValue
ModuleSubjects (the school-wide subject catalogue) and ClassSubjects (one class teaching one subject) — two permission modules composed by AcademicStructureModule.
SubmoduleClassSubjects is the per-class assignment layer over Subjects; the mobile "my subjects" reads are a third, audience-scoped surface over the same two tables.
Primary user valueLets the school define what it teaches once ("Mathematics", with a default mark scheme) and then assign that same subject to as many classes as it actually runs, each with its own teacher and its own pass/max marks — so Mr Sharma's Grade 5A Mathematics and Ms Rai's Grade 5B Mathematics are two configurations of one entity, not two subjects, and every report that groups by subject groups by something real.
ActorsSuperadmin for every write on both tables — no seeded admin role exists in this database, so Subjects_CREATE/_UPDATE/_DELETE and ClassSubjects_CREATE/_UPDATE/_DELETE are superadmin-only until an operator hand-builds a custom role and grants them. Staff and teachers hold Subjects_READ and ClassSubjects_READ. Pupils hold Subjects_READ alone and read their own subjects through a dedicated audience-scoped route, never through the admin list. Guardians hold neither code. No guest or unauthenticated actor reaches any part of this module.
Main entry pointsGET/POST/PATCH/DELETE /api/subjects, GET /api/subjects/options; GET/POST /api/subjects/:publicId/assignments, GET /api/classes/:classPublicId/subjects, PATCH/DELETE /api/class-subjects/:id; GET /api/mobile/student/subjects, GET /api/mobile/teacher/subjects.
Main outputsPersisted subjects and class_subjects rows; the response DTOs behind the admin subject catalogue screen, the per-class assignment grid, a pupil's "my subjects" screen, and a teacher's "my subjects" screen.
Related docsAPI, Backend.

3. Actor Matrix

ActorCan DoCannot DoAuth RequirementNotes
Guest (unauthenticated)Nothing.Everything in this module.None — and none is accepted.Every route sits behind the global JwtAuthGuard; there is no @Public() route anywhere subjects or class_subjects are touched.
Logged-in user without the relevant permissionNothing module-specific.List, read, create, update or delete a subject or an assignment.JWT + active role, but lacking Subjects_*/ClassSubjects_*.Refused with 403 PERMISSION_INSUFFICIENT (or 403 AUTH_ACTIVE_ROLE_REQUIRED if several roles are held and none is selected).
Pupil (seeded student role)Read Subjects_READ-gated endpoints on the admin surface if reached directly; in practice reads their own subjects through GET /api/mobile/student/subjects, which needs no permission at all.List a subject's or a class's assignments (ClassSubjects_READ is not granted), see any teacher's employee code or employment status, see any classmate's teacher-identity data.JWT + active role with scopeKind: "student".The consumer route is audience-scoped by assertPortalAudience, not by the permission catalogue — see Section 5.10.
Guardian (seeded guardian role)Nothing on this module.Read the subject catalogue, read any assignment, read a ward's subjects.JWT + active role with scopeKind: "guardian".Deliberately withheld — see 12.3: a guardian holding Subjects_READ alone would be routed by the panel to the pupil-only consumer endpoint and refused there, permanently, since grants are never revoked.
Staff or teacher (seeded staff/teacher role)List/read the subject catalogue and subject options (Subjects_READ); list a subject's assignments and a class's assigned subjects (ClassSubjects_READ); a teacher additionally reads their own teaching load through GET /api/mobile/teacher/subjects.Create, update, retire, reactivate or delete a subject; create, reconfigure, suspend, revive or delete an assignment.JWT + active role holding the read codes above.ClassSubjects_READ is safe for this audience specifically because Classes_READ (already held by both roles) already exposes every class's teacher name and employee code — see the grant's own rationale in seed-auth.ts.
SuperadminEverything in this module, unconditionally.Nothing is withheld.JWT + a role flagged is_superadmin.The bypass is keyed on the boolean flag, never on a role's display name, and is the ONLY seeded role holding Subjects_CREATE/_UPDATE/_DELETE or any ClassSubjects_CREATE/_UPDATE/_DELETE code today.
Custom role holding a write codeWhatever action the granted code names — e.g. Subjects_CREATE alone permits creating a subject and nothing else.Anything outside the specific code granted.JWT + active role with that exact permission code.An operator builds this through the Roles/Permissions module; nothing in the seed produces it.
Worker/systemNo automated actor touches this module.Everything, since none exists.N/ANo job, scheduler or queue reads or writes subjects or class_subjects — the backend doc's BullMQ and Realtime sections are both "not applicable".

4. Capability Matrix

CapabilitySurfaceActorRoute/TriggerState ReadState WrittenLinked API Section
List/search the subject catalogueAdmin/Staff/TeacherSubjects_READGET /api/subjectssubjects, correlated class_subjects count—8.1
Subject options for pickersAdmin/Staff/TeacherSubjects_READGET /api/subjects/optionssubjects—8.2
Read one subjectAdmin/Staff/TeacherSubjects_READGET /api/subjects/:publicIdsubjects, correlated count—8.3
Create a subjectSuperadminSubjects_CREATEPOST /api/subjects—subjects8.4
Update/retire/reactivate a subjectSuperadminSubjects_UPDATEPATCH /api/subjects/:publicIdsubjects (locked read, reactivation collision check)subjects8.5
Delete a subjectSuperadminSubjects_DELETEDELETE /api/subjects/:publicIdsubjects, class_subjects (existence/FK check)subjects8.6
List a subject's class assignmentsAdmin/Staff/TeacherClassSubjects_READGET /api/subjects/:publicId/assignmentsclass_subjects joined to classes/grades/sections/staff/users/designations—8.7
Assign a subject to one or more classes (batch)SuperadminClassSubjects_CREATEPOST /api/subjects/:publicId/assignmentssubjects, classes, staff+designations, existing class_subjects rowsclass_subjects (insert or revive)8.8
List a class's assigned subjectsAdmin/Staff/TeacherClassSubjects_READGET /api/classes/:classPublicId/subjectsclasses (scoped resolve), class_subjects joined as above—8.9
Update a class subject assignmentSuperadminClassSubjects_UPDATEPATCH /api/class-subjects/:idclass_subjects (locked), subjects (revival precondition), sibling class_subjects (label collision)class_subjects8.10
Delete a class subject assignmentSuperadminClassSubjects_DELETEDELETE /api/class-subjects/:idclass_subjectsclass_subjects8.11
A pupil views their own subjectsMobile/consumerPupil, scopeKind: "student"GET /api/mobile/student/subjectsstudent_class_enrollments, class_subjects, subjects, classes, grades, sections, staff, users, academic_sessions—8.12
A teacher views their own subjectsMobile/consumerTeacher, live staff rowGET /api/mobile/teacher/subjectsclass_subjects, subjects, classes, grades, sections, academic_sessions—8.13

5. User-Facing Flows

5.1 List/search the subject catalogue

Summary

An administrator, staff member or teacher opens a subject picker or the subject-management screen, and the school's full or filtered subject list loads with each row's live count of active class assignments.

Preconditions

  • Valid JWT with an active role holding Subjects_READ.

Main Flow

StepActor/SystemActionResultSource
1Admin/Staff/TeacherOpens the subject list, optionally with search, type or isActive.GET /api/subjects.Controller.
2BackendChecks the Redis cache for this exact query shape.Cache hit returns immediately; a miss falls through.SubjectsReadService.findAll.
3BackendReads subjects, filtered, ordered by case-folded name then id, with classCount as a correlated subquery.Rows mapped to SubjectDto.Service.
4BackendWrites the result to cache for 300 seconds.Next identical query is served from cache.Service.

Sequence Diagram

Branches and Edge Cases

BranchConditionBehaviorError/Result
search suppliede.g. ?search=g%Matched against name and code with ILIKE, with the pattern escaped — a literal % or _ in the search text filters as a literal, never as a wildcard.Rows whose name or code contains the term.
type/isActive filterse.g. ?type=optional&isActive=falseANDed into the WHERE clause.Narrowed result set.
pagination=falseReading the whole catalogue for a select boxAllowed, capped at PaginationUtil.UNPAGINATED_HARD_CAP (1000) — the catalogue carries no person data.Full (capped) list in one response, no count/currentPage/totalPage in the envelope.
Cache hitSame query shape within 300 secondsServed without touching Postgres.Identical response, including a classCount that may be up to 300 seconds stale relative to a concurrent assignment write.
Redis unavailableCache read/write throwsFails soft — treated as a miss on read, a no-op on write, both logged as a WARN.Request still succeeds, served from Postgres.
Missing permissionRole lacks Subjects_READRefused before any query runs.403 PERMISSION_INSUFFICIENT.

5.2 Subject options for assignment pickers

Summary

The class-assignment grid needs a lightweight, person-data-free list of subjects to populate its "add a subject" dropdown, always unpaginated and always capped.

Preconditions

  • Subjects_READ.

Main Flow

StepActor/SystemActionResultSource
1Admin/Staff/TeacherOpens the assignment grid's subject picker.GET /api/subjects/options.Controller.
2BackendReads active subjects (or all, with includeInactive=true) ordered by case-folded name, fetching one row past the 1000 cap.Up to 1000 rows returned; truncated flags whether more exist.SubjectsReadService.findOptions.

Sequence Diagram

Branches and Edge Cases

BranchConditionBehaviorError/Result
includeInactive omittedDefaultActive subjects only.Retired subjects excluded from the picker.
includeInactive=trueExplicitEvery subject, active or archived.Used by a screen that must show a retired subject's historical label.
Over 1000 rows existA very large catalogueRows beyond 1000 are silently withheld from data, but truncated: true names the fact rather than hiding it.Caller can decide to narrow the request; no error is raised.
Route declared before :publicIdN/A — routing orderoptions is a literal segment declared ahead of the parameterised GET /api/subjects/:publicId in the controller, so it can never be swallowed as a subject whose id is the string options.Consistent 200, never a confusing 404.

5.3 Read one subject

Summary

An operator opens a single subject's detail screen by its publicId.

Preconditions

  • Subjects_READ.

Main Flow

StepActor/SystemActionResultSource
1Admin/Staff/TeacherOpens a subject's detail view.GET /api/subjects/:publicId.Controller.
2BackendLooks up the row by publicId, including the live classCount.SubjectDto, or a 404.SubjectsReadService.loadDto.

Branches and Edge Cases

BranchConditionBehaviorError/Result
Unknown publicIdNo row with that idNot found.404 SUBJECT_NOT_FOUND.
Archived subjectisActive: falseStill readable by id — retirement does not hide a row from a direct lookup.200, isActive: false in the payload.

5.4 Create a subject

Summary

Superadmin defines a new subject the school teaches: its display name, its stable machine code, whether it is compulsory or optional by default, and the default mark scheme every new assignment inherits.

Preconditions

  • Subjects_CREATE.
  • No active subject already holds the same name (case-insensitive) or the same code.

Main Flow

StepActor/SystemActionResultSource
1SuperadminSubmits name, code, type, and optionally defaultMaxMarks/defaultPassMarks/isActive.POST /api/subjects.Controller.
2BackendTrims the name; defaults marks to 100/40 when omitted.Validated payload.SubjectsWriteService.create.
3BackendChecks passMarks <= maxMarks on the two now-known values.Passes, or refuses.Service.
4BackendInserts the row inside a transaction.New subjects row, publicId generated server-side (UUIDv7).Service.
5BackendInvalidates the entire academic-structure:subjects:* cache prefix.Every cached list/options/detail response for this module is dropped.Service.
6BackendRe-reads the just-written row inside the same connection and returns it.201 with the full SubjectDto.Service.

Sequence Diagram

Branches and Edge Cases

BranchConditionBehaviorError/Result
Duplicate nameAn active subject already has this name, case-insensitivelyRefused by the partial unique index.409 SUBJECT_NAME_TAKEN.
Duplicate codeAn active subject already has this codeRefused by the partial unique index.409 SUBJECT_CODE_TAKEN.
passMarks > maxMarksCross-column checkRefused pre-write, before the CHECK constraint would fire.409 SUBJECT_MARKS_INVALID.
Padded/control-character namee.g. " Math" or embedded newlineRefused by the DTO's regex validators before the request reaches the service.400 from the global pipe.
Same name/code as an archived subjectThe retired row's name is free againAllowed — the unique indexes are partial on is_active.201, two rows now share one label, one active and one archived.
isActive: false on createExplicitA subject can be created already retired.201, classCount: 0, no assignment possible until reactivated.

5.5 Update, retire, and reactivate a subject

Summary

Superadmin edits a subject's name, code, type, default marks, or active state. Reactivating a previously retired subject is the one transition with its own precondition.

Preconditions

  • Subjects_UPDATE.
  • The version last read must still match the row's current version.
  • Reactivation only: neither the merged name nor the merged code may belong to a different currently-active subject.

Main Flow

StepActor/SystemActionResultSource
1SuperadminSubmits a partial patch plus the version they last read.PATCH /api/subjects/:publicId.Controller.
2BackendReads the existing row inside a transaction.404 if absent.SubjectsWriteService.update.
3BackendMerges the patch onto the existing row for the passMarks <= maxMarks check.Passes, or refuses.Service.
4BackendIf isActive flips false → true, checks whether the merged name or code now belongs to a different active subject.Passes, or refuses naming the holder's code.Service.
5BackendRuns UPDATE ... WHERE public_id = ... AND version = dto.version.0 rows affected means gone-or-stale; re-reads to tell which.Service.
6BackendThe bump_row_version trigger advances version and stamps updated_at.New version.Database trigger.
7BackendInvalidates the cache prefix and returns the freshly re-read row.200 with the updated SubjectDto.Service.

Sequence Diagram

Branches and Edge Cases

BranchConditionBehaviorError/Result
Stale versionRow changed since the caller last read itZero rows affected, row still exists.409 SUBJECT_VERSION_CONFLICT.
Row deleted mid-flightZero rows affected, row no longer existsReported as absent, not as a conflict.404 SUBJECT_NOT_FOUND.
Reactivating onto a taken nameisActive: false → true, merged name matches another active subjectRefused before the write, naming the holder's code.409 SUBJECT_NAME_TAKEN_ON_REACTIVATE.
Reactivating onto a taken codeSame, for codeAs above.409 SUBJECT_CODE_TAKEN_ON_REACTIVATE.
Empty patch, version onlyCaller sends only { version }The service self-assigns name = existing.name so the UPDATE statement is non-empty and the trigger still fires — version still advances.200, no field actually changed except version/updatedAt.
Partial update crossing the marks ruleRequest sets defaultPassMarks: 120, row's existing defaultMaxMarks is 100Merged check catches what no single-field DTO validator could.409 SUBJECT_MARKS_INVALID.
Retiring a subject with active assignmentsisActive: true → false on a subject several classes still teachAllowed — retirement does not touch class_subjects.200; those classes keep teaching a now-archived subject until reassigned.

5.6 Delete a subject

Summary

Superadmin permanently removes a subject that was never actually assigned to a class, or has had every assignment removed first.

Preconditions

  • Subjects_DELETE.
  • Zero class_subjects rows reference this subject — active or suspended.

Main Flow

StepActor/SystemActionResultSource
1SuperadminRequests deletion.DELETE /api/subjects/:publicId.Controller.
2BackendResolves the row; 404 if absent.—SubjectsWriteService.remove.
3BackendCounts total and active class_subjects rows referencing it.Any total > 0 refuses.Service.
4BackendDeletes the row.Hard delete, no soft-delete flag on this table.Service.
5BackendInvalidates the cache prefix.—Service.

Branches and Edge Cases

BranchConditionBehaviorError/Result
Any assignment exists, even suspendedtotal > 0Refused, message names both the total and the active count so the operator is not refused on the strength of a number the screen never showed them.409 SUBJECT_IN_USE.
All assignments were already removedtotal === 0Delete proceeds.200, row gone, null data.
Race: an assignment is created between the pre-check and the deleteConcurrent POST .../assignmentsON DELETE restrict on the foreign key refuses the delete at the database, mapped by name.409 SUBJECT_IN_USE (same code, database-level backstop).

5.7 Assign a subject to one or more classes (batch)

Summary

Superadmin assigns one already-created subject to any number of classes in a single request — the assignment grid's "save row" and "save all" actions both go through this one endpoint, since a single-item assign is a one-element batch.

Preconditions

  • ClassSubjects_CREATE.
  • The subject exists and is active.
  • 1-100 items per request.

Main Flow

StepActor/SystemActionResultSource
1SuperadminSubmits { items: [...] } against a subject's publicId.POST /api/subjects/:publicId/assignments.Controller.
2BackendResolves the subject once; refuses if missing or retired.—ClassSubjectsWriteService.assign step 1.
3BackendResolves every classId in one query; collects CLASS_NOT_FOUND, CLASS_INACTIVE, and in-batch duplicate class errors per item, deduped on the resolved integer id.—Steps 2-4.
4BackendResolves every non-null teacherId in one query joined to designations; collects "not found" and "not a teaching designation" errors per item.—Step 5.
5BackendFills any absent type/maxMarks/passMarks from the subject's defaults, then validates passMarks <= maxMarks per item after defaulting.—Steps 5 cont., 7.
6BackendChecks for a label collision — the class already actively teaches a different subject row sharing this name or code — and checks a stale version against any existing row for (class, this subject).—Step 6.
7BackendIf ANY item failed any of the above, throws with every failure attached. Nothing is written.—Step 8.
8BackendUpserts every surviving item on (classId, subjectId): insert if absent, merge-update if present — absent fields keep the existing value, explicit null on teacherId clears it. A suspended existing row not explicitly asked to revive (isActive absent) is left untouched and reported as skipped.class_subjects rows created or updated.Step 9.
9BackendCommits, then invalidates the subjects cache prefix (assignment count changed).—Step 10.

Sequence Diagram

Branches and Edge Cases

BranchConditionBehaviorError/Result
Empty items arrayZero-length batchRefused before the transaction opens.400 CLASS_SUBJECT_BATCH_INVALID.
Subject archivedsubjects.is_active = falseWhole request refused, no items are validated further.422 SUBJECT_INACTIVE.
Same class appears twice in the batchTwo items name the same resolved classSecond occurrence collected as an error.Item-level CLASS_SUBJECT_DUPLICATE_CLASS inside 422 CLASS_SUBJECT_BATCH_INVALID.
Teacher id does not resolve, or resolves to a soft-deleted staff rowBad or stale teacherIdItem-level failure.CLASS_SUBJECT_TEACHER_NOT_FOUND.
Teacher's designation is not a teaching onedesignations.is_teaching = falseItem-level failure.CLASS_SUBJECT_TEACHER_NOT_TEACHING.
Post-defaulting passMarks > maxMarksItem omitted marks that, once defaulted from the subject, violate the boundItem-level failure.CLASS_SUBJECT_MARKS_INVALID.
Class already actively teaches a different subject with the same name/codeTwo subject rows sharing one retired-then-recreated label, both assigned to one classNo database constraint can express this (it spans two tables); the service guard is the only enforcement.CLASS_SUBJECT_DUPLICATE_LABEL.
version supplied and stale against an existing rowConcurrent editItem-level failure, folded into the batch rather than partially committing.CLASS_SUBJECT_VERSION_CONFLICT.
Existing row is suspended, item omits isActiveA prior suspension, re-assign without asking to reviveLeft untouched, its id reported in skippedSuspended — never silently reactivated.201, no write to that row.
Existing row is suspended, item sets isActive: trueExplicit revivalTreated as a normal upsert; other omitted fields keep their existing values.201, row revived.
Absent field on an existing rowe.g. item omits maxMarks on a re-assignThe existing value is kept, never reset to the subject's current default.Distinguishes a partial edit from a full re-send.
One item fails validation among many valid onesMixed batchThe WHOLE batch is refused — nothing partially commits.422 CLASS_SUBJECT_BATCH_INVALID naming every failing index.

5.8 List a subject's class assignments

Summary

An operator opens a subject's detail screen and sees every class currently (or previously) teaching it.

Preconditions

  • ClassSubjects_READ.
  • Row scope resolved via PeopleAccessService.scopeFor(actor, "classSubjects", …) — in practice all for staff/teacher/superadmin and refused (empty) for anyone else, since pupils and guardians are never granted this code.

Main Flow

StepActor/SystemActionResultSource
1Staff/Teacher/AdminOpens a subject's assignments tab.GET /api/subjects/:publicId/assignments.Controller.
2BackendResolves the subject by publicId; 404 if absent.—ClassSubjectsReadService.findForSubject.
3BackendResolves the caller's scope, applies it plus subjectId = ... and the optional isActive filter.Ordered by grade sort order, section sort order, shift, id.Service.

Branches and Edge Cases

BranchConditionBehaviorError/Result
pagination=false requestedQuery explicitly disables paginationRefused — every row embeds a ClassTeacherDto (employee code, employment status).400 PAGINATION_LIMIT_INVALID.
isActive=falseFilter to suspended assignments onlyNarrowed result.Only suspended rows returned.
Unknown subjectNo row with that publicId404 before any assignment query runs.404 SUBJECT_NOT_FOUND.

5.9 List a class's assigned subjects

Summary

An operator opens a class's detail screen and sees every subject it currently (or previously) taught, each with its teacher and its effective mark scheme.

Preconditions

  • ClassSubjects_READ.

Main Flow

StepActor/SystemActionResultSource
1Staff/Teacher/AdminOpens a class's subjects tab.GET /api/classes/:classPublicId/subjects.Controller.
2BackendResolves the class through the caller's scope predicate, not after.An out-of-scope class reads identically to an absent one.ClassSubjectsReadService.findForClass.
3BackendApplies the same scope again to the assignment rows, plus the optional isActive filter.Ordered as in 5.8.Service.

Branches and Edge Cases

BranchConditionBehaviorError/Result
Class outside the caller's scopeA narrower-than-all scope existed and excluded itIndistinguishable from a genuinely absent class — never a 403.404 CLASS_NOT_FOUND.
pagination=falseAs in 5.8Refused for the same staff-identity reason.400 PAGINATION_LIMIT_INVALID.

5.10 A pupil views their own subjects

Summary

A pupil opens their own "My Subjects" screen and sees exactly what their currently-enrolled class is being taught this academic session — subject name, code, type, teacher's name only, and the pass/max marks — with no way to reach any other class's data through this route.

Preconditions

  • Active session with scopeKind: "student".
  • No permission code is checked — STUDENT_PERMISSIONS is deliberately [] beyond Subjects_READ, and this route needs none at all.

Main Flow

StepActor/SystemActionResultSource
1PupilOpens their subjects screen.GET /api/mobile/student/subjects.StudentSubjectsPortalController.
2BackendAsserts scopeKind === "student".403 otherwise.assertPortalAudience.
3BackendResolves the target academic session — the query param if given, otherwise the current session.null if no current session is configured.StudentPortalService.resolveSessionId.
4BackendBuilds a subquery of the pupil's own actively-enrolled class ids (student_class_enrollments.status = 'active', pupil not soft-deleted, record_status = 'active') and reads class_subjects rows whose classId is in that set, active, in the target session.Every subject their current class teaches.Service.

Sequence Diagram

Branches and Edge Cases

BranchConditionBehaviorError/Result
Session acting as a non-student rolee.g. staff, teacher, guardianRefused before any query runs.403 PERMISSION_INSUFFICIENT.
No active role selectedMulti-role session, none chosenRefused.403 AUTH_ACTIVE_ROLE_REQUIRED.
Pupil has no active enrolmentNot currently placed in a classMatches nothing — not an error.200 { data: [], truncated: false }.
No current academic session configuredNothing marked is_currentResolved session is null, short-circuits before any subject query.200 { data: [], truncated: false }.
Pupil record soft-deleted or record_status not activeWithdrawn or on leaveExcluded by the enrolment subquery's own filters.Empty result, not an error.
Assignment has no teacher yetclass_subjects.teacher_id IS NULLteacherName: null.Visible in the payload as a gap, not an error.
Over 1000 rowsA pupil enrolled in an improbably large number of active assignmentsTruncated at the cap, truncated: true.Same withholding pattern as SubjectOptionsPayloadDto.

5.11 A teacher views their own subjects

Summary

A teacher opens their own "My Subjects" screen and sees every class they are the assigned teacher of, for the current academic session — their own name filled in as teacherName on every row, since the query is already scoped to their own teacherId.

Preconditions

  • Active session with a live staff row, not acting as guardian or student.
  • No permission code is checked — the same reasoning as 5.10, restated for staff: holding Students_READ with scopeKind: "all" would make a permission gate here no gate at all.

Main Flow

StepActor/SystemActionResultSource
1TeacherOpens their subjects screen.GET /api/mobile/teacher/subjects.TeacherSubjectsPortalController.
2BackendResolves the caller's own live staff.id; refuses a session not acting as the role named teacher or with no live staff row.—assertTeacherStaffRow.
3BackendResolves the target academic session, as in 5.10.—TeacherPortalService.resolveSessionId.
4BackendReads class_subjects rows where teacherId is the caller's own resolved staff id, active, in the target session.Every class they teach this subject in.Service.

Branches and Edge Cases

BranchConditionBehaviorError/Result
Session acting as guardian or studentWrong audience for this routeRefused.403 PERMISSION_INSUFFICIENT.
No live staff rowDismissed staff member who kept another live profileRefused — staff.deleted_at is filtered explicitly.403 PERMISSION_INSUFFICIENT.
No class_subjects row names this teacherSubject teacher who is nobody's assigned teacher yetMatches nothing.200 { data: [], truncated: false }.
No current academic sessionAs in 5.10Short-circuits.200 { data: [], truncated: false }.
teacherName fieldAlways the caller's own nameDerived from the decoded session (actor.name), never re-selected from users — no join needed since teacherId in the WHERE clause already IS the caller.Never null for this route.

6. Admin Flows

Repeat for every admin flow:

  • Create: 5.4 (subject), 5.7 (assignment).
  • List: 5.1, 5.8, 5.9.
  • Read detail: 5.3.
  • Update: 5.5 (subject), assignment PATCH (covered inline below).
  • Reorder: Not applicable — neither table carries a sortOrder column; the catalogue sorts by case-folded name and the assignment lists sort by grade/section/shift.
  • Activate/deactivate: isActive on both tables — retire/reactivate a subject (5.5), suspend/revive an assignment (below).
  • Soft delete: Not applicable — both tables hard-delete only; there is no deleted_at column on either.
  • Restore: Not reachable — Subjects_RESTORE and ClassSubjects_RESTORE codes exist in the generated permission catalogue (every module gets all five actions), but nothing in either controller implements a restore route, because there is nothing soft-deleted to restore.
  • Export/import: Not applicable — neither table is wired into the DataImport/DataExport module.
  • Moderation: Not applicable — no content moderation surface exists on either table.
  • Manual retry: Not applicable — no queue or async job touches either table.

6.1 Update a class subject assignment (reconfigure, suspend, revive)

Route, permission, mutation side effects, cache invalidation and failure branches:

  • Route: PATCH /api/class-subjects/:id, permission ClassSubjects_UPDATE.
  • version is required and applied in the WHERE clause — the same optimistic-concurrency shape as the subject PATCH.
  • A false → true transition on isActive runs the SAME preconditions assign() runs: the subject must still be active, and the class must not already actively teach a different subject sharing the same name or code.
  • On success, the subjects cache prefix is invalidated (classCount on the parent subject may have moved).
  • No audit call beyond the global ActivityAuditInterceptor — every mutating request on this route is already recorded.

6.2 Delete a class subject assignment

DELETE /api/class-subjects/:id, permission ClassSubjects_DELETE. Hard delete, no pre-check against a dependent table today — CLASS_SUBJECT_IN_USE is registered but unreachable, because nothing references class_subjects yet. Invalidates the subjects cache prefix on success, since the parent subject's classCount moves.

7. Lifecycle and State Transitions

Subjects

EntityFromEvent/ActionToGuard ConditionSide Effects
subjects(none)POST /api/subjectsactive (or archived if isActive: false was sent)Name/code not taken by another active subject; passMarks <= maxMarksCache prefix invalidated.
subjectsactivePATCH { isActive: false }archivedNone beyond the version check — retiring never checks assignments.Cache prefix invalidated; existing assignments untouched.
subjectsarchivedPATCH { isActive: true }activeMerged name/code must not belong to a different active subject.Cache prefix invalidated.
subjectsactive or archivedDELETE(gone)Zero class_subjects rows reference it, active or suspended.Row removed, cache prefix invalidated.

Class subject assignments

EntityFromEvent/ActionToGuard ConditionSide Effects
class_subjects(no row for this class+subject)POST .../assignments (insert branch)active (or isActive: false if explicit)Subject active; class active; no label collision; teacher (if given) is a live, teaching staff memberRow created, subjects cache prefix invalidated.
class_subjectsexisting row, any statePOST .../assignments (upsert branch, any field supplied)Fields merge-updated; state changes only if isActive explicitly suppliedSame preconditions as insert, plus a supplied version must matchRow updated.
class_subjectssuspendedPOST .../assignments, item omits isActiveUnchanged — explicitly left suspendedN/AReported in skippedSuspended, nothing written to that row.
class_subjectsactivePATCH { isActive: false }suspendedVersion match.Cache prefix invalidated.
class_subjectssuspendedPATCH { isActive: true }activeSubject still active; no label collision on the class.Cache prefix invalidated.
class_subjectsanyDELETE(gone)None today (no dependent table yet).Row removed, cache prefix invalidated.

9. Data and Side Effects by Flow

FlowDB WritesCache EffectsJobsRealtimeAnalyticsNotifications
List/read subjects, options—Read/write academic-structure:subjects:* (300s TTL)NoneNoneNoneNone
Create/update/delete a subjectsubjectsInvalidates academic-structure:subjects:*NoneNoneNoneNone
List assignments (per subject/class)—None (scope-dependent reads are never cached)NoneNoneNoneNone
Assign / update / delete an assignmentclass_subjectsInvalidates academic-structure:subjects:* (parent's classCount)NoneNoneNoneNone
Pupil/teacher own-subjects read—NoneNoneNoneNoneNone

10. Error and Recovery Flows

ScenarioTriggerUser/System ExperienceRecoverySource
Rate limitedNot applicableThis module applies no route-specific rate limit beyond whatever a global guard enforces elsewhere in the application.N/AControllers carry no throttling decorator.
Queue failureNot applicableNo BullMQ job is produced or consumed by this module.N/ABackend doc, Section 9.
Cache failureRedis unavailable during a list/options/detail readFalls through to Postgres transparently; response is unaffected, only slower.Automatic — getSoft treats any Redis error as a cache miss.RedisCacheService.getSoft.
Cache invalidation failureRedis unavailable right after a successful writeThe write already committed; the stale cached list/options response persists until its 300-second TTL expires.Automatic expiry; a WARN log line is the only signal in the interim.RedisCacheService.delPatternSoft.
Optimistic concurrency lostTwo operators edit the same subject or assignment concurrentlyThe second writer's request fails cleanly rather than silently overwriting the first.Caller reloads the row and retries with the fresh version.SUBJECT_VERSION_CONFLICT / CLASS_SUBJECT_VERSION_CONFLICT.
Batch assignment partially invalidAny one item in a 1-100 item batch fails validationThe entire batch is refused; nothing is written.Caller fixes every named index and resubmits — no partial state to reconcile.CLASS_SUBJECT_BATCH_INVALID, per-index errors[].

11. Diagrams Required Per Module

  • Actor capability diagram: Section 3, Section 12.2.
  • High-level module flow diagram: Section 12.2.
  • Sequence diagram for each major user/admin/system flow: present under every Section 5.x flow that has one, plus 6.1.
  • State machine diagram for every lifecycle: Section 7, two diagrams (subject, assignment).
  • Data side-effect diagram for write flows: Section 12.6.
  • Error branch diagram for critical flows: Section 6.1.

12. Mandatory Feature and Flow Deep-Dive Pack

12.1 Feature Inventory With Minor Behaviors

FeatureMinor BehaviorActorTriggerUser/System ResultBackend Side EffectSource
List subjectsSearch escapes %/_ in the termAdmin/Staff/Teacher?search=...A literal percent sign in a subject name filters literally, never as a wildcardescapeLikePattern applied before ILIKEsubjects-read.service.ts
List subjectspagination=false capped, not refusedAdmin/Staff/Teacher?pagination=falseFull catalogue in one response, up to 1000 rowsUNPAGINATED_HARD_CAPsubjects-read.service.ts
List subjectsclassCount never joins classesAdmin/Staff/TeacherAny list/detail readA subject's assignment count is stable regardless of a class being deactivatedCorrelated subquery scoped to class_subjects.is_active onlysubjects-read.service.ts
Subject optionsTruncation is reported, not silentAdmin/Staff/TeacherCatalogue exceeds 1000 rowstruncated: true in the payloadOne extra row fetched, sliced backsubjects-read.service.ts
Create subjectName is trimmed server-sideSuperadminPOST /api/subjectsLeading/trailing whitespace from a copy-paste is silently removeddto.name.trim()subjects-write.service.ts
Create subjectMarks default independentlySuperadminMarks omitteddefaultMaxMarks defaults to 100, defaultPassMarks to 40, even if only one is suppliedNullish-coalescing per fieldsubjects-write.service.ts
Update subjectNo-op patch still advances versionSuperadminPATCH { version } onlyThe token changes even though nothing else didSelf-assignment keeps the UPDATE non-empty so the trigger firessubjects-write.service.ts
Update subjectReactivation collision names the holderSuperadminReactivating onto a taken labelError message includes the other subject's codePre-write SELECT against active rows excluding selfsubjects-write.service.ts
Delete subjectRefusal message states both countsSuperadminAny dependent assignment existsMessage shows total AND active counts, since classCount only shows activeTwo aggregate counts in one querysubjects-write.service.ts
Assign to classesAbsent field keeps existing value on re-assignSuperadminPartial item in a batch against an existing rowUntouched fields are not reset to the subject's current defaultsObject.hasOwn per field drives the SET clauseclass-subjects-write.service.ts
Assign to classesExplicit null clears the teacherSuperadminteacherId: nullThe assignment becomes unassigned rather than keeping the prior teacherValidateIf distinguishes absent from explicit nullclass-subject.dto.ts
Assign to classesSuspended row is never silently revivedSuperadminBulk re-assign across a grade, one class suspendedThat row stays suspended and its id is reported backskippedSuspended arrayclass-subjects-write.service.ts
Assign to classesDuplicate class in one batch is caught before any writeSuperadminTwo items name the same class (by resolved integer id, not uuid text)Second occurrence flagged, nothing writtenseenClassIntIds setclass-subjects-write.service.ts
List assignmentsPagination cannot be disabledAdmin/Staff/Teacher?pagination=falseRefused — every row carries an employee codePAGINATION_LIMIT_INVALIDclass-subjects-read.service.ts
List for classOut-of-scope class reads as absentNon-all-scope callerGET /classes/:id/subjects on an out-of-scope class404, never 403Class resolved THROUGH the scope predicateclass-subjects-read.service.ts
Pupil subjectsEmpty is 200, not 404PupilNo active enrolment{ data: [], truncated: false }No pre-resolve-or-404 of a students rowstudent-portal.service.ts
Pupil subjectsDefaults to the current sessionPupilacademicSessionId omittedOnly the current year's subjects, never the pupil's whole historyresolveSessionId reads academic_sessions.is_currentstudent-portal.service.ts
Teacher subjectsteacherName is always the caller's own nameTeacherAny readNo extra join needed, and no risk of showing a stale nameactor.name, not re-selectedteacher-portal.service.ts

12.2 Business Process Diagram Pack

12.3 Business Rules and Policy Traceability

RuleBusiness ReasonActor ImpactEnforced InAPI ImpactBackend ImpactTests
A subject is defined once, school-wide; a class-subject assignment is a separate row per classMathematics is taught by different teachers in different classes — collapsing the two would force one "Mathematics" row per class and break every report that groups by subjectEvery actor works with one subject entity regardless of how many classes teach itsubjects vs class_subjects tablesTwo distinct route familiessubjects.ts schema docblocksubjects-write.service.integration.spec.ts
type, maxMarks, passMarks are COPIED onto the assignment at assignment time, never referencedEditing a subject's defaults must never retroactively change a class whose marks were already recordedAn operator changing a subject's default pass mark does not silently alter any class's gradingclass_subjects.type/max_marks/pass_marks are plain columns, not a joinAssignSubjectItemDto defaults from the subject only when a field is absentclass-subjects.ts schema docblockclass-subjects-write.service.integration.spec.ts
subjects name/code uniqueness is partial on is_activeA retired subject's name must be reusable, or archiving becomes a dead endAn operator can retire "Sanskrit" and create a fresh "Sanskrit"subjects_name_unique, subjects_code_uniqueSUBJECT_NAME_TAKEN/SUBJECT_CODE_TAKEN only fire against active rowsPartial uniqueIndex ... WHERE is_activesubjects-write.service.integration.spec.ts
Reactivating a subject re-checks the name/code against currently-active rowsThe partial index cannot catch this — a reactivating row does not yet violate anything at the moment it is checkedAn operator reactivating a retired subject onto a now-taken label is refused with the holder's name, not a generic conflictApplication-only pre-write checkSUBJECT_NAME_TAKEN_ON_REACTIVATE/SUBJECT_CODE_TAKEN_ON_REACTIVATEsubjects-write.service.ts reactivation branchsubjects-write.service.integration.spec.ts
class_subjects identity (class_id, subject_id) is TOTALLY unique, including while suspendedTwo live rows for one identity would make every mark, attendance record and timetable slot pointing at one of them unresolvableAn operator cannot accidentally create two rows for one class-subject pair, even across a suspend/revive cycleclass_subjects_identity_unique (non-partial)The assign endpoint upserts rather than insertsonConflictDoUpdate targeting the identity indexclass-subjects-write.service.integration.spec.ts
Assigning to a class holding a suspended row REVIVES it, but only when the caller explicitly asksA bulk re-assign must not silently un-suspend something an administrator deliberately suspendedAn operator's routine "re-run this term's assignments" cannot accidentally undo a deliberate suspensionClassSubjectsWriteService.assign step 9AssignSubjectItemDto.isActiveExplicit isActive: true required, else reported in skippedSuspendedclass-subjects-write.service.integration.spec.ts
Optimistic concurrency via a server-owned version, bumped by a triggerPrevents a lost update between two operators editing the same rowThe second of two concurrent editors is told to reload rather than silently overwrittenbump_row_version BEFORE UPDATE trigger on both tablesversion required on every UpdateSubjectDto/UpdateClassSubjectDtoWHERE ... AND version = ... in every UPDATEBoth write-service integration specs
ClassSubjects_READ is withheld from pupils and guardiansObject-level scope restricts which ROWS a caller sees, never which FIELDS — a pupil holding this code would read every teacher's employee code and employment status for their own classPupils use a name-only consumer endpoint insteadpackages/db/src/seed/seed-auth.ts (STUDENT_PERMISSIONS, GUARDIAN_PERMISSIONS)GET /subjects/:id/assignments and GET /classes/:id/subjects are unreachable for these rolesclass-subjects.dto.ts, ClassTeacherDtoclass-subjects-authorization.integration.spec.ts
Subjects_READ alone is granted to pupils; ClassSubjects_READ alone is granted to staff/teacher, never both to guardiansA guardian holding Subjects_READ would be routed by the panel to a pupil-only route and refused there permanentlyGuardians hold neither code today, by deliberate choice, not oversightGUARDIAN_PERMISSIONS = []No guardian-facing subjects route exists yetseed-auth.ts commentaryportal-authorization.integration.spec.ts
Pupils and teachers read their own subjects through an audience-scoped route, never through PeopleAccessService.scopeForscopeFor can return {kind:"all"} for a broadly-scoped role, which would leak the entire pupil roll through a portal named for one audienceA pupil's "my subjects" screen cannot be reached by a teacher or staff session, and vice versaassertPortalAudience / assertTeacherStaffRowConsumer routes carry no @Permissions() at allportal-audience.tsportal-subjects.integration.spec.ts
Both consumer subject reads default to the current academic sessionclass_subjects carries no session column and nothing prunes it; without a bound, a long-serving pupil or teacher's list grows without limitA pupil or teacher sees only this year's subjects unless they explicitly ask for another sessionresolveSessionId in both portal servicesMySubjectsQueryDto.academicSessionId optionalacademic_sessions.is_current lookupportal-subjects.integration.spec.ts
The subjects list/options/detail reads are cached; the assignment reads are notAssignment reads are scope-dependent (row-scoped by actor), so a shared cache key would serve one audience's rows to anotherList/options/detail responses may lag up to 300 seconds behind a concurrent write; assignment reads are always currentSubjectsReadService uses getSoft/setSoft; ClassSubjectsReadService does notN/Aacademic-structure:subjects:* prefixsubjects-read.service.integration.spec.ts

12.4 Tradeoffs and Product Rationale

Product DecisionUser BenefitEngineering BenefitAlternativeTradeoffRisk
Batch assignment is all-or-nothing in one transactionA 100-row grid save either fully succeeds or fully fails, never leaving a half-applied grid an operator has to reconcile row by rowOne write path, one set of invariants, no partial-commit recovery logicPer-item commit, collecting successes and failures independentlyA single bad row in a large batch blocks 99 good ones until fixedLow — errors carry a per-index list so the fix is immediate
Assignment defaults are copied, not referencedA class's grading never silently drifts when someone edits the subject catalogue laterNo join needed on every mark calculation; the assignment row is self-containedA foreign-key reference to the subject's live defaultsTwo rows must be kept mentally distinct — "the subject's default" vs "this class's actual"Low — the DTOs and docs both label the field as effective/copied
ClassSubjects_READ granted to staff/teacher but not pupils/guardiansProtects staff identity (employee code, employment status) from a non-staff audienceOne permission code instead of a field-level redaction layer on this DTOField-omitting a pupil-safe variant of ClassSubjectDtoPupils/guardians need an entirely separate route (already built) instead of a filtered view of the same oneLow — the separate route already exists and is audience-scoped
Consumer "my subjects" routes bypass PeopleAccessService.scopeFor entirelyCloses a real, previously-found hole where a broadly-scoped staff session could read another audience's portal dataThe row-scope IS the query's own join condition, impossible to widen by a permission grant elsewhereReuse scopeFor with a classSubjects/students entityA second, portal-specific authorization pattern to maintain alongside the admin oneLow — documented at length in portal-audience.ts, and covered by its own authorization integration spec
Suspension (isActive: false) instead of deletion for a class-subject assignmentConfiguration (teacher, marks) is preserved for a quick, safe revivalAvoids re-entering marks and re-picking a teacher on a temporary pauseHard delete and re-createA suspended row still occupies the identity slot — a genuinely new assignment for the same pair must revive it, never insert a second rowLow — the identity index is total specifically to make this the only path
Marks upper bound of 1000 on both tablesCatches an obvious typo (10000) without constraining real assessment schemesOne shared, generous ceiling instead of per-school configurationNo ceiling at allA school running a genuinely unusual scale above 1000 would need a schema changeVery low — stated explicitly as a fat-finger guard, not a domain rule

12.5 Flow Edge-Case Matrix

FlowEdge CaseTriggerExpected BehaviorUser/System FeedbackSource
List subjectsEmpty stateNo subjects created yet{ data: [], totalCount: 0 }Empty list, no errorsubjects-read.service.ts
List subjectsFirst useCache cold, first request after bootFull DB round trip, then cachedSame response, slightly slower first callsubjects-read.service.ts
Assign to classesLast item in a batch fails99 valid + 1 invalid itemWhole batch refused422, every failing index namedclass-subjects-write.service.ts
Assign to classesDuplicate actionSame batch submitted twice, unchangedSecond call is idempotent for unchanged fields (self-assignment no-op on the SET clause)201 both times, version may advance on the second if the trigger firesclass-subjects-write.service.ts
Assign to classesConcurrent actionTwo operators assign the same subject to the same class simultaneouslyThe identity unique index serializes them; the loser's onConflictDoUpdate still succeeds since it is an upsert, not a plain insertBoth requests succeed, last write wins on overlapping fields (bounded by any supplied version)class_subjects_identity_unique
Update subjectExpired/stale stateversion sent is older than currentRefused409 SUBJECT_VERSION_CONFLICTsubjects-write.service.ts
Update subjectPermission mismatchCaller holds Subjects_READ but not _UPDATERefused before the handler runs403 PERMISSION_INSUFFICIENTRoleGuard
Pupil subjectsGuest limitationN/A — route requires a JWTNo guest path exists401 from the global guardJwtAuthGuard
Pupil subjectsMissing dependencyNo current academic session configured anywhereEmpty result rather than an error200 { data: [], truncated: false }student-portal.service.ts
List subjectsCache stale/missRedis downFalls through to DBNo visible error, response unaffectedredis.service.ts getSoft
Assign to classesQueue failureNot applicableNo queue is involved anywhere in this moduleN/ABackend doc, Section 9
List subjectsUnsupported filter/sorte.g. sort=name on the subject listListSubjectsQueryDto extends QueryDto, which accepts sort/order, but SubjectsReadService.findAll uses a FIXED orderBy clause that never reads either fieldAlways sorted case-folded name then id, regardless of what was requestedsubjects-read.service.ts

12.6 Flow-to-Data Trace

FlowReadsWritesCacheJobs/EventsResponse Fields
List subjectssubjects, correlated class_subjects count—academic-structure:subjects:list:*NonepublicId, name, code, type, defaultMaxMarks, defaultPassMarks, isActive, classCount, version, createdAt, updatedAt
Create subjectsubjects (uniqueness via index)subjectsPrefix invalidatedNoneFull SubjectDto
Assign to classessubjects, classes, staff+designations, class_subjects (collisions, existing)class_subjectsPrefix invalidatedNoneassigned: ClassSubjectDto[], skippedSuspended: string[]
List a subject's assignmentsclass_subjects joined to classes/grades/sections/staff/users/designations—Never cached (scope-dependent)NoneClassSubjectDto[]
Pupil own subjectsstudent_class_enrollments, class_subjects, subjects, classes/grades/sections, staff/users, academic_sessions—Never cachedNoneMySubjectDto[] (no ids, teacher name only)

12.7 Experience Quality Checklist

  • The doc explains what the actor is trying to accomplish — define what the school teaches and who teaches it where.
  • The doc explains what the backend does that the actor does not see — copy-not-reference defaults, cache invalidation, scoped row resolution.
  • The doc covers every minor flow and branch — suspension skip, reactivation collision, batch partial failure, cache fail-soft.
  • The doc includes user, admin, worker, and system flows where applicable — no worker/system flow exists on this module, stated explicitly rather than omitted.
  • The doc explains business logic, tradeoffs, and rationale — 12.3, 12.4.
  • The doc maps every flow to API routes and backend side effects — 4, 12.6.
  • The doc includes diagrams appropriate to each flow type — sequence, state, flowchart.
  • The doc covers all edge cases and failure recovery — 12.5, 10.

13. Completion Checklist

  • Every feature, minor action, and submodule capability is listed.
  • Every actor has allowed and forbidden behavior.
  • Every major and minor flow includes steps, branches, and diagrams.
  • Every lifecycle has a transition table and state diagram.
  • Every flow links to the API and backend docs.
  • No TDD document exists for this module — there is no tdd.mdx page and none is referenced; the schema file's own extensive docblocks serve as the technical design record.

See Also

On this page

Subjects Features and Flows1. Documentation Evidence2. Feature Summary3. Actor Matrix4. Capability Matrix5. User-Facing Flows5.1 List/search the subject catalogueSummaryPreconditionsMain FlowSequence DiagramBranches and Edge Cases5.2 Subject options for assignment pickersSummaryPreconditionsMain FlowSequence DiagramBranches and Edge Cases5.3 Read one subjectSummaryPreconditionsMain FlowBranches and Edge Cases5.4 Create a subjectSummaryPreconditionsMain FlowSequence DiagramBranches and Edge Cases5.5 Update, retire, and reactivate a subjectSummaryPreconditionsMain FlowSequence DiagramBranches and Edge Cases5.6 Delete a subjectSummaryPreconditionsMain FlowBranches and Edge Cases5.7 Assign a subject to one or more classes (batch)SummaryPreconditionsMain FlowSequence DiagramBranches and Edge Cases5.8 List a subject's class assignmentsSummaryPreconditionsMain FlowBranches and Edge Cases5.9 List a class's assigned subjectsSummaryPreconditionsMain FlowBranches and Edge Cases5.10 A pupil views their own subjectsSummaryPreconditionsMain FlowSequence DiagramBranches and Edge Cases5.11 A teacher views their own subjectsSummaryPreconditionsMain FlowBranches and Edge Cases6. Admin Flows6.1 Update a class subject assignment (reconfigure, suspend, revive)6.2 Delete a class subject assignment7. Lifecycle and State TransitionsSubjectsClass subject assignments9. Data and Side Effects by Flow10. Error and Recovery Flows11. Diagrams Required Per Module12. Mandatory Feature and Flow Deep-Dive Pack12.1 Feature Inventory With Minor Behaviors12.2 Business Process Diagram Pack12.3 Business Rules and Policy Traceability12.4 Tradeoffs and Product Rationale12.5 Flow Edge-Case Matrix12.6 Flow-to-Data Trace12.7 Experience Quality Checklist13. Completion ChecklistSee Also