This property management software case study covers monthly charges, bank-statement matching and the review behind each receipt.
The workspace starts from a lease and its monthly charge. It imports bank credits, proposes which charges they cover, and records the decision a reviewer makes on each one. Property, unit and tenant records give that ledger its context.
This is an internal property management tool. It records receipts, but it does not collect money or run a public rental marketplace.
The challenge
A transfer can match the rent to the cent without naming the tenant. A reference might point at a different month. A second statement download might even repeat a credit that was imported already. On its own, the amount cannot confirm a payment.
What we built
Staff import CSV or CAMT.053 XML statements and review the credit lines against open charges. Each suggestion shows the evidence behind it. That evidence can be a known bank account or a confirmed payer name. It can also be a tenant name or a unit reference.
Automatic posting follows a narrow rule. The payer must have been confirmed before, the amount must equal the outstanding charge, and the match must be unambiguous. Everything else stays a review decision.
Confirming a match records the payment, updates the charge and remembers the payer for later imports. Allocation then starts from the oldest open charge. A reference that names a month overrides that, and any excess simply stays unapplied rather than moving onto another obligation.
Preventing the same work from being applied twice
A database uniqueness rule blocks duplicate monthly charges for one lease and one period. Each statement line also gets a fingerprint. That fingerprint comes from the bank reference or other identifying fields. Reimports are then skipped, even when two imports race to insert the very same line.
Confirmation claims a transaction only while it still awaits review. That stops two reviewers from acting on the same transfer at once. The payment writes that follow use compensation and cleanup if a later step fails, so they are not one atomic database transaction.
Keeping the operating limits clear
Matching and reminder rules live in shared TypeScript functions, kept apart from database access. API inputs are validated before anything is processed. Each installation uses one database and imports its bank data from files.
An authentication layer has to stand in front of any real tenant record and any real bank information.
Project success
The ledger keeps both the evidence and the decision behind an applied credit. Uncertain payments and unapplied amounts stay visible, next to the open and partially paid charges and the paid and overdue ones. Before preparing a reminder, it checks for payments still awaiting review. A likely receipt is therefore in view before the tenant is contacted. The reminder itself stays a draft for a person to send from their own email account.
Technology stack
Backend
TypeScript, Hono, Cloudflare Workers
Database
Cloudflare D1, SQLite
Frontend
React, Vite, Tailwind CSS, shadcn/ui
Input validation
Zod
Statement formats
CSV, CAMT.053 XML
Other engagements
Recognize the pattern?
If one of these looks like your situation, the discovery call is where we find out how close.