Block Settings — Management Fee & Apportionment
Block Settings — Management Fee & Apportionment
Every block can now be individually configured through a dedicated settings page. Settings are grouped into three areas: management fee structure, RMC/RTM company details, and service charge apportionment.
Accessing Block Settings
Open a block from the Blocks list, then click the Settings button in the block detail view. This navigates to /dashboard/blocks/{blockId}/settings.
Permission required: Reading settings is available to all authenticated users. Saving any changes requires the admin role.
Tab 1 — Management Fee
Configures how the managing agent's fee is calculated and invoiced for this block.
Fee Type
| Option | Description |
|---|---|
| Fixed Annual Fee | A fixed amount in pounds charged once per year. |
| Percentage of Service Charge Budget | A percentage of the total service charge budget, entered as a decimal (e.g. 15.00 for 15%). Stored internally as basis points. |
Management fees are always kept isolated from client and block funds in compliance with the "Management Fee Income Isolation" requirement.
Billing Schedule
- Invoice Frequency — Monthly, Quarterly, or Annual.
- Financial Year Start — The month the financial year begins. Defaults to April (standard UK financial year).
- Auto-generate invoices — When enabled, management fee invoices are automatically created on the configured schedule.
Tab 2 — RMC/RTM Details
Stores company information for blocks that have a Resident Management Company (RMC) or Right to Manage (RTM) company.
Company Details
| Field | Description |
|---|---|
| Company Name | Full legal name (e.g. Maple Court (RMC) Ltd) |
| Company Number | Companies House registration number |
Registered Address
Address line 1, address line 2, city, and postcode.
Directors
Directors can be added individually with the following fields:
| Field | Required | Description |
|---|---|---|
| Name | Yes | Full legal name |
| No | Contact email address | |
| Role | No | e.g. Director, Secretary |
Directors with an empty name are ignored on save.
Tab 3 — Apportionment
Defines how service charge costs are divided between units in the block.
Apportionment Methods
| Method | Description |
|---|---|
| Equal Split | Each unit pays an identical share. Basis points are distributed evenly, with any rounding remainder assigned to the first unit. |
| By Floor Area | Proportional to each unit's floor area. Calculated server-side from unit floor area records. |
| Custom Percentages | Manually specify each unit's share using the basis point editor. |
Basis Points
Apportionment is stored and edited in basis points, where 10,000 = 100%. The UI displays the equivalent percentage alongside each input.
- The total must equal exactly 10,000 basis points (100%) before a custom apportionment can be saved.
- A running total row shows the current sum and any shortfall or excess in real time.
- The Distribute Equally button resets all units to an equal split at any time.
Saving Apportionment Changes
Before saving, an optional reason field can be filled in. This reason is stored with the version record for audit purposes (e.g. Updated following lease extension on Flat 3).
For equal and floor area methods, the server calculates and applies the distribution. For custom, the per-unit basis point values entered in the editor are submitted directly.
Apportionment Version History
Every saved apportionment change creates an immutable versioned snapshot. Versions are numbered sequentially per block.
Each version record contains:
| Field | Description |
|---|---|
| Version number | Sequential integer per block |
| Method | The apportionment method used |
| Effective date | When the schedule takes effect |
| Reason | Optional free-text reason for the change |
| Snapshot | Full per-unit breakdown (unit number + basis points) at time of change |
The version history panel in the UI is collapsed by default. Click to expand it and view all past versions. Each version has an expandable snapshot showing every unit's percentage at that point in time.
Version records are never updated after creation. They form a permanent audit trail suitable for financial compliance review.
Data Model Reference
block_settings table
One record per block. All fields except id, orgId, blockId, and autoGenerateInvoices are nullable — settings can be partially configured.
block_settings
├── id text (PK)
├── org_id text
├── block_id text (unique)
├── management_fee_type enum: fixed_annual | percentage_of_budget
├── management_fee_amount_pence integer (pence)
├── management_fee_percentage_basis_points integer (basis points)
├── billing_schedule enum: monthly | quarterly | annual
├── auto_generate_invoices boolean (default: false)
├── financial_year_start_month integer 1–12 (default: 4)
├── rmc_company_name text
├── rmc_company_number text
├── rmc_address_line_1 text
├── rmc_address_line_2 text
├── rmc_city text
├── rmc_postcode text
├── rmc_directors text (JSON: [{name, email, role}])
├── apportionment_method enum: equal | floor_area | custom (default: custom)
├── notes text
├── created_at timestamp
└── updated_at timestamp
apportionment_versions table
Append-only. Never updated after creation.
apportionment_versions
├── id text (PK)
├── org_id text
├── block_id text
├── version integer
├── method enum: equal | floor_area | custom
├── snapshot text (JSON: [{unitId, unitNumber, basisPoints}])
├── reason text
├── created_by text
├── effective_date timestamp
└── created_at timestamp
API Reference (tRPC)
blockSettings.get
Returns the settings record for a block, or null if none exists yet.
trpc.blockSettings.get.useQuery({ blockId: string })
blockSettings.upsert
Creates or updates block settings. All fields except blockId are optional.
trpc.blockSettings.upsert.useMutation()
// Input:
{
blockId: string;
managementFeeType?: "fixed_annual" | "percentage_of_budget" | null;
managementFeeAmountPence?: number | null;
managementFeePercentageBasisPoints?: number | null;
billingSchedule?: "monthly" | "quarterly" | "annual" | null;
autoGenerateInvoices?: boolean;
financialYearStartMonth?: number;
rmcCompanyName?: string | null;
rmcCompanyNumber?: string | null;
rmcAddressLine1?: string | null;
rmcAddressLine2?: string | null;
rmcCity?: string | null;
rmcPostcode?: string | null;
rmcDirectors?: { name: string; email?: string; role?: string }[];
}
Requires admin role.
blockSettings.updateApportionment
Updates the apportionment schedule. For custom method, all unitApportionments must sum to exactly 10,000 basis points or the mutation will fail with a validation error.
trpc.blockSettings.updateApportionment.useMutation()
// Input:
{
blockId: string;
method: "equal" | "floor_area" | "custom";
unitApportionments?: { unitId: string; basisPoints: number }[]; // required when method is "custom"
reason?: string;
}
On success: creates an apportionment_versions snapshot and updates all affected unit records.
Requires admin role.
blockSettings.listApportionmentVersions
Returns all version history records for a block, ordered newest first.
trpc.blockSettings.listApportionmentVersions.useQuery({ blockId: string })
blockSettings.getCurrentApportionment
Returns current live apportionment from unit records, with a completion indicator.
trpc.blockSettings.getCurrentApportionment.useQuery({ blockId: string })
// Returns:
{
units: { id, unitNumber, leaseholderName, apportionmentBasisPoints }[];
totalBasisPoints: number;
isComplete: boolean; // true when totalBasisPoints === 10000
unitCount: number;
}