14 — QA Checklist (Fees Module)
- 1. Functional (server-contract mirrors)
- 2. Concurrency (critical)
- 3. Visual / layout
- 4. Accessibility (00-shared/09)
- 5. Performance
- 6. Offline & network (00-shared/10 §2)
- 7. Tablet / desktop
- 8. Localization
- 9. Permissions
- 10. Dark mode
- 11. Animations
- 12. Security
- 13. Server-backed edge mirrors
- QA scripts
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;
dueDatestored as Date; defaults (currency='XAF',lateFee=0,isActive=true) applied (fee-structure.schema.ts:9-37). -
Create with missing
classId/academicYearId/items/totalAmount/dueDate→ 400VALIDATION_ERRORwithdetails[](create-fee-structure.dto.ts:26-68). -
Edit:
classId/academicYearIdnot accepted (absent fromupdate-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); statusissued,issuedAtset (: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/invoicesimmediately (fees.service.ts:142-144).
Payments (fees)
-
Record payment → 201;
status: 'completed'; invoicepaidAmountupdated (fees.service.ts:162-176). -
Partial payment (< due) → invoice
partial; dues list shows remainingdue(fees.service.ts:168-172,213-216). -
Full payment (>= due) → invoice
paid; row leaves dues list (fees.service.ts:194-201filters paid out). -
Overpayment (amount > due) → accepted (no cap); invoice →
paid(fees.service.ts:169-172) — record the resultingpaidAmountdisplay (OQ-2). -
Idempotency replay: re-POST with the SAME
idempotencyKey→ same payment doc,paidAmountunchanged (fees.service.ts:148-152); UI shows success. -
Pay on
paid/cancelledinvoice → 409 "Invoice is already paid or cancelled." (fees.service.ts:155-160). -
Invalid
paymentMethodstring → 400 (enum list inrecord-payment.dto.ts:21-25,payment.schema.ts:7-13).
Payments v2 / receipts
-
POST /payments→{payment, receipt};transactionReferenceformatTXN-{ts}-{uuid8}(payments.service.ts:215-217); receiptRCP-{ts}-{n}(:180-198). -
linkToInvoicerecomputes invoicepaidAmount/statuswheninvoiceIdgiven (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, elsepending(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-41unique index is the final arbiter;fees.service.ts:148-152first line). -
Cashier + online payment overlap →
linkToInvoicesums 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+RepaintBoundaryon 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-
POSTpayment → 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+Entershortcuts.
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.collectfor Accountant,role.schema.ts:42-48) degrade gracefully when server adds@Permissions; hide actions on 403.
10. Dark mode
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).
-
idempotencyKeynever 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)—FeeReminderJobenqueues onesend-payment-reminderjob per invoice without a dedup key (fee-reminder.job.ts:25-41); overlapping cron runs would re-enqueue (nojobId/dedup) → when the worker lands, QA must verify no duplicate reminders per invoice per day (OQ-6). -
Overdue marking
(planned)—finance.worker.ts:75-91flipsissued|partialpast-due →overdue; verify chips + dues filtering after the cron fires (repeatable job0 6 * * *UTC,scheduler.service.ts:48-55). -
Late fee —
lateFeefield 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).