Fix reserve fund balance, dynamic project funding, year-end report, and unit form

- Dashboard reserve fund KPI now uses reserve equity accounts (fund balance
  position) instead of asset accounts, correctly showing the total reserve
  fund balance regardless of how users categorize their reserve accounts
- Projects findAll() and findForPlanning() dynamically compute funded_percentage
  and current_fund_balance from reserve equity account balances via CTE,
  distributing the total reserve balance proportionally across projects
- Year-end summary reserve status now queries unified projects table instead
  of deprecated reserve_components table
- Remove standalone Monthly Assessment field from Units form — assessment
  amount is now inherited from the selected assessment group

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-20 08:22:31 -05:00
parent 739ccaeed4
commit 112578672e
3 changed files with 112 additions and 41 deletions

View File

@@ -1,7 +1,7 @@
import { useState } from 'react';
import {
Title, Table, Group, Button, Stack, TextInput, Modal,
NumberInput, Select, Badge, ActionIcon, Text, Loader, Center, Tooltip, Alert,
Select, Badge, ActionIcon, Text, Loader, Center, Tooltip, Alert,
} from '@mantine/core';
import { useForm } from '@mantine/form';
import { useDisclosure } from '@mantine/hooks';
@@ -56,7 +56,7 @@ export function UnitsPage() {
const form = useForm({
initialValues: {
unit_number: '', address_line1: '', city: '', state: '', zip_code: '',
owner_name: '', owner_email: '', owner_phone: '', monthly_assessment: 0,
owner_name: '', owner_email: '', owner_phone: '',
assessment_group_id: '' as string | null,
},
validate: {
@@ -96,7 +96,7 @@ export function UnitsPage() {
form.setValues({
unit_number: u.unit_number, address_line1: u.address_line1 || '',
city: '', state: '', zip_code: '', owner_name: u.owner_name || '',
owner_email: u.owner_email || '', owner_phone: '', monthly_assessment: parseFloat(u.monthly_assessment || '0'),
owner_email: u.owner_email || '', owner_phone: '',
assessment_group_id: u.assessment_group_id || '',
});
open();
@@ -108,21 +108,10 @@ export function UnitsPage() {
// Pre-populate with default group
if (defaultGroup) {
form.setFieldValue('assessment_group_id', defaultGroup.id);
form.setFieldValue('monthly_assessment', parseFloat(defaultGroup.regular_assessment || '0'));
}
open();
};
const handleGroupChange = (groupId: string | null) => {
form.setFieldValue('assessment_group_id', groupId);
if (groupId) {
const group = assessmentGroups.find(g => g.id === groupId);
if (group) {
form.setFieldValue('monthly_assessment', parseFloat(group.regular_assessment || '0'));
}
}
};
const filtered = units.filter((u) =>
!search || u.unit_number.toLowerCase().includes(search.toLowerCase()) ||
(u.owner_name || '').toLowerCase().includes(search.toLowerCase())
@@ -213,17 +202,16 @@ export function UnitsPage() {
<TextInput label="Owner Phone" {...form.getInputProps('owner_phone')} />
<Select
label="Assessment Group"
description="Required — all units must belong to an assessment group"
description="Required — the unit's monthly assessment is inherited from the selected group"
required
data={assessmentGroups.map(g => ({
value: g.id,
label: `${g.name}${g.is_default ? ' (Default)' : ''}$${parseFloat(g.regular_assessment || '0').toFixed(2)}/${g.frequency || 'mo'}`,
}))}
value={form.values.assessment_group_id}
onChange={handleGroupChange}
onChange={(v) => form.setFieldValue('assessment_group_id', v)}
error={form.errors.assessment_group_id}
/>
<NumberInput label="Monthly Assessment" prefix="$" decimalScale={2} min={0} {...form.getInputProps('monthly_assessment')} />
<Button type="submit" loading={saveMutation.isPending}>{editing ? 'Update' : 'Create'}</Button>
</Stack>
</form>