Fees & finance
Fee collection flow
How a charge becomes money in the bank: fee types, structures, per-student fee instances, counter and online payment, receipts, waivers, and the overdue follow-up list.
Fees & finance guide for day and boarding schools.
Last updated August 29, 2026
What this is for
Everything in the fees module is a chain, and each link is a different record. A fee type is the catalogue entry for a charge. A fee structure prices that charge for one academic year and one class or grade, and says how many installments it splits into. Generating creates FeeInstance rows: one per student, per structure, per installment, each with its own due date and status. Only a fee instance can be paid, chased, waived or refunded, so nothing happens for a family until generation has run.
Set the chain up once at the start of the academic year, immediately after the calendar and the class list exist and before the first collection day. Mid-year additions are normal: a new student picks up their fee lines at enrolment, a transport rider picks up monthly lines when they are assigned to a stop, and a library fine posts on its own. What you should not do mid-year is change a structure that has already been generated against, because the instances that came from it do not follow the change.
The screens, in the order you use them
- /fees/fee-types - the charge catalogue. Codes matter: TRN is read by transport, and LIB_FINE is created automatically the first time a library fine is posted.
- /fees/fee-structures - amount, installment count, due date and grace days for one academic year, tied either to a specific class or to a grade.
- /fees/fee-schedule - the grid view of types against grades for the selected year, with /fees/fee-schedule-view as a read-only version.
- /fees/bulk-generate and /fees/setup - create the fee instances a class of students is missing. Both wrap the same per-student call, so re-running is safe and only fills gaps.
- /fees/class-summary - the check before you open collection: what each class has been billed, and whether the totals look like the fee circular.
- /fees/collections - the counter. Search the student, confirm the balance, record the payment, then print or email the receipt.
- /fees/payments, /fees/receipts and /fees/payment-history - the audit trail, receipt reprints, and the per-student ledger.
- /fees/defaulters, /fees/outstanding and /fees/forecast - who is late, how much is unpaid school-wide, and what is due in the next 30, 60 and 90 days.
A counter payment and an online payment are not the same transaction
| Behaviour | Counter (/fees/collections) | Online (/fees/pay-online) |
|---|---|---|
| Who records it | Finance staff, against any student | The student, or a linked parent |
| Part payment | Allowed, any amount up to the balance | Refused - the amount must equal the whole outstanding balance of one installment |
| Payment method stored | CASH, CHEQUE, BANK_TRANSFER, CARD, ONLINE or OTHER, with a reference | The gateway label, plus the gateway transaction id |
| Receipt email | A button on the receipt dialog - it is not sent automatically | Attempted automatically as soon as the payment is captured |
| Duplicate protection | Receipt numbers are unique per transaction | Idempotent on the gateway transaction id |
| Reversal | Cheque bounce, or a refund request at /fees/refunds | Refund request at /fees/refunds - the gateway is not called |
Both paths write the same FeePayment record and the same receipt series, RCP-YYYY-000001 upwards, and both recompute the fee instance from its completed payments afterwards. That recompute is what moves an instance to PARTIAL, PAID or OVERDUE, so the status column is always derived and never typed in.
Families, transport and library fines
Families open /fees/me for balance and history. Parent notifications link there through feeLinkFor, which builds /fees/me with both studentId and childId set to the same value so old bookmarks keep working; a parent who is not linked to that student at /parents is refused. Staff who open /fees/me are redirected to the fees hub, because it is a learner page. Transport fees appear on the same screen: assigning a student to a stop that has a monthly fee creates monthly TRN instances due on the 7th, from the current month to the end of the academic year and capped at twelve. Ending that assignment waives the remaining unpaid lines and leaves paid history alone. A library fine posts a real LIB_FINE fee instance due in fourteen days and is collected like any other fee.
Who owns it
- Fee operations - Platform Admin, Principal, Vice Principal, Support Staff - Finance, and Admin. These five roles can see school-wide fee data, record payments, and raise waivers and refunds.
- Waiver and refund approval - a different person from the one who raised it, and Principal or Vice Principal once the amount is high value.
- Everyone else is read-only or scoped: a teacher can see fee lines for their own class through /my-class, a parent only for a linked child, a student only for themselves.
Limits, stated plainly
- Nothing in fees runs on a timer unless a server administrator schedules scripts/run-school-cron.sh with FEES_CRON_SECRET. Without it no reminder is ever sent and no fee line ever advances along the dunning ladder.
- There is no discount rules engine. Sibling and staff-ward concessions are reporting labels on a waiver; every amount is typed in by a person.
- A late fee is calculated for display at the counter and is never posted. The lateFineAmount field on a fee instance is not written by any application route, and a payment larger than the outstanding balance is rejected.
- This is not a general ledger. There is no chart of accounts and no trial balance; statutory accounting stays in the accounting package the school already runs, fed by CSV export.
Common questions
Quick answers in plain language.
A student has no fee lines at all on /fees/me. What is missing?+
Almost always a fee structure the student matches. Generation looks for an active structure in the current academic year on the class the student is actively enrolled in, and falls back to grade and section only when there is no active ClassStudent row. Check the student is active with a class enrolment at /students, that a structure exists for that class or grade at /fees/fee-structures, then run /fees/bulk-generate for the class.
Can a parent pay only part of an installment online?+
No. The online routes accept the full outstanding balance of a single installment and reject anything else with an amount mismatch, so a family that wants to pay in parts has to do it at the counter, where staff can enter any amount up to the balance. Splitting a large charge into more installments at /fees/fee-structures is the better fix.
Why did the receipt email not arrive after a counter payment?+
Because it is not sent automatically. Counter collection saves the payment and shows a receipt dialog with an Email receipt button; only gateway and simulated captures try the email on their own. Send it from that dialog, from /fees/receipts, or from /fees/payment-history. A payment stamped [RECEIPT_EMAIL:FAILED] means SMTP rejected the send - check /settings/email-test.
Does changing a fee structure amount update invoices that already exist?+
No. A fee instance stores its own amount and due date when it is generated, so editing the structure afterwards only affects instances generated later. Correcting already-generated lines means editing them individually, or deleting and regenerating - which is why /fees/class-summary should be checked before collection opens.
Related searches
School leaders and IT teams often search for: fee structure setup, and tuition billing schools.

