School Management System
Replacing paper registers and legacy software for a school is straightforward until you hit fee management — where real-world flexibility, relational database rigidity, and auditable financial records have to coexist.
Most schools in the target market were managing student records, attendance, and fee collection through physical registers, spreadsheets, and legacy software with hard limitations. The manual overhead was significant and the existing tools couldn't adapt — a fee structure change meant manual recalculation, and there was no reliable audit trail for payments.
The product needed to digitise these workflows without flattening the complexity that made them hard in the first place. Schools don't have uniform fee structures. Within a single school, different student groups can have different structures. Discounts, installments, partial payments, and waivers all need to be tracked. One school required per-family billing — a single payment distributed across multiple enrolled siblings.
The challenge was bridging three things that pull in different directions: the flexibility of real-world fee structures, the rigidity of a relational database, and the UX that school administrators — not developers — would use daily.
02 / DecisionsGetting the fee module design right
The fee module went through multiple complete redesigns. Each iteration was driven by a specific failure in the previous version — insufficient auditability, insufficient flexibility, or UX that didn't map to how administrators actually worked. Each redesign meant a schema change.
The core issue was entity design. A fee structure is not the same as a fee assignment, which is not the same as a payment, which is not the same as a payment record. Collapsing these concepts — as earlier versions did — produced a system that was either too rigid to accommodate real-world fee changes or too loose to maintain a clean audit trail. The eventual model treated each as a distinct entity with explicit relationships and versioned fee structures, so changes going forward didn't affect historical payment records.
One iteration happened in production with live data. The migration had to transform existing records into the new schema without data loss, without orphaned records, and without disrupting the school mid-cycle. It was the most pressured technical execution on this project.
Modelling per-family billing
Standard billing models operate per student. One school's requirement broke this assumption: a single family invoice distributed across multiple enrolled siblings. Rather than treating this as a special case bolted onto the existing model, families were introduced as a first-class entity — able to aggregate students, carry a unified payment record, and still allow individual student-level tracking. Schools that didn't need family billing simply didn't use the entity.
The production migration was the hardest execution challenge. Existing fee records, payment histories, and student assignments all had to survive a schema that was not backwards compatible with what had been in production. Understanding the full shape of live data before writing a single migration line, accounting for partial payments and mid-cycle assignments, and maintaining referential integrity throughout — all without data loss or downtime.
Team continuity was the other sustained pressure. The team composition shifted significantly across the project. Maintaining architectural consistency and code quality standards across a changing team, while continuing to deliver features, required active ownership of what went into the codebase — not just what I built personally.
04 / OutcomeThe system is live. Fee collection, student records, and administrative workflows that previously ran on paper and spreadsheets now run on the system. The fee module — the hardest and most reworked part of the product — handles real school billing complexity including family-level payments, installment tracking, and a full audit trail.