13 — State Management (Bulk Module)
- 1. Cubit map
- 2. BulkHomeCubit (SS1)
- 3. BulkPreviewCubit (SS2)
- 4. BulkImportCubit — the import state machine (SS3/SS4)
- 5. BulkExportCubit (SS5)
- 6. Persistence & navigation
Per-screen state on top of 00-shared/06_State_Management.md (Bloc/Cubit proposal). Cubits are
(planned); repository/API facts are exact. Special attention: the synchronous import state machine (§4).
1. Cubit map
BulkHomeCubit → SS1 (entity, file, client parse)
BulkPreviewCubit → SS2 (headers, warnings, sample rows)
BulkImportCubit → SS3/SS4 (owns the import state machine, §4)
BulkExportCubit → SS5
SessionCubit → shared (self id, permission gate)
2. BulkHomeCubit (SS1)
- State:
LoadState+entities: ['students'](hard-coded client mirror ofstudents-import.adapter.ts:15;(planned)discovery),selectedEntity,fileMeta? {name, size, rows, hash},parseStatus: idle|parsing|ok|error,parseError?. - Events:
SelectEntity,PickFile(File)→ isolate parse (15 §4) with options mirrored from the server (columns:true, skip_empty_lines:true, trim:true—bulk-import.service.ts:26-30),RetryParse. - Rules:
Nextenabled ⇔parseStatus == ok && rows > 0(mirrors 400CSV must include a header row and data.—:34-35); file hash replaces wizard state (10 §1.5).
3. BulkPreviewCubit (SS2)
- State: headers with classification (
required | optional | unknownper08 §1),blocking: string[](missing required columns — names matchMissing required column "X".,students-import.adapter.ts:43),warnings: string[](unknown columns, size, whitespace headers),sampleRows(first 10, physical line numbers). - Events:
Next(→ confirm),Back(file retained). - Pure computation, no server calls — the preview must agree with the server
parser by construction (same library + options in the isolate,
15 §4).
4. BulkImportCubit — the import state machine (SS3/SS4)
The backend is synchronous (bulk-import.service.ts:22-65); the cubit
models the request lifecycle and keeps a (planned) async seam.
4.1 States
Idle → Confirming → InFlight → Done { AllSuccess | Partial | FailedPreRow }
└→ FixAndReupload → (SS1) → …
4.2 Mermaid
stateDiagram-v2
[*] --> Idle
Idle --> Confirming: confirmReady (SS3)
Confirming --> InFlight: startImport (POST /bulk/import/students)
InFlight --> AllSuccess: 200 & imported == totalRows
InFlight --> Partial: 200 & failed > 0
InFlight --> FailedPreRow: 400/404 (malformed | empty | unknown entity)
InFlight --> FailedPreRow: 429 | 5xx | network drop
AllSuccess --> [*]
Partial --> FixAndReupload: fix errors
FailedPreRow --> FixAndReupload
FixAndReupload --> Confirming: corrected file re-picked
Partial --> Idle: view students (exit)
state "async seam (planned)" as AS {
InFlight --> JobQueued: jobId returned
JobQueued --> Polling: GET /bulk/import/:id (planned)
Polling --> AllSuccess
Polling --> Partial
}
4.3 State shape
| Field | Meaning |
|---|---|
phase | idle | confirming | inFlight | done |
outcome | allSuccess | partial | failedPreRow |
report? | ImportReport {entity,totalRows,imported,failed,errors[{rowNumber,errors[]}]} (import-adapter.interface.ts:14-25) — verbatim from data |
requestState | sent | timedOut — timeout ≠ failure: rows may exist (06 §4.1a) |
jobId?, progress? | (planned) async seam — unused today (no queue, queue.constants.ts:1-17) |
failedRowsQueue | row numbers carried to SS1 for re-upload highlighting (03 J3) |
4.4 Transitions (exact)
| From | Event | To | Condition / source |
|---|---|---|---|
| Confirming | startImport | InFlight | multipart file field (bulk.controller.ts:38); CTA double-fire blocked |
| InFlight | importSucceeded | AllSuccess | 200, imported === totalRows |
| InFlight | importPartial | Partial | 200, failed > 0 |
| InFlight | importRejected | FailedPreRow | 400 Malformed CSV: could not parse file. (:32) / CSV must include a header row and data. (:35); 404 No import adapter for entity "X". (:19) |
| InFlight | importRejected | FailedPreRow | 429 (countdown copy) / 5xx / network drop (partial-upload guidance) |
| Partial / FailedPreRow | fixErrors | FixAndReupload | → SS1 with failedRowsQueue |
4.5 Client-side report assertions
imported + failed === totalRows— violated →dataMismatchflag renders the raw report with a support banner (06 §4.2).- Group errors (04 §7) by string prefix — grouping is derived, never hard-coded against future adapter messages.
5. BulkExportCubit (SS5)
- State:
entities(hard-codedstudents),downloading,lastStatus(ok | empty | error). - Events:
Download(entity)→GET /api/v1/bulk/export/:entity(bulk.controller.ts:50-60); save via browser attachment (filename="<entity>.csv",:55-58).
6. Persistence & navigation
- Wizard state lives in the cubits, not route args (file objects can't serialize); on app restart mid-import → fresh start + guidance (09 §4).
- No cache layer needed (stateless endpoints); export result is the file itself.
- Optimistic updates: none — imports/exports are never optimistic
(
00-shared/06 §3.4).