School management ERP with permissions enforced per branch
Students, fees, payments, payroll and accounting across multiple branches, with role and branch permissions enforced in the database and fee reminders sent over WhatsApp.

- branch isolation
- Multi
- enforced permissions
- RLS
- fee reminders
The problem
Multiple branches, each needing to see its own students, fees and staff (and head office needing to see all of them), with the same application serving both without leaking data between sites.
Fee collection depended on someone remembering to chase, and the records of who had been chased lived in a notebook.
What we built
Scope by role and by branch, in the database
Row-level security policies carry both dimensions, so a branch administrator's queries return their branch's rows and nothing else, regardless of which screen asked. The interface reflects the same rules, but the database is what enforces them.
One ledger from enrolment to accounts
Students, fee invoices, payments, expenses, employees and payslips feed a single accounting layer, so a fee receipt and a salary payment land in the same books and the reports reconcile without a spreadsheet in between.
Reminders where parents actually read them
Fee reminders go out over WhatsApp from templates, turning collection into a scheduled action rather than a remembered one.
Closing the obvious doors
Public signup disabled, account creation moved behind a privileged server-side function holding the only elevated credential, and receipts in their own storage bucket.
Built with Claude Code, under review
The system was built and extended with Claude Code. Its commit history carries a Claude co-author trailer on 136 of 242 commits, and changes arrive as pull requests that must pass lint, tests and a build in CI before they merge. The agent drafts quickly; the review and the test suite decide what ships.
Reviewed by seven read-only agents
One project command runs seven review agents in parallel: security, database, accounting, code quality, deployment and dependencies, UX and compliance, and a features reviewer. They only read the code. Their reports are merged into one summary, the highest-severity findings are spot-checked against the source, and what survives is ranked into waves of work.
Rules written as ordered migrations
Permissions and money rules live in ordered, idempotent, transaction-wrapped migrations: role and branch policies, an audit trail written by database triggers rather than the browser, money functions that post an invoice, payment and ledger entry together, ledger guards and a private receipts bucket. Each carries a pre-flight checklist and rollback notes.

The outcome
Branch data isolation that a mistake in the interface cannot undo, because the policy is evaluated in Postgres.
Real-time multi-session sync, so two staff working the same list see each other's changes.
Fee chasing became a process with a record rather than an act of memory.
Forty-two test files, including date and payroll logic checked under two time zones, and a CI workflow that lints, tests, builds and audits dependencies on every pull request.
Built with
- React
- Supabase
- PostgreSQL
- Row-level security
- Edge Functions
- WhatsApp API
- Vercel
- Claude Code
Where this is applied
Page copy drafted with AI assistance. Skofi is accountable for it. Editorial policy
Have something shaped like this?
If any part of the above resembles a problem you are living with, a short call is the fastest way to find out whether it is the same problem underneath.
What happens on the call
- 15 minutes, no deck
- We map the process on the call
- A written scope, or an honest no