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

14 — QA Checklist (Fees Module)

Manual + automated QA for the Fees client, contract-driven. Sources: fees.service.ts, payments.service.ts, invoice.schema.ts, fees DTOs, finance.worker.ts, fee-reminder.job.ts. Concurrency, partial, overpay, duplicate, and idempotency cases are the money-correctness core.


1. Functional (server-contract mirrors)

Fee structures

  • Create with all fields → 201 doc; dueDate stored as Date; defaults (currency='XAF', lateFee=0, isActive=true) applied (fee-structure.schema.ts:9-37).
  • Create with missing classId/academicYearId/items/totalAmount/dueDate → 400 VALIDATION_ERROR with details[] (create-fee-structure.dto.ts:26-68).
  • Edit: classId/academicYearId not accepted (absent from update-fee-structure.dto.ts:15-54).
  • Delete = soft delete: row gone from list, GET /fees/structures/:id → 404 (base.repository.ts:68-74); no cascade to invoices (OQ).
  • Line items sum mismatch vs totalAmount → warning banner (server accepts).

Invoice generation

  • Generate → 201; totalAmount = max(0, structure.totalAmount − Σ discounts) (fees.service.ts:117-119); status issued, issuedAt set (:122-130).
  • Duplicate invoice: same (student, structure, year) → 409 DUPLICATE_RESOURCE "Invoice already exists for this student and term." (fees.service.ts:107-115); UI links to existing invoice.
  • Unknown structure → 404 (fees.service.ts:105).
  • Discount with negative amount → 400 (generate-invoice.dto.ts:20-22).
  • Invoice shows on GET /fees/students/:id/invoices immediately (fees.service.ts:142-144).

Payments (fees)

  • Record payment → 201; status: 'completed'; invoice paidAmount updated (fees.service.ts:162-176).
  • Partial payment (< due) → invoice partial; dues list shows remaining due (fees.service.ts:168-172,213-216).
  • Full payment (>= due) → invoice paid; row leaves dues list (fees.service.ts:194-201 filters paid out).
  • Overpayment (amount > due) → accepted (no cap); invoice → paid (fees.service.ts:169-172) — record the resulting paidAmount display (OQ-2).
  • Idempotency replay: re-POST with the SAME idempotencyKey → same payment doc, paidAmount unchanged (fees.service.ts:148-152); UI shows success.
  • Pay on paid/cancelled invoice → 409 "Invoice is already paid or cancelled." (fees.service.ts:155-160).
  • Invalid paymentMethod string → 400 (enum list in record-payment.dto.ts:21-25, payment.schema.ts:7-13).

Payments v2 / receipts

  • POST /payments{payment, receipt}; transactionReference format TXN-{ts}-{uuid8} (payments.service.ts:215-217); receipt RCP-{ts}-{n} (:180-198).
  • linkToInvoice recomputes invoice paidAmount/status when invoiceId given (payments.service.ts:200-213).
  • Refund: only completed → allowed; partial → partially_refunded; full → refunded; over-refund → 409 (payments.service.ts:74-111).
  • Reconcile: success→completed, failed→failed, else pending (payments.service.ts:121-127).
  • Receipts list/detail render all fields (receipt.schema.ts:7-41).

2. Concurrency (critical)

  • Two concurrent posts, same invoice, different keys → both recorded; paidAmount = sum; status derived from last recompute (fees.service.ts:168-172).
  • Same key raced concurrently → exactly one payment created; second resolves to the same doc (payment.schema.ts:40-41 unique index is the final arbiter; fees.service.ts:148-152 first line).
  • Cashier + online payment overlap → linkToInvoice sums both (payments.service.ts:200-213).
  • Dues list refresh mid-write → no stale overwrite of local state; server response wins.
  • Refund racing a second refund → 409 guard on refundedAmount (payments.service.ts:81-86).

