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

13 — State Management (Bulk Module)

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 of students-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:truebulk-import.service.ts:26-30), RetryParse.
  • Rules: Next enabled ⇔ parseStatus == ok && rows > 0 (mirrors 400 CSV 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 | unknown per 08 §1), blocking: string[] (missing required columns — names match Missing 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

FieldMeaning
phaseidle | confirming | inFlight | done
outcomeallSuccess | partial | failedPreRow
report?ImportReport {entity,totalRows,imported,failed,errors[{rowNumber,errors[]}]} (import-adapter.interface.ts:14-25) — verbatim from data
requestStatesent | timedOut — timeout ≠ failure: rows may exist (06 §4.1a)
jobId?, progress?(planned) async seam — unused today (no queue, queue.constants.ts:1-17)
failedRowsQueuerow numbers carried to SS1 for re-upload highlighting (03 J3)

4.4 Transitions (exact)

FromEventToCondition / source
ConfirmingstartImportInFlightmultipart file field (bulk.controller.ts:38); CTA double-fire blocked
InFlightimportSucceededAllSuccess200, imported === totalRows
InFlightimportPartialPartial200, failed > 0
InFlightimportRejectedFailedPreRow400 Malformed CSV: could not parse file. (:32) / CSV must include a header row and data. (:35); 404 No import adapter for entity "X". (:19)
InFlightimportRejectedFailedPreRow429 (countdown copy) / 5xx / network drop (partial-upload guidance)
Partial / FailedPreRowfixErrorsFixAndReupload→ SS1 with failedRowsQueue

4.5 Client-side report assertions

  • imported + failed === totalRows — violated → dataMismatch flag 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-coded students), 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).