Back to BlogIndustry Guides

Service-Charge Budgeting and Reconciliation for Blocks

Rupak Amin

Founder & Lead Engineer, RAITHub

9 min read

RAITHub ships and tests production software. See QA as a Service or talk to us.

Service charges go wrong in the apportionment and the year-end reconciliation, not the invoice. Each unit owes a fixed share of each cost head, demands are raised against a budget, and actual spend must reconcile back to a balancing charge or credit per unit. Store apportionment as data, keep a budget-to-actual ledger per cost head, and reconcile so the sum of every unit's share equals the actual spend exactly.

If you would rather have it built and tested for you, see how RAITHub would build this below.

This is about the block's shared costs, not an owner's own money. Per-owner income and expense statements are a separate report; here the job is budgeting a building's communal spend and settling it fairly across the units. Collecting what is then owed, including arrears, is covered in a rent arrears and collections system.

Why is service-charge accounting hard to get right?

Because one cost does not split evenly, and the year-end has to balance to the cent. Cleaning might split by floor area, lift maintenance only among upper floors, and buildings insurance equally, so each cost head has its own apportionment. Demands go out against a budget set before the year, then actual spend comes in over the year, and at year-end every unit's demanded share must be reconciled against its share of what was actually spent, producing a balancing charge or credit. If the shares do not sum back to the actual total, someone is over- or under-charged, and in many places that is a legal problem as well as an accounting one.

StageWhat goes wrongThe correct rule
ApportionmentOne split applied to every cost headA schedule per cost head; a unit's share can differ by head
DemandsCharged on actual spend, which is not known yetDemanded against the budget, in instalments, during the year
ActualsLogged against the building, not the headEvery invoice coded to a cost head, so budget-to-actual is visible
ReconciliationRounding leaves the shares not summing to actualAllocate to integer minor units; the last unit absorbs the rounding remainder

How do you model apportionment?

As a schedule: for each cost head, each unit holds a share, and the shares for a head sum to the whole (often expressed as a fraction or a number of parts). Store the shares as data so a cost head's split is explicit and auditable, not hidden in a formula.

CREATE TABLE cost_heads (
  id    bigserial PRIMARY KEY,
  name  text NOT NULL        -- cleaning | lift | insurance | grounds | reserve
);

CREATE TABLE apportionments (
  cost_head_id bigint NOT NULL REFERENCES cost_heads(id),
  unit_id      bigint NOT NULL REFERENCES units(id),
  parts        integer NOT NULL CHECK (parts >= 0),   -- this unit's share of this head
  PRIMARY KEY (cost_head_id, unit_id)
);

-- A unit's fraction of a head = its parts / the sum of parts for that head.
-- Allocate amounts in integer minor units so shares sum back to the total exactly.

Keeping shares as integer parts (not floating fractions) lets you allocate money exactly: compute each unit's minor-unit share, then give the rounding remainder to one nominated unit so the parts always sum to the whole. This is the same integer-money discipline used in any ledger, covered in double-entry ledger database design.

How do demands and the budget work?

Demands are raised against the year's budget, not against actual spend, because the actual is not known until the costs come in. Set a budget per cost head for the year, apportion it to units by the schedule, and demand it in instalments (quarterly, say). Each demand is a charge in the unit's ledger, exactly as rent arrears are tracked, so what is owed, paid and outstanding is always the sum of the rows.

-- A unit's demanded service charge for a head = budget * (its parts / total parts).
-- Allocate in minor units; the nominated unit takes the remainder so the total is exact.
SELECT
  a.unit_id,
  (b.budget_minor * a.parts) / sum(a.parts) OVER (PARTITION BY a.cost_head_id) AS demand_minor
FROM apportionments a
JOIN budgets b ON b.cost_head_id = a.cost_head_id AND b.year = $1;

How does year-end reconciliation work?

At year-end, total the actual spend per cost head, apportion the actual the same way the budget was apportioned, and compare each unit's share of actual against what it was demanded. The difference is a balancing charge (actual exceeded budget) or a credit (it came in under). The invariant that must hold: the sum of every unit's share of a head equals that head's actual spend, to the cent. If it does not, the allocation lost or invented money in rounding, which is the bug to catch in tests. Keep a reserve fund as its own head, so money set aside for future major works is tracked separately from the year's running costs.

How do you test service-charge reconciliation?

The invariant is "shares sum to the total," so assert it directly, especially where rounding bites.

import { describe, it, expect } from 'vitest'
import { apportion, reconcile } from './service-charge'

