13 — State Management (Library Module)
- 1. CatalogCubit (S1 — list + search + filters)
- 2. BookDetailCubit (S2)
- 3. BookFormCubit (S3 — create + edit)
- 4. IssueCubit (S4)
- 5. BorrowsCubit (S5 — active + history)
- 6. ReturnCubit (S6)
- 7. OverdueCubit (S7)
- 8. FinePayCubit (inline)
- 9. Repository layer
- 10. Cross-cubit refresh rules
Per-screen Cubits (Flutter/bloc; proposal, 00-shared/06) backed by
LibraryRepository(dio) calling the endpoints in 12_API_Mapping.md. Module-wide rules: no optimistic mutations, server-confirmed writes only; availability and fine amounts are server-authoritative; overdue is client-derived (OQ-2).
1. CatalogCubit (S1 — list + search + filters)
stateDiagram-v2
[*] --> initial
initial --> loading : Load(page 1)
loading --> loaded : BookDoc[] + meta
loading --> error : 5xx / offline(no cache)
loaded --> loading : Refresh | Search(q) | LoadMore
loaded --> empty : 0 items
error --> loading : Retry
loaded --> loaded : Filter(chips) — in-memory only
- State:
{status, books[], meta, query, filters{category?, status?, availableOnly?}}. - Load:
GET /books?page&limit&q(library.controller.ts:33-41);meta.hasNextgatesLoadMore(library.service.ts:75). - Search: debounced
q→ server regex (title/author/ISBN,book.repository.ts:26-30). - Filters are client-side only (OQ-5): applied to
books[]before render, never sent as params. - Caching: key
lib:catalog:{tenant}:{q}:{page}, TTL 5 min, stale-while-revalidate (00-shared/06 §3.3); pull-to-refresh bypasses. - Events:
Load,Refresh,Search(q),LoadMore,ChangeFilters,Retry.
2. BookDetailCubit (S2)
- State:
{status, book?};book=BookDocmodel (id, title, author, isbn, publisher?, category?, edition?, totalCopies, availableCopies, status, shelfLocation?, description?). - Load:
GET /books/:id(library.controller.ts:43-47); 404 →notFoundterminal-ish state (empty + back). - Derived:
isAvailable = availableCopies > 0,canIssue = isAvailable && status == available. - Events:
Load(id),Refresh(focus),IssueDone/EditDone/DeleteDone(re-fetch or pop). - No cache (volatile) — re-fetch on focus (availability changes elsewhere).
3. BookFormCubit (S3 — create + edit)
- State:
{mode, form{title, author, isbn, publisher?, category?, edition?, totalCopies, shelfLocation?, description?}, copiesProjection, status: idle|submitting|error(field?)}. - Create submit →
POST /books(library.controller.ts:27-31); edit →PATCH /books/:id(:49-53). Success → emit done with server doc; navigate. - Copies projection: create
available = total(library.service.ts:41-46); editavailable = max(0, available + Δ)(:87-90). - Errors: 409 duplicate ISBN →
fieldErrors.isbn(server message verbatim); 400 → mapdetails[].message→ field; 5xx → error (form preserved). - No optimistic writes. Draft persist
(proposed).
4. IssueCubit (S4)
stateDiagram-v2
[*] --> idle
idle --> ready : student selected && dueDate valid
ready --> submitting : Confirm
submitting --> success(record) : 201
submitting --> noCopies : 409 (library.service.ts:109-111)
submitting --> capReached : 409 (library.service.ts:116-120)
submitting --> error : 5xx / network / 404
noCopies --> ready : refresh book (re-check)
capReached --> ready : refresh student borrows
error --> ready : Retry
- State:
{status, book, studentId?, dueDate?, notes?, errorKind?}. - Submit:
POST /books/issue {bookId, studentId, dueDate, notes?}(issue-book.dto.ts:4-21,library.controller.ts:61-65). noCopies/capReachedare banner states — re-read the book/borrows and return toready; never auto-resubmit.- Client guard: due date must be future (server has no min —
issue-book.dto.ts:14-15). - Double-tap guard:
submittingblocks re-entry (server has no idempotency — QA-2).
5. BorrowsCubit (S5 — active + history)
- State:
{status, active[], history[], studentId}. - Load:
GET /books/borrows/:studentId/active(library.controller.ts:79-83) andGET /books/borrows/:studentId(:73-77) — not paginated (library.service.ts:195-201); client shows first N + "load more"(proposed). bookIdis populated (borrow-record.repository.ts:31,42);studentIdidentity comes from Students profile state (OQ-9).- Derived per record:
isOverdue = status=='active' && now > dueDate,daysOverdue = ceil((now − dueDate)/day),finePreview = daysOverdue × 5(mirrorslibrary.service.ts:211-217; labeled "estimate"). - Events:
Load(studentId),Refresh,ReturnedDone(recordId)(replace doc),FinePaidDone(recordId)(replace doc). - Caching: active 1 min TTL; history 5 min; focus refresh bypasses.
6. ReturnCubit (S6)
- State:
{status, record?, fineAmount?, fineStatus?, notes?, derived{daysOverdue, computedFine}}. - Prefill:
fineAmount = computedFine(client mirror of server rule); staff may edit (@Min(0)—return-book.dto.ts:9-13). - Submit →
POST /books/return {borrowRecordId, fineAmount?, notes?}(library.controller.ts:67-71) → server doc (server recomputes fine when no override —library.service.ts:159). notActivestate on 409 (library.service.ts:155-157) → banner + refresh record.- If returned
fineAmount > 0→ offer pay:POST /books/fines/:id/pay(library.controller.ts:85-89) →fineStatus: PAID(library.service.ts:203-209).
7. OverdueCubit (S7)
- State:
{status, rows[]}where row ={student, book, dueDate, daysLate, finePreview}. - Derived: aggregates active borrows from
BorrowsCubit-loaded data (per-student); no server endpoint (OQ-2). Persistent UI notice: "derived — server scan planned". - Refresh re-pulls the underlying active lists.
- Server-side sweep
(planned)(IMPLEMENTATION_PLAN.md:228) will replace this.
8. FinePayCubit (inline)
{status, recordId?, fineAmount?};Pay→ confirm dialog → endpoint → PAID doc.- Server doesn't guard state (OQ-6): cubit only exposes pay when
fineStatus == 'pending'andfineAmount > 0.
9. Repository layer
LibraryRepository (dio): methods map 1:1 to the endpoints in 12_API_Mapping.md;
wraps the envelope, unwraps data/meta, throws typed ApiException{code,message,details,requestId};
injects auth token + x-request-id. Models (Book, BorrowRecord) parse the raw
docs; enums kept as string constants mirroring book.schema.ts:7-12,
borrow-record.schema.ts:7-18.
10. Cross-cubit refresh rules
| Write done | Refresh |
|---|---|
| Book created/updated/deleted | CatalogCubit + BookDetailCubit |
| Issue succeeded | BookDetailCubit (availability) + BorrowsCubit (student) |
| Return succeeded | BookDetailCubit + BorrowsCubit + OverdueCubit |
| Fine paid | BorrowsCubit (record chip) |
All refreshes are server-confirmed re-fetches; no local doc mutation except replacing with the server response.