badal.sahani

Writing · Aug 2026 · 7 min read

A fees engine is a state machine, not a form

What it takes to model real school fees on ERPNext: plans, instalments, deferrals, discounts that post to the GL, and links you can hand to a guest.

The naive model of school fees is a form: an amount, a due date, a paid flag. The real thing is a state machine that runs for a year per student and touches the general ledger at every transition. Our fees module grew to 40-plus DocTypes not because we enjoy DocTypes, but because every one of them earns its place holding a state the business actually has.

The states nobody tells you about

  • Payment plans and instalments. Families choose how to pay — one shot, termly, monthly, or an education-loan EMI where a lender pays upfront and the family repays the lender. Each plan reshapes the schedule of receivables; switching between them mid-year is OTP-gated, with a preview of exactly what changes before it does.
  • Deferrals. A due date is a negotiation, not a constant. The deferral engine moves obligations without losing the audit trail of what was originally due and when.
  • Discounts as ledger events.A discount is not a smaller number on an invoice — it is a rule-driven entry that posts to the GL, permissioned by role, so finance can answer “how much did we concede this term, and who approved it?” from the ledger alone.
  • The long tail. Late fees, refunds, security deposits, transport slabs, split payments across ledgers, and recurring ancillary billing with pro-rata math — the half-month snapping rules have their own unit tests, which tells you how those conversations with finance went.

Multi-company is where it gets real

A school group is several legal entities in one system, and money must land in the right one. Chart-of-accounts heads are created automatically per company; gateway settlement accounts resolve per company; one payment can split into per-company Payment Entries. Row level security via permission query conditions keeps each school’s staff inside their own lane.

Links you can hand to a stranger

Fee checkout has to work for someone with no login — a guardian on a phone, following a link. Those links address fees by an opaque hash rather than a document ID, so nothing about the URL leaks record identity or invites enumeration. Small choice, large consequence: the guest checkout surface stops being an attack surface on your naming scheme.

The rewrite

Version one of all this taught us what the model should have been, so we built version two and migrated to it with zero downtime — data moved in patches, both models live during the transition, cutover without a maintenance window. A fees engine holds a school group’s revenue; “we’ll be back at 6 AM” is not a migration strategy.

If you are building on ERPNext, the lesson is not our schema. It is that fees are a domain, and domains deserve modeling. The form-based version works right up until the first family asks to defer one instalment of a discounted plan paid through an EMI — and then it very much does not.

← all writing