What it is
BuildingSetup is a multi-tenant building management platform. A super admin creates companies; each company is a tenant with its own PostgreSQL database and its own subdomain. A company manages one or more buildings and everything inside them — units, residents, owners, tenancies, billing, documents, complaints, staff, visitors, maintenance and inventory.
It deliberately mirrors the tenancy, provisioning and deployment patterns of our payroll product rather than inventing new ones, on the basis that a second system built the same way is far cheaper to operate than two systems built differently.
The two money flows that must never touch
There are two completely separate billing systems in this product, and the most important architectural rule is that they are never conflated.
Platform billing is the company paying us, priced per building by unit count, and it lives in the central database. Operational billing is residents paying their management company for their units, and it lives inside that company's own tenant database.
They look superficially similar — invoices, payments, ledgers — which is exactly why they are dangerous. Sharing code or tables between them would eventually let a resident's payment land against a platform invoice, or a pricing change to our own rate card touch a resident's bill. Keeping them in different databases makes that structurally impossible rather than merely discouraged.
Rules the schema enforces
A handful of conventions apply everywhere, and each one exists because the alternative fails quietly:
- Money is stored as an integer in minor units alongside an ISO-4217 currency code — never a float, and never with the currency assumed. Floating-point money produces rounding differences that only appear once totals get large enough to matter
- History, audit, ledger and event tables are append-only. A historical row is never updated or deleted, so a ledger can always be replayed to explain how a balance was reached
- Anything whose value changes over time — pricing, ownership, tax slabs, salary — uses an effective-dated pattern with a start date, a nullable end date, and a partial unique index enforcing exactly one current row. The current value is derived, not overwritten, so last year's invoice can still be explained by last year's rate
- Rates, unit types, charge composition and currency live in the database rather than in code, so a pricing change is configuration rather than a deployment
Why the effective-dated pattern keeps reappearing
That third rule is worth dwelling on, because it is the same pattern our own client portal uses for employee records, and it solves the same problem in both places.
The naive approach stores a current value and overwrites it on change. It is simpler, and it destroys your ability to answer the most common question anyone asks about a bill: why was I charged that? Once the rate has been overwritten, the historical invoice and the current rate table disagree and nothing can reconcile them.
Storing each value with the period it applied to means the current state is a query rather than a column, and every past document remains explicable by the data that produced it.
This is an OffSetup product, so there is no client to anonymise and the figures above are drawn from the project's own engineering documentation.