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

09 — User Behaviour (Fees Module)

Expected behaviour derived from server contracts, plus the failure modes that money UIs invite. Sources: fees.service.ts, payments.service.ts, fees DTOs, fees/payments schemas.


1. Expected behaviour (server-contract-driven)

  • Balance is server truth. due = totalAmount − paidAmount is produced by GET /fees/dues (fees.service.ts:213-216); the client never recomputes an authoritative balance, only derives display from server values (06 §0).
  • Payment posts are idempotent. Reusing an idempotencyKey returns the original payment (fees.service.ts:148-152). Double-tap or retry-after-timeout must NOT double-count.
  • Invoice generation is once per term. Duplicate → server 409 ("Invoice already exists for this student and term.", fees.service.ts:107-115); the UI treats it as "navigate to existing", never as a hard error.
  • Status is derived/pushed, never client-mutated. issued → partial/paid by paidTotal >= totalAmount (fees.service.ts:168-176); overdue is pushed by the finance worker (finance.worker.ts:74-91) (planned).
  • Overdue rendering should prefer invoice.status == 'overdue' (invoice.schema.ts:12); money past-due that still holds issued/partial (worker not yet run) is styled identically client-side but flagged (proposed).

2. Common mistakes & countermeasures

MistakeCountermeasure (client)
Double-tap "Record payment"disable while posting + reuse the same idempotencyKey on retry (record-payment.dto.ts:31-34)
Wrong amount paidprefill amount = due; MoneyField >= 0; overpay inline warning (server allows, OQ-2)
Posting to a paid/cancelled invoicehide the action when `paid
Misreading a 409 replay as failurerender as success — the returned doc is the original payment (fees.service.ts:148-152)
Items sum != totalAmountLineItemsTable warning banner; auto-sum helper on create (server never validates)
Confusing XAF vs USDcurrency locked from context: structures default XAF (fee-structure.schema.ts:27-28), v2 payments default USD (payments/schemas/payment.schema.ts:42-43)
Reading refunded/failed sums as paiduse server paidAmount/dues; never hand-sum v2 logs (OQ-5 two sumByInvoice implementations)
Editing class/year after createfields absent from UpdateFeeStructureDto (update-fee-structure.dto.ts:15-45) — disabled + note
Offline payment attemptblocked with guidance (no offline write queue for fees; 00-shared/07 §10)

3. Power & new users

  • Power (cashier): remember last payment method; paidDate = today default; one-tap "Collect" on dues rows; keyboard-friendly sheet (.next, .done).
  • Power (admin): structure duplicate (planned) (IMPLEMENTATION_PLAN.md:205), bulk invoice generation (planned) (today generation is per-student sync, OQ-8).
  • New (parent): only the child-invoices read surface; every screen explains "Total / Paid / Due"; "Pay online" (forward-looking).

4. Mobile & a11y users

  • Touch targets ≥ 48 (00-shared/02 §9); rows ≥ 56.
  • Amounts with currency symbol/word + tabularFigures; screen readers announce localized (e.g. "fifty thousand CFA francs").
  • Status chips are icon + text + color — never color-only.
  • Forms: labels + Semantics(error); errors announced; MoneyField announces value + currency.
  • Payment sheet: live-region announce submit progress, success, errors.

5. Cross-device & conflict

  • Two terminals posting the same invoice: idempotency prevents doubles; the last write recomputes paidAmount/status from totals (fees.service.ts:168-176).
  • Parent pays online while a cashier posts offline: both recorded; linkToInvoice sums all (payments.service.ts:200-213).
  • Lists vs version drift: re-fetch on focus + pull-to-refresh + invoice.updated WS (planned) topic (00-shared/07 §8).

6. Interrupted sessions & retries

  • Failure mid-POST payment: same idempotencyKey retried → replay success.
  • Failure mid-POST invoices/generate → retry hits 409 → navigate to existing invoice with info banner.
  • Session expiry mid-payment: key retained while the sheet is open; discard invalidates client flow (server key stays inert until reused).
  • Background kill mid-edit structure: re-fetch on open; form preserves draft (proposed).

7. Abandonment

  • Structure form dirty → "Discard changes?" dialog; (proposed) draft to shared_preferences.
  • Payment sheet back mid-post → "Payment not recorded — discard?"; discarding abandons the key.
  • Generate-invoice dialog cancel → nothing sent; invoice exists only after 201.

8. Frustration points → UX treatments (summary)

FrustrationTreatment
Duplicate-invoice 409info banner + link to the existing invoice, dialog open
Pay on paid/cancelled invoiceclose sheet + info banner, never an error dialog
Overdue backlog with 5-10 rowsgroup/filter dues by status; overdue chip distinct from "valid error"
$0 balance"Settled" text, never "−0"
Receipt print/share(proposed) RepaintBoundary render → system share/print
No single-invoice endpointinvoice detail composes from student invoices + GET /payments/invoice/:id (OQ-3)

9. Adoption path (holistic)

Fees is the highest-trust workflow; order screens by trust: (1) dues at a glance (fees home → dues list), (2) collect at the till (dues → record payment; cash is the dominant method), (3) structure management once a term, (4) online pay + receipts for parents (forward-looking), (5) reports / reminders once server lands (planned).