Building reports the finance team stops exporting to Excel to fix
The finance team exports the monthly fee collection report to Excel, spends forty minutes fixing it, and only then takes it to the principal. We have seen this pattern in every school ERP we have audited. The report is technically present in the system, but nobody internal trusts the numbers as-is.
The fixes are usually small and mechanical. Discount amounts that don't reconcile against the concession approval log. Late fees applied on the wrong date. GST split out on some rows but not others. Transport fees billed for students who deactivated mid-month. Each discrepancy is a rounding or edge-case problem the original developer didn't encounter because they tested with clean sample data.
We rebuild these reports by sitting with the accountant for one fee cycle. Not to gather requirements — to watch them fix the spreadsheet. Every column they delete, every formula they paste in, every row they manually override tells us exactly where the logic in the ERP is wrong. The spreadsheet is the spec.
Most of the corrections fall into three categories.
The first is timing. A concession approved on April 5th but applied retroactively to the April 1st billing run creates a mismatch. The ERP shows full billed amount; the accountant knows the principal approved the concession before the due date, so the effective bill is lower. The fix is to compute billed amount against concession state as of the billing date, not as of the report date.
The second is partial-period billing. A student who withdraws on the 17th of a month with a 30-day notice period gets billed differently than one who leaves on the 3rd. Most ERPs either bill the full month or nothing, and the accountant prorates in Excel. The logic belongs in the billing engine, not in a post-hoc report adjustment.
The third is tax rounding. Indian GST on education services has specific exemptions depending on whether the school is above or below a revenue threshold, and on how auxiliary services like transport and canteen are structured. Rounding at the line-item level versus the invoice level produces different totals by a rupee or two per student, which aggregates to thousands across a 2,000-student school. The accountant reconciles to the invoice-level rounding because that's what was collected. The report needs to match.
A concrete example: we inherited a school ERP where the fee defaulters report showed 347 students with outstanding balances. The accountant's spreadsheet showed 281. The gap was 66 students who had paid after the report's cutoff date but before the report was actually run — because the report queried `due_date < NOW()` for billing but `payment_date <= report_date` for collections, creating a five-day window where paid students still showed as defaulters. The accountant's fix was to filter the Excel export by payment status. Our fix was to align both queries to the same cutoff parameter.
SELECT s.student_id, s.name,
COALESCE(b.billed_amount, 0) - COALESCE(p.paid_amount, 0) AS balance
FROM students s
LEFT JOIN (
SELECT student_id, SUM(amount) AS billed_amount
FROM billing
WHERE billing_date <= $1
GROUP BY student_id
) b ON b.student_id = s.id
LEFT JOIN (
SELECT student_id, SUM(amount) AS paid_amount
FROM payments
WHERE payment_date <= $1
GROUP BY student_id
) p ON p.student_id = s.id
HAVING balance > 0Both subqueries use the same `$1` parameter. The report either includes everything up to that timestamp or it doesn't — there is no window where the two halves disagree.
The test for whether a report is done is not whether it renders without errors. It is whether the finance team runs the report, glances at the totals, and attaches it to the board meeting agenda without opening Excel. That takes two or three iterations, and it requires someone on our side who understands double-entry bookkeeping well enough to argue with the accountant about whether a contra entry should appear in the receivables aging.
We don't consider a school ERP shipped until that happens.