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

03 — User Journeys (Students Module)

Happy-path + failure branch journeys with Mermaid. Every endpoint referenced is exact (src/modules/students/controllers/student.controller.ts, src/modules/parents/ controllers/parent.controller.ts, src/modules/bulk/bulk.controller.ts). All journeys assume an authenticated JWT; tenantId comes from the token only.


1. Create + enroll student (admission desk)

sequenceDiagram
    actor R as Admission staff
    participant UI as Students UI
    participant API as POST /users + POST /students
    participant Q as BullMQ (in-app / audit)
    R->>UI: Open "Add student" wizard
    UI->>API: POST /users {firstName,lastName,email} (identity step)
    alt user already exists (409/duplicate email)
        API-->>UI: 409 — staff searches existing user instead
        UI->>API: GET /users?q=email (reuse existing userId)
    else created
        API-->>UI: 200 user {_id}
    end
    UI->>API: POST /students {userId, admissionNumber, classId, academicYearId, gradeId, sectionId, admissionType…}
    Note over API: duplicate admissionNumber → 409 ConflictException<br/>(student.service.ts:56-62); else status=active + auto-enroll ACTIVE (71-78)
    alt duplicate admission number
        API-->>UI: 409 DUPLICATE_RESOURCE — inline field error, staff corrects
    else success
        API-->>UI: 200 student doc (with enrollment created)
        UI-->>R: Success snackbar → detail screen
    end
    Note over Q: StudentCreated → in-app "student-enrolled"<br/>(event-queue-map.ts:28); audit log via StudentCreated/audit

Failure branches: 400 validation (missing userId, malformed admissionNumber), 409 duplicate, 5xx. Identity-first ordering is a hard server constraint: CreateStudentDto.userId is required (create-student.dto.ts:6-7).

sequenceDiagram
    actor R as Admission staff
    participant UI as Student detail → Parents tab
    participant API as POST /parents + POST /parents/link/:studentId
    R->>UI: "Add parent" (choose existing or create new)
    alt new parent
        UI->>API: POST /users (guardian identity)
        UI->>API: POST /parents {userId, occupation?, pickupAuthorization?}
        Note over API: duplicate userId → 409 (parent.service.ts:30-34)
    end
    UI->>API: POST /parents/link/:studentId {parentId, relationship, isPrimaryGuardian?, financialResponsibility?, pickupAllowed?, emergencyPriority?}
    Note over API: student existence checked (student-parent-link.service.ts:27);<br/>parent existence NOT checked (OQ-11)
    alt success
        API-->>UI: 200 link doc (relationship enum: mother|father|guardian|grandparent|relative|foster_parent — student-parent-link.schema.ts:7-14)
        UI-->>R: Snackbar "Guardian linked"; Parents tab refreshes
    else 404 student
        API-->>UI: 404 RESOURCE_NOT_FOUND
    end
    opt unlink
        UI->>API: DELETE /parents/link/:linkId (soft delete; 404 if missing — student-parent-link.service.ts:38-41)
    end

Relationship is required on the link DTO (link-parent.dto.ts:9-20); all four flags are optional booleans with schema defaults (student-parent-link.schema.ts:27-37).

3. Transfer student to another class

flowchart TD
    A[Student detail → Actions → Transfer] --> B{status == active?}
    B -- no --> C[Blocked: 409 'Cannot transfer a student with status X'<br/>student.service.ts:175-179 — show status banner]
    B -- yes --> D[Transfer form: class + academic year<br/>+ optional grade/section/rollNumber<br/>transfer-student.dto.ts]
    D --> E[POST /students/:id/transfer]
    E --> F{Server}
    F -- ok --> G[Old enrollments set TRANSFERRED + leftAt<br/>student.service.ts:125-131 via enroll()]
    F -- ok --> H[Student classId/academicYearId updated + optional gradeId/sectionId<br/>student.service.ts:185-192]
    F -- ok --> I[StudentUpdated → audit 'log-student-updated'<br/>event-queue-map.ts:29]
    G & H & I --> J[UI: refresh detail + academic history<br/>snackbar 'Transferred']
    F -- 404 --> K[Student not found]
    F -- 5xx --> L[Generic + requestId]

gradeId/sectionId are optional and default to the target class's own grade/section (transfer-student.dto.ts:13-25).

4. Upload student documents