3. Visual / layout

  • Amounts: mono tabular, grouped, currency symbol; XAF = 0 decimals, USD = 2 (06 §0); no "-0"; zero = "Settled".
  • Status chips icon + text + color (06 §5 palette) in light/dark.
  • Dues row overdue treatment; chips contrast ≥ 4.5:1 (11 §6).
  • Payment sheet on small phones: CTA reachable above keyboard, no clipped fields.
  • Tablet master-detail ≥ 840 dp; desktop rail + hover.

4. Accessibility (00-shared/09)

  • Amount announcements with currency; live-region on payment submit/success/error.
  • Every chip/icon has text label or tooltip; touch targets ≥ 48 dp.
  • Focus order: filters → list → FAB; dialogs trap focus; ESC closes.
  • Forms: error text + Semantics(error); announce validation on submit.

5. Performance

  • Dues/invoice lists paginate (limit 20 default; max 100) — infinite scroll ends cleanly on meta.hasNext == false (pagination-query.dto.ts:13-19,47-54).
  • Invoice detail: invoice + payments fetched in parallel (Future.wait), single skeleton.
  • Student invoices non-paginated — verify perf with large arrays (fees.service.ts:142-144); ListView.builder + RepaintBoundary on receipt doc.
  • No layout jank from amount counting animations (reduced-motion path).

6. Offline & network (00-shared/10 §2)

  • Offline: dues/structures/receipts from cache + AppOfflineBanner; payments and invoice generation blocked with guidance.
  • Network drop mid-POST payment → same key retried → replay success (never double).
  • 429 RATE_LIMITED → countdown, no auto-retry; 5xx → retry with backoff.

7. Tablet / desktop

  • Master-detail dues → invoice detail; two-pane structure list; payment sheet as drawer (proposed); N/Ctrl+Enter shortcuts.

8. Localization

  • Money formatting per locale (intl); currency words in amounts announced localized; i18n keys for statuses/errors (fees.* namespace, 15 §10).

9. Permissions

  • No RBAC on fees endpoints today (fees.controller.ts:24; OQ-9) — verify UI gates (fees.collect for Accountant, role.schema.ts:42-48) degrade gracefully when server adds @Permissions; hide actions on 403.

10. Dark mode

  • All chips/amounts/banners pass 02 §10 parity; no hardcoded colors (11 §8).

11. Animations

  • Reduce-motion: instant transitions; counting animation disabled (10 §4).

12. Security

  • Tokens in secure storage; no amount/log leakage; no raw server internals in error UI (00-shared/07 §11).
  • idempotencyKey never reused across different payment attempts (only retries).

13. Server-backed edge mirrors

  • Duplicate invoice — verify 409 → UI navigates to existing invoice (fees.service.ts:107-115).
  • Reminder idempotency (planned)FeeReminderJob enqueues one send-payment-reminder job per invoice without a dedup key (fee-reminder.job.ts:25-41); overlapping cron runs would re-enqueue (no jobId/dedup) → when the worker lands, QA must verify no duplicate reminders per invoice per day (OQ-6).
  • Overdue marking (planned)finance.worker.ts:75-91 flips issued|partial past-due → overdue; verify chips + dues filtering after the cron fires (repeatable job 0 6 * * * UTC, scheduler.service.ts:48-55).
  • Late feelateFee field stored but never applied (OQ-7): UI must not promise automatic late fees.
  • CastError on malformed ids → 400 VALIDATION_ERROR "Invalid resource identifier." (http-exception.filter.ts:47-48,91-95).

QA scripts

  • Unit: cubits (record-payment 409 branches, derived due, refund guards) — 13 §12.
  • Widget: money states (loading/error/empty), chip palette snapshots (light/dark), payment sheet keyboard, receipt doc.
  • Golden: AmountText (XAF/USD/locales), StatusChip (all statuses × 2 themes), ReceiptDocument, InvoiceCard, DueRow (07 §G).
  • E2E (P0): full loop — admin creates structure → generates invoice → cashier records partial → parent pays remainder online (v2) → receipt printed; duplicate-invoice and double-post paths asserted.
  • Integration: cron-triggered overdue + reminder paths once workers are wired (planned).