09 — User Behaviour (Students Module)
- 1. Roster list
- 2. Create wizard
- 3. Transfer / Enroll
- 4. Documents
- 5. Bulk import
- 6. Parent linking
- 7. Lifecycle actions (graduate/archive/restore/delete)
- 8. Offline & connectivity
- 9. Permission-driven behaviour (client)
Behavioural rules per screen: defaults, expectations, friction points and mitigations. All server behaviour cited is exact (
student.service.ts,bulk-import.service.ts). Anything marked(planned)/(proposed)is flagged.
1. Roster list
| Rule | Detail |
|---|---|
| Default view | page 1, limit 20 (pagination-query.dto.ts:11-19); client shows status "All" |
| Ordering | server returns natural order (no sort applied — student.service.ts:104-108); client sorts loaded page by name/admission number when user picks a sort chip |
| Search expectation | user expects server-wide search (q); today client filters loaded pages only → surface honesty: helper text "Searching current results — full search coming soon" (planned) (OQ-2) |
| Refresh | RefreshIndicator reloads page 1; infinite scroll appends via meta.hasNext |
| Returning from detail | list refreshes silently in background (stale-while-revalidate) |
| Long session | statuses can change elsewhere (another admin) — pull-to-refresh is the contract; WS roster updates (forward-looking) |
2. Create wizard
| Rule | Detail |
|---|---|
| Completion expectation | "created + enrolled in one action" — exact server semantics (student.service.ts:55-93); tell the user upfront on step 4 |
| Identity discovery | reuse-existing-user is the happy path for re-admissions; new-user only when not found. 409 email → auto-suggest search |
| Admission number | suggest pattern ADM{YYYY}{seq} (example in DTO create-student.dto.ts:9); client caps length; uniqueness pre-checked + server-verified |
| Abandonment | no draft persistence (YAGNI); warn on back with unsaved changes via system back-interception |
| Double submit | CTA disabled while pending (00-shared/08 §6) |
3. Transfer / Enroll
| Rule | Detail |
|---|---|
| Expectation | "history preserved, current enrollment closed" — banner before submit (student.service.ts:125-131) |
| Same-class guard | client blocks identical classId+academicYearId (no server guard — server creates a duplicate enrollment; OQ-13) |
| Non-active student | transfer form replaced by read-only status banner (server 409 mirror, student.service.ts:175-179) |
| After success | navigate to history tab; new ACTIVE node on top, old node transferred with leftAt |
4. Documents
| Rule | Detail |
|---|---|
| Expectation | uploaded file is instantly part of the student record — true (doc created synchronously) |
| Category | free text (no server enum); suggest chips but allow custom |
| Preview | not implemented server-side (only fileId metadata; OQ-9) — do not show a dead preview button; show "Open file" only when a serving endpoint exists (planned) |
| Ordering | newest first (student.service.ts:277-278) — matches "just uploaded on top" |
5. Bulk import
| Rule | Detail |
|---|---|
| Expectation | "big batch, one report" — synchronous loop server-side (bulk-import.service.ts:45-63); 1000 rows can take a while (OQ-12) → progress step must not pretend real progress; use indeterminate + row-count readout |
| Re-import | user re-uploads the same file to retry failed rows — dedup by admission number makes fixed rows import cleanly (server checks each row) |
| Template mismatch | export CSV ≠ import CSV columns (see 08 §6) — the UI must present the import column list, not the export file |
| Partial success | normal; report card shows imported/failed/total; never an error page |
6. Parent linking
| Rule | Detail |
|---|---|
| Expectation | "link the guardian who should get calls and fee messages" — flags map to isPrimaryGuardian, financialResponsibility, pickupAllowed, emergencyPriority (link-parent.dto.ts) |
| Duplicate parent user | 409 → reuse existing parent instead of creating |
| Unlink | soft delete (student-parent-link.service.ts:38-41); history of links is not shown after unlink (deleted rows excluded by scopedFilter) |
| Edit link | no PATCH endpoint — edit = unlink + relink; tell the user ("changes require re-linking") |
7. Lifecycle actions (graduate/archive/restore/delete)
| Rule | Detail |
|---|---|
| Graduate | terminal in UI (no un-graduate endpoint; OQ-5) — copy must say so |
| Archive | reversible; archived students hidden from default roster (client filter) |
| Restore | returns to active (no restoration to graduated/transferred) |
| Delete | soft delete — "removed from all lists, history retained"; no restore UI (no restore endpoint for soft-deleted; OQ-7) |
| Idempotent taps | double-tap same action → server 409 (already graduated/archived/active) → snackbar + state refresh, no crash (student.service.ts:207-209,228-230) |
8. Offline & connectivity
| Rule | Detail |
|---|---|
| Reads | cached last-good roster + detail; AppOfflineBanner (00-shared/06 §3.7) |
| Writes | all blocked (create/enroll/transfer/upload/import/link are server-first, no offline queue defined) — guidance snackbar |
| Upload interrupted | DocumentUploadTile error state with Retry (no chunked resume — 00-shared/12 B7) |
| Import offline | blocked before file pick |
9. Permission-driven behaviour (client)
- Actions hidden when permission missing (00-shared/05 §9): create FAB needs
student.create; edit/transfer/graduate/archive/restore needstudent.update; delete needsstudent.delete; upload needsfile.upload; import/export(planned)gate — nostudent.importconstant (OQ-4). - Server currently authorizes by JWT only (
student.controller.ts:32-35) — a 403 can still appear later; treat via standard error mapping (00-shared/06 §5).