03 — User Journey (Bulk Module)
- 1. Journey map
- J1 — First-time bulk import (happy path)
- J2 — Import with row errors (partial success)
- J3 — Fix and re-upload (idempotency)
- J4 — Export roster / template
- J5 — Malformed / wrong file (failure)
- Cross-journey notes
End-to-end journeys through the bulk import/export flow. Screen references (
SSn) resolve in 05_Screen_Inventory.md; exact API behaviour in 12_API_Mapping.md. Forward-looking client on top of the live API.
1. Journey map
| # | Journey | Persona | Screens | Outcome |
|---|---|---|---|---|
| J1 | First-time bulk import | Admissions Admin | SS1 → SS2 → SS3 → SS4 | All rows imported; report shown |
| J2 | Import with row errors | Admissions Admin | SS1 → SS2 → SS3 → SS4 | Partial success; error list reviewed |
| J3 | Fix and re-upload | Admissions Admin | SS4 → SS2 (corrected) → SS4 | Previously failed rows now import; successes not duplicated |
| J4 | Export roster / template | Registrar | SS5 | students.csv downloaded |
| J5 | Malformed / wrong file | any admin | SS2 → SS1 (error) | Clear failure, file not partially imported |
J1 — First-time bulk import (happy path)
- Entry — Bulk/Import menu (client gate: admin-capable user, 02 §7).
- SS1 Upload — picks
students.csv(file picker,.csvonly); client parses in an isolate to render a preview (15 §4); "Next" enabled when headers match the contract (08 §1). - SS2 Preview — header map (contract vs file), row count, sample rows; mismatches and missing required columns flagged before upload; "Import N rows" CTA.
- SS3 Confirm — reads the risk summary: "N rows will create N users and N student records. Duplicates and reference mismatches will be skipped."
- SS4 Result —
POST /bulk/import/students; indeterminate progress (sync request); on 200, report renders: imported/failed/total, per-row error table. All-success → celebratory summary + links (view students, download report). - Exit — back to Students list; imported users/students are live immediately (synchronous create; no queue).
J2 — Import with row errors (partial success)
- Same entry through SS3.
- SS4 — report shows
imported < totalRows,failed > 0; error table with row numbers and messages, e.g.Email "x" already registered./Academic year "2026-27" not found.(08 §2). - Error rows are grouped by cause (duplicates / missing refs / bad format) for fast triage; each row links to its line in the file.
- User downloads an error CSV (client-synthesized: original row + error column) for offline fixing — no server endpoint exists for this.
J3 — Fix and re-upload (idempotency)
- From SS4, "Fix errors" → re-upload the corrected file (SS2).
- Already-imported rows now fail as duplicates (admission number / email,
students-import.adapter.ts:49-56) — expected and explained: report showsimported ≈ previously-failed rows, duplicates listed as duplicates. - No duplicate users are created even if a race occurs — the create-time
fallback reuses the existing user id (
students-import.adapter.ts:72-75). - Design copy must teach this: "duplicate" ≠ "problem" on re-upload (14_QA_Checklist.md §5).
J4 — Export roster / template
- SS5 Export — entity selector (
studentsonly today), "Download CSV". GET /bulk/export/students→text/csvattachmentstudents.csv(bulk.controller.ts:50-60).- Content:
admissionNumber, rollNumber, status, admissionDate(students-import.adapter.ts:92-97), sorted by admissionNumber (:90-91). - Templating caveat (flagged): the export is a roster snapshot, not an
import template — it lacks
firstName/lastName/email/grade/section/ academicYear. UI copy must not claim "download a template from Export"; a true template is client-generated(forward-looking)(04 §6).
J5 — Malformed / wrong file (failure)
- Wrong entity offered (e.g. "teachers") → client blocks it (entity list is
hard-coded
students); if it slips through, server 404No import adapter for entity "teachers".(bulk-import.service.ts:17-20) → SS4 error state. - Malformed CSV (unclosed quote, bad encoding) → client preview fails first
(isolate parse, 15 §4); if the file bypasses preview, server 400
Malformed CSV: could not parse file.(bulk-import.service.ts:31-33). - Empty CSV (header only) → 400
CSV must include a header row and data.(:34-35). - Missing file field → 400
CSV file is required (multipart field "file").(bulk.controller.ts:43-46). - Nothing is partially imported on any of these — parse happens before the first row is touched.
Cross-journey notes
- No async queue exists — every journey above blocks on one HTTP request
per upload; async workers with BullMQ progress/rollback are
(planned)(IMPLEMENTATION_PLAN.md:172) and will later turn SS4's indeterminate bar into a progressed job (design keeps that seam open, 13 §4). - Large files (≥ 1000 rows): the journey is identical but slow; UI must
pre-cap size (recommended 1000 rows / ~2 MB) and warn before upload
(10 §6); server-side row limit
(planned). - Events: every imported row emits
UserCreated(in-app notification,event-queue-map.ts:10) andStudentCreated(in-appstudent-enrolled,event-queue-map.ts:28) — students/parents may react to onboarding notifications; the importing admin does not need to send anything manually.