RentTracker: The Hidden Accounting Engine Behind Property Management SaaS

How one Next.js app turns rent, dues, deposits, and bank transfers into a precise reconciliation system that behaves more like ERP software than a landlord spreadsheet.

9 min read • View on GitHub • More from dturkuler

A landlord’s desk becomes a financial circuit. One bank transfer arrives as a stamped envelope, then splits into several ledger slips that move into separate drawers for rent, dues, and deposits. The scene explains that RentTracker treats one payment as many possible obligations, and that reconciliation is the real product.
RentTracker’s core idea is not tracking tenants. It is allocating one incoming payment across multiple debts without losing accounting precision.
Key Takeaways

Why RentTracker Is Really About Debt, Not Dashboards

Most property tools start with the tenant and end with a paid badge. RentTracker starts with the obligation. That is a deeper model, because the hard problem is not displaying rent due. It is deciding how a single incoming payment should settle rent, dues, deposits, and any leftover balance without breaking the books.

That difference matters. A dashboard can tell you who paid. An accounting engine can tell you what was paid, what remains, what was partially satisfied, and how that status changed over time. RentTracker is built around the second problem.

The Splitter Pattern That Makes Reconciliation Work

The heart of the repo is the reconciliation flow in process-accruals and reconcile/match. A bank movement does not simply flip a debt from open to closed. It becomes a transaction, then gets allocated across one or more accruals until each obligation is either partially covered or fully satisfied.

One bank movement can settle several obligations. The mechanism is not mark-paid logic. It is allocation logic that updates status as each accrual is satisfied.

A close-up mechanical switchboard routes one incoming wire into several terminals, each terminal receiving only part of the current until the full load is balanced. The image explains how RentTracker allocates a payment across accruals and updates partial or paid status as the balance changes.
The splitter pattern is the article’s technical center of gravity. It turns reconciliation into a controlled allocation loop instead of a binary paid or unpaid flag.

The key idea is simple: needed = amount - paid_amount. If a transaction covers only part of an accrual, the system keeps that remainder visible. If the balance reaches zero, the status becomes Paid. If not, it stays Partial. That is a small rule with large consequences, because it preserves the legal and operational truth of the account.

That is why the repo feels unusual. It does not treat reconciliation as an afterthought. It treats reconciliation as the product.

Why This Feels Like ERP, Not CRUD

The domain model is more disciplined than a typical admin app. Rent, aidat, deposits, leases, residents, funds, transactions, and allocation records are separate objects, which means the code can preserve the differences that matter in accounting. The result is less like a landlord checklist and more like a small ERP system built for one narrow financial lifecycle.

ModelWhat it optimizes forWhat it loses
Spreadsheet trackingFast manual entryAllocation history, auditability, and consistent status logic
Generic property CRUDBasic admin workflowsAccounting semantics and partial settlement precision
RentTrackerFund-level reconciliationSimplicity, because the model is intentionally more structured

A flat system can answer, “Did the tenant pay?” RentTracker can answer, “What did that money settle, in what order, and what still remains open?” That is the difference between records and accounting.

The Onboarding Shortcut: Resident, Lease, and Linkage in One Move

The resident endpoint shows the product thinking clearly. When a property is present, POST /api/residents can create the resident, create the lease, and link them through the join table in one flow. That compresses onboarding and avoids the common trap of forcing users through a string of disconnected admin steps.

// Smart onboarding flow in POST /api/residents
const resident = await createResident(input)

if (propertyId) {
  const lease = await createLease({ propertyId, residentId: resident.id })
  await linkLeaseResident({ leaseId: lease.id, residentId: resident.id })
}

return { resident }

That is not just convenience. It is a signal that the repo optimizes for workflow compression. The system wants the user to move from intent to a valid financial relationship in as few steps as possible.

The Data Model Is Doing the Real Product Work

The schema in DATABASE_SCHEMA.md is where the design becomes visible. Join tables like lease_residents preserve many-to-many relationships. Tables like condition_report_items keep operational history intact. JSONB fields handle files and photos without forcing the relational core to become messy.

That combination matters. Normalization keeps the accounting and tenancy model honest, while JSONB gives the app enough flexibility for real-world attachments and metadata. It is a pragmatic split between structure and variation.

Schema choiceWhy it helpsTrade-off
Normalized relationshipsPreserves leases, residents, and shared responsibilityMore joins and more design discipline
JSONB for files and photosStores messy operational data without schema churnLess rigid querying for attachments
Flat tables everywhereEasy to startBreaks down once the property lifecycle gets complex

If the reconciliation engine is the brain, the schema is the skeleton. Without the right relationships, the rest of the product would collapse into a pile of status flags.

The Stack Is Conventional. The Product Thinking Is Not.

The stack is familiar: Next.js 15, Neon, Tailwind, Shadcn, Zod, and react-hook-form. Nothing here is exotic on purpose. That is the point. The repo uses standard parts to support an uncommon financial workflow, which is usually the right move for a B2B app that needs to be understandable and maintainable.

The interesting layer is not the framework choice. It is the way the stack is composed around accounting rules, reconciliation flows, and a polished operational interface.

What the AI-First Scaffolding Suggests About the Repo’s Future

The .agents directory, PRD-style scaffolding, and template-like structure suggest that the repository is meant to be legible to agents as well as humans. That is not a gimmick. It is a maintenance strategy. When a codebase can explain its own domain, future changes are less likely to drift away from the original accounting model.

That also makes the repo feel like a template for a class of products, not just one product. The code is trying to stay machine-readable in the same way the financial model stays auditable.