02 — User Personas (Fees Module)
- 1. Admin / Bursar — defines fee structures
- 2. Accountant / Cashier — records payments
- 3. Parent / Guardian — pays and tracks dues
- 4. Org Admin — financial oversight
- 5. Platform Admin (cross-tenant support)
- 6. Role × fees-appearance matrix
Who touches fees, what they can do on the current backend, and what the client should do for them. Permissions reflect
permissions.constants.tsand the system roles inrole.schema.ts:42-48— fees endpoints today are guarded byJwtAuthGuardonly (fees.controller.ts:21-24), so RBAC below is the intended model(planned).
1. Admin / Bursar — defines fee structures
- Job: set up the fee structures for each class and term (tuition, transport, lab, library…), due dates, whether active.
- API surface:
POST/PATCH/DELETE /fees/structures…(fees.controller.ts:28-56). - Constraints: structure requires
name, classId, academicYearId, items[], totalAmount, dueDate; currency defaultsXAF,lateFeedefaults0(fee-structure.schema.ts:9-34;create-fee-structure.dto.ts:26-68). Deletion is soft (base.repository.ts:68-74) — structures remain visible history; invoices already generated are unaffected. - Pain points to solve: creating a season of structures fast (duplicate
structure endpoint is
(planned),IMPLEMENTATION_PLAN.md:205); seeing which structures are active vs archived; avoiding atotalAmountthat doesn't match the sum of line items (server does not validate the sum).
2. Accountant / Cashier — records payments
- Summary: collects cash/mobile-money at the till and posts against an invoice.
- Own surface:
POST /fees/invoices/:id/payments(fees.controller.ts:70-74), reads dues viaGET /fees/dues(fees.controller.ts:76-80). - Role perms: default Accountant role =
['fees.collect', 'student.read'](role.schema.ts:42-48). - Constraints: every payment needs a fresh
idempotencyKey; replay with the same key returns the original payment (never doubles —fees.service.ts:148-152). Payment method must becash|bank_transfer|mobile_money|cheque|other(record-payment.dto.ts:21-25). - Pain points: concurrent keyboards double-submitting (mitigated by idempotency);
accidentally overpaying (no cap server-side); chasing an exact "make paid" state
when partial payments exist (
PARTIALis derived frompaidTotal >= total,fees.service.ts:169-172).
3. Parent / Guardian — pays and tracks dues
- Summary: sees the child's invoices and balance, pays online, reads receipts.
- Surface (read):
GET /fees/students/:studentId/invoices(fees.controller.ts:64-68) — childstudentIdcomes from the Students/Profiles module (no parent-scoped endpoint). Online pay goes through Payments:POST /payments(process-payment.dto.ts), receipts viaGET /payments/receipts(payments.controller.ts:63-67). - Role perms: default Parent =
['student.read'](role.schema.ts:50-56); nofees.*perms — parent fees UI is about the data surfaces the parent role can already reach + gap flagged (OQ-9/12_API). - Constraints: overpay/partial allowed; server derives status from sums. Receipt
is print/shareable client-side from the
receiptsdoc.
4. Org Admin — financial oversight
- Summary: sees all structures, invoices, dues; reconciles odd states.
- Surface today: everything under
/fees/**+/payments/**; no admin-specific fees endpoint (dues report(planned),IMPLEMENTATION_PLAN.md:208discounts,:211statement).
5. Platform Admin (cross-tenant support)
- Summary: support agent;
BaseRepository.scopedFilterbypasses tenant scope for platform admin (base.repository.ts:20-23) — a support-only, read-mostly surface on the same endpoints(planned)across tenants.
6. Role × fees-appearance matrix
| Screen | Admin/Bursar | Accountant | Parent | Org admin |
|---|---|---|---|---|
| Fee structures list/detail | CRUD (fees.controller.ts:28-56) | read | — | read |
| Student invoices | ✓ (:64-68) | ✓ | child only | ✓ |
| Record payment | ✓ | ✓ (till) | via POST /payments | view only |
| Dues | ✓ (76-80) | ✓ | child dues (derived) | ✓ (e.g. aggregation) |
| Payments v2 + receipts | payments.read (81) | — | receipts.read (85) | view |
All rows are guidance until per-endpoint
@Permissions()metadata lands (OQ-9). Fees endpoints today require only a valid JWT.