sequenceDiagram
    actor R as Admission staff
    participant UI as Documents tab
    participant API as POST /students/:id/documents
    participant S as StorageProvider
    R->>UI: Tap "+ Document" → pick/capture file
    UI->>API: multipart POST file=… + field category (optional, string)
    Note over API: FileInterceptor('file') (student.controller.ts:82-90);<br/>no size/type validation server-side (OQ-9)
    API->>S: storage.upload({buffer, filename, mimeType, tenantId}) → fileId
    S-->>API: fileId (local: "<tenantId>/<uuid>--<name>" — local-storage.provider.ts:26-30)
    API-->>UI: 200 document doc {fileName, mimeType, size, fileId, category, uploadedBy}
    UI-->>R: Attachment tile appears; list sorted createdAt desc (student.service.ts:273-279)
    Note over UI: Preview/download needs a file-serving endpoint — only fileId returned today (OQ-9)

5. Bulk CSV import (admissions batch)

flowchart TD
    A[Students list → menu → Import] --> B[Download template GET /bulk/export/students<br/>bulk.controller.ts:50-60 — exports admissionNumber, rollNumber, status, admissionDate]
    B --> C[Fill rows: firstName*, lastName*, email*, admissionNumber*,<br/>grade*, section*, academicYear*, rollNumber?<br/>students-import.adapter.ts:17-26]
    C --> D[Pick CSV → POST /bulk/import/students<br/>multipart field 'file']
    D --> E{Parse}
    E -- malformed/empty --> F[400 'Malformed CSV' / 'CSV must include a header row'<br/>bulk-import.service.ts:31-35]
    E -- ok --> G[Per-row validate + create<br/>rowNumber = index + 2 (header = row 1)<br/>bulk-import.service.ts:45-63]
    G --> H[Report: totalRows, imported, failed, errors[{rowNumber, errors}]] 
    H --> I[UI: result screen — imported count, failed rows list with<br/>per-row messages: missing columns, invalid email,<br/>duplicate admission, duplicate email,<br/>academicYear/grade/section not found, no class for grade+section<br/>students-import.adapter.ts:38-60,100-144]
    I --> J[Retry failed rows after fixing CSV]

Import is synchronous in code (loop in the request) — no BullMQ progress job today; the "import progress" state is client-side simulation of a long request (OQ-12). A 1000-row file = 1000 sequential create calls.

6. Graduate / withdraw student

flowchart TD
    A[Student detail → Actions → Graduate] --> B[Confirm dialog]
    B --> C[POST /students/:id/graduate]
    C --> D{status == graduated?}
    D -- yes --> E[409 'Student is already graduated.' student.service.ts:207-209]
    D -- no --> F[status := graduated; StudentUpdated ['graduate'] → audit]
    A2[Student detail → Actions → Archive] --> B2[Confirm dialog<br/>copy: hidden from roster, restorable]
    B2 --> C2[POST /students/:id/archive]
    C2 --> D2{status == archived?}
    D2 -- yes --> E2[409 student.service.ts:228-230]
    D2 -- no --> F2[status := archived; StudentUpdated ['archive']]
    F2 --> G[Restore: POST /students/:id/restore → active<br/>student.controller.ts:74-77]
    Note over G: There is NO 'withdraw' status — withdrawal = archive today<br/>(enum has inactive/transferred but unwritable, OQ-5/OQ-6)

7. Student self-view (forward-looking)

sequenceDiagram
    actor S as Student (linked userId)
    participant UI as My Profile
    participant API as (planned) student-self endpoints — OQ-1
    S->>UI: Open My Profile
    Note over UI: No GET /students/me exists. Today the student's userId<br/>has no student-scoped endpoint; profile reachable only via staff routes.
    UI->>API: (planned) GET /students/me → profile + academic history + documents
    Note over UI: Alternative interim: staff shares a read-only link?<br/>Blocked on OQ-1 decision.

Journey → endpoint inventory (summary)

JourneyPrimary endpointsSource
Create + enrollPOST /users, POST /studentsusers.controller.ts:36-40, student.controller.ts:38-40
Link parentPOST /parents, POST /parents/link/:studentId, DELETE /parents/link/:linkIdparent.controller.ts:29-31,47-60
TransferPOST /students/:id/transferstudent.controller.ts:62-65
DocumentsPOST /students/:id/documents, GET /students/:id/documentsstudent.controller.ts:78-90
Bulk importPOST /bulk/import/students, GET /bulk/export/studentsbulk.controller.ts:35-60
Graduate/archivePOST /students/:id/graduate|archive|restorestudent.controller.ts:66-77
Academic historyGET /students/:id/academic-history, GET /students/:id/enrollmentsstudent.controller.ts:47-49,91-94