Finance
The finance module covers the full money cycle for a scheme — budgets, levy runs, payments and arrears, bank reconciliation, expenses, and year-end statements. It is built around two principles: money math is allocated by unit entitlement to the exact cent, and the bank ledger is immutable and import-only so the audit trail can be trusted.
The money model
Three concepts sit underneath everything else:
- Financial year — each scheme runs on financial years. Budgets, levies, expenses, and statements are all scoped to a year. A year is eventually closed, which locks it for reporting.
- Funds — money is tracked across separate funds, with the administrative fund (day-to-day running costs) and the capital works fund (long-term sinking fund) kept distinct. Balances never co-mingle: a fund balance is
opening + credits − debitsfor that fund alone. - Unit entitlement (UE) — every lot carries a unit entitlement, and levies are split in proportion to it. Before a levy run can be generated, the sum of lot entitlements must equal the scheme's recorded total; a mismatch returns HTTP 422 rather than producing a silently wrong allocation.
Access tiers
Finance uses a three-tier access model, distinct from the platform billing role. Every finance endpoint resolves the caller into one of three levels:
| Tier | Who | Can do |
|---|---|---|
| Member | Any scheme membership. | Read finance data. Owners are scoped to their own lot's levy position and payments. |
| Writer | Any committee role, or a member flagged is_financials_admin. | Operational work: draft budgets, generate a levy draft, record payments and expenses, import and reconcile bank statements. |
| Financials admin | Only members flagged is_financials_admin. | Sensitive actions: approve a budget, issue a levy run, reverse a payment, and close a financial year. Each writes an entry to the audit log. |
is_financials_admin is separate from is_billing_admin (which governs the scheme's own Stripe subscription). The founder is bootstrapped as a financials admin during onboarding; the role is granted to others by a committee member and the grant is itself audited.
Budgets
A budget is the planned income and expenditure for a financial year, broken down by fund. The lifecycle is draft → approved:
- A writer drafts and edits the budget while it is in draft.
- A financials admin approves it. Approval is the gate that lets a levy schedule be built from the budget, and it is recorded in the audit log.
Levies
A levy schedule turns an approved budget into the instalments that owners actually pay. Generating a schedule:
- Takes the budgeted amount per fund and divides it into instalments (e.g. quarterly).
- Allocates each instalment across lots by unit entitlement. The allocator sums to the exact cent — any rounding residual is assigned to the largest-entitlement lot so the parts always reconcile to the whole.
- Produces the individual levy charges — one row per lot per instalment — that drive each owner's account.
A schedule is drafted first (so it can be reviewed) and then issued. Issuing is a financials-admin action: it commits the charges, makes them payable, and refreshes each lot's financial status. Until issued, no charge affects an owner's balance.
Payments & arrears
Payments are recorded against a lot and applied to its outstanding charges. Two rules keep the ledger honest:
- Payments are reversed, never deleted. A mistaken payment is soft-voided with a reason and an audit entry, leaving the original record visible. Reversal is a financials-admin action.
- Manual money observations are payments, not bank rows. A cheque or cash receipt is entered as a levy payment; nobody hand-types a row into the bank ledger (see below).
Arrears are derived from unpaid charges. After a payment is recorded or reversed, and after a levy run is issued, each lot's financial status is recalculated — a lot in arrears is marked non-financial on its membership, which affects voting eligibility. A daily job re-checks every scheme. An explicit override is respected where set, and the recalculation never touches a separate voting suspension.
Bank reconciliation
The bank ledger is deliberately immutable and import-only. Bank transactions are created only by importing a statement (CSV or OFX) against a bank account; a database trigger blocks deletes and edits to any financial field on a bank row. The only thing that can change after import is a transaction's reconciliation status.
The flow is:
- Add a bank account for the scheme.
- Upload a statement file. A preview step shows what will be imported before it is committed.
- For each imported transaction, either match it to a payment or expense (reconcile) or ignore it. Matching is what links the real-world bank movement to the scheme's records.
Because money observations live in payments and expenses while the bank statement is the independent source of truth, reconciliation is the cross-check between the two — not a place to create new financial facts.
Expenses
Expenses record money spent by the scheme, scoped to a financial year and a fund. An expense can optionally link to an accepted quote on a matter:
- Setting
quote_idties the expense back to a TicketQuote, and through it to the matter and supplier. The matter reference (ticket_id) is derived from the quote so the two can never disagree. - This powers quoted-vs-actual variance on the matter — committees can see whether a job came in over or under its accepted quote.
- Recurring spend that isn't tied to a job (insurance, cleaning, utilities) simply leaves the quote link empty.
Year-end statement
At the close of a financial year, the scheme can produce a year-end statement — a per-fund summary of opening balances, levy income, expenses, and closing balances. It renders on a dedicated print page designed for browser print-to-PDF, suitable for distribution to owners and for audit.
API reference
All routes are under /api/schemes/{schemeId}/.... The Auth column is the minimum tier.
| Endpoint | Auth | Notes |
|---|---|---|
GET /finance/overview | Member | Financial year, fund balances, and budget summary for the landing page. |
GET /finance/register | Member | The levy register — charges and balances per lot. |
GET /finance/fund-balances | Member | Per-fund opening / credits / debits / closing. Optional ?fy=. |
GET /finance/year-end-statement | Member | Data behind the year-end statement. Optional ?fy=. |
GET /budgets · POST /budgets | Member / Writer | List or draft a budget. |
POST /budgets/{id}/approve | Admin | Approve a draft budget. Audited. |
GET /levy-schedules · POST /levy-schedules | Member / Writer | List or generate a draft levy schedule from an approved budget. |
POST /levy-schedules/{id}/issue | Admin | Issue the run — commits charges and refreshes financial status. Audited. |
GET /levy-charges · GET /arrears | Member | Individual charges; arrears summary across lots. |
GET /lots/{lotId}/levy-position | Member | A single lot's charges, payments, and balance. |
GET /levy-payments · POST /levy-payments | Member / Writer | List or record a payment. Filter by ?lot=. |
POST /levy-payments/{id}/reverse | Admin | Soft-void a payment with a reason. Audited. |
GET /expenses · POST /expenses | Member / Writer | List or create an expense. Optional quote_id link. |
GET /bank-accounts · POST /bank-accounts | Member / Writer | List or add a bank account. |
POST /bank-accounts/{id}/imports/preview | Writer | Preview a statement file before committing. |
POST /bank-accounts/{id}/imports | Writer | Import a CSV / OFX statement. |
POST /bank-transactions/{txnId}/matches | Writer | Reconcile a transaction to a payment or expense. |
POST /bank-transactions/{txnId}/ignore | Writer | Mark a transaction as ignored. |
Business rules
- Levy allocation is by unit entitlement and sums to the exact cent; the rounding residual goes to the largest-entitlement lot.
- A levy run cannot be generated unless the sum of lot unit entitlements equals the scheme total (otherwise HTTP 422).
- The administrative and capital works funds are tracked separately and never co-mingle.
- Bank transactions are import-only; a database trigger blocks deletes and edits to financial fields, allowing only the reconciliation status to change.
- Payments are reversed (soft-void + reason + audit), never hard-deleted.
- Approving a budget, issuing a levy run, reversing a payment, and closing a financial year are financials-admin-only and are written to the audit log.
- A lot's financial status is derived from arrears and drives voting eligibility; it is recalculated after payments, levy issuance, and on a daily schedule.