describe('service-charge apportionment', () => {
  it('shares sum exactly to the total, with rounding absorbed', () => {
    // 100.00 split three ways cannot divide evenly in minor units.
    const shares = apportion({ totalMinor: 10000, parts: { A: 1, B: 1, C: 1 } })
    expect(shares.A + shares.B + shares.C).toBe(10000)   // no cent lost or invented
  })

  it('produces a balancing charge when actual exceeds budget', () => {
    const r = reconcile({
      demandedMinor: { A: 5000, B: 5000 },
      actualMinor: 12000,
      parts: { A: 1, B: 1 },
    })
    expect(r.A).toBe(1000)   // owes 10.00 more
    expect(r.B).toBe(1000)
  })
})

Add cases with different apportionment per head, a unit with a zero share of a head (ground floor, no lift), and a credit when actual comes in under budget.

Buy, build or hire?

OptionChoose this whenThe catch
Block-management or property accounting softwareYour apportionment and reporting fit the tool, in your marketPer-head apportionment, reserve funds and local statutory formats follow the tool's model
A spreadsheet per blockOne or two small blocks and a diligent accountantRounding and per-head splits are error-prone; reconciliation is manual and hard to audit
Custom buildMany blocks, varied apportionment schedules, or integration with how you collect and reportYou own the apportionment, the ledger and the tests that keep shares exact

How long does it take to build yourself, and what is the risk?

For an experienced backend developer adding apportionment, a budget-to-actual ledger and reconciliation to an existing property app, our estimate is 2 to 4 weeks for a first version, or 4 to 6 weeks with reserve funds, statements and reporting. The main risk is twofold: rounding that makes shares not sum to the total, and statutory requirements (how service charges must be demanded, accounted for and reported) that vary by jurisdiction. The rounding is a test; the statutory part is legal, so confirm the rules for your properties with your adviser. This is general information.

Why RAITHub for this

  • Ledgers that balance. Sundor Skin runs tier pricing and credit as append-only entries with a hash-chained audit log across 146 PostgreSQL tables and 530+ tests. See the Sundor Skin case study.
  • Property software in production. RAITHub built PropDesk, property-management SaaS with four roles and five daily automation jobs, with 1,024 tests.
  • Exact money. Integer minor units and tested allocation are standard, so apportioned shares sum back to the total to the cent.

When you don't need us

  • Block-management software already fits. If its apportionment and statutory reports match your market, use it.
  • You manage one small block. A careful accountant and a spreadsheet may be enough.
  • You only need the model. The apportionment and reconciliation functions above are a fair start for your own developer.

How RAITHub would build this

  • Apportionment schedules: a share per unit per cost head, stored as integer parts and auditable.
  • Budget and demands: a per-head annual budget, apportioned and demanded in instalments to each unit's ledger.
  • Reconciliation: actual spend coded to heads, apportioned the same way, settled as a balancing charge or credit per unit, with a tracked reserve fund.
  • Tests: shares-sum-to-total and reconciliation suites gated in CI.

Timeline: adding this to an existing app is typically 2 to 4 weeks; a full build with statements and reserves fits the 4 to 6 week fixed-scope range. See SaaS development and the real-estate industry page. A related build is a rent arrears and collections system.

You receive: the apportionment, budget and reconciliation code, tests gated in CI, handover docs, and full IP in your name under NDA.

Next step: book the free 15-minute technical audit with a sample apportionment schedule, and we will follow up with a written fixed quote.

Frequently asked questions

Why is service-charge accounting harder than it looks?

Because different costs split differently (lift by upper floors, insurance equally, cleaning by area), demands go out against a budget set before the year, and at year-end each unit's share of the actual spend must reconcile to a balancing charge or credit that sums exactly back to what was spent.

How should apportionment be modelled?

As a schedule per cost head: each unit holds a share (as integer parts), and the parts for a head sum to the whole. Storing shares as data, not a formula, makes each head's split explicit and auditable, and lets you allocate money to the cent.

How do you stop rounding errors in apportioned shares?

Allocate in integer minor units and give the rounding remainder to one nominated unit, so the shares always sum back to the total exactly. A test splits an amount that cannot divide evenly and asserts the shares add up with no cent lost or invented.

What is year-end service-charge reconciliation?

Totalling actual spend per cost head, apportioning it the same way the budget was, and comparing each unit's share of actual against what it was demanded. The difference is a balancing charge if actual exceeded budget, or a credit if it came in under.

How is a reserve fund handled?

As its own cost head, so money set aside for future major works is budgeted, demanded and tracked separately from the year's running costs, and never silently mixed into the operating reconciliation.

Does this handle the legal rules for service charges?

It enforces the apportionment and reporting you configure, but how service charges must be demanded, accounted for and reported varies by jurisdiction and is a legal matter. This is general information; confirm the statutory rules for your properties with your adviser before relying on it.

service chargebudgetingreconciliationproperty managementapportionmentproptech

Ready to discuss your project?

Book a free 15-minute technical audit with our engineering team.