Fix account balances showing $0 and dashboard KPIs on new tenants
Root cause: multiple issues with balance computation across the system. Accounts list: - findAll() was doing SELECT * FROM accounts, returning the stale denormalized `balance` column (always 0). Now computes balances from journal entries using proper double-entry logic (debit-credit for assets/expenses, credit-debit for liabilities/equity/income). Dashboard KPIs: - Total Cash filtered by name LIKE '%Cash%' which missed accounts not named "Cash". Now queries ALL asset accounts regardless of name. - Reserve Fund queried the legacy reserve_components.current_fund_balance column. Now computes from journal entries on reserve asset accounts. Opening balance journal entries: - On blank tenants, equity offset accounts (3000/3100) don't exist, so the balancing journal entry line was silently skipped, leaving entries unbalanced. Now auto-creates Operating Fund Balance (3000) and Reserve Fund Balance (3100) equity accounts when needed. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -394,28 +394,44 @@ export class ReportsService {
|
||||
}
|
||||
|
||||
async getDashboardKPIs() {
|
||||
// Total cash (all asset accounts with 'Cash' in name)
|
||||
// Total cash: ALL asset accounts (not just those named "Cash")
|
||||
// Uses proper double-entry balance: debit - credit for assets
|
||||
const cash = await this.tenant.query(`
|
||||
SELECT COALESCE(SUM(sub.balance), 0) as total FROM (
|
||||
SELECT COALESCE(SUM(jel.debit), 0) - COALESCE(SUM(jel.credit), 0) as balance
|
||||
FROM accounts a
|
||||
LEFT JOIN journal_entry_lines jel ON jel.account_id = a.id
|
||||
LEFT JOIN journal_entries je ON je.id = jel.journal_entry_id AND je.is_posted = true AND je.is_void = false
|
||||
WHERE a.account_type = 'asset' AND a.name LIKE '%Cash%' AND a.is_active = true
|
||||
WHERE a.account_type = 'asset' AND a.is_active = true
|
||||
GROUP BY a.id
|
||||
) sub
|
||||
`);
|
||||
const totalCash = parseFloat(cash[0]?.total || '0');
|
||||
|
||||
// Receivables
|
||||
// Receivables: sum of unpaid invoices
|
||||
const ar = await this.tenant.query(`
|
||||
SELECT COALESCE(SUM(amount - amount_paid), 0) as total
|
||||
FROM invoices WHERE status NOT IN ('paid', 'void', 'written_off')
|
||||
`);
|
||||
|
||||
// Reserve fund balance
|
||||
// Reserve fund balance: computed from journal entries on reserve fund_type accounts
|
||||
// credit - debit for equity/liability/income accounts (reserve equity + reserve income - reserve expenses)
|
||||
const reserves = await this.tenant.query(`
|
||||
SELECT COALESCE(SUM(current_fund_balance), 0) as total FROM reserve_components
|
||||
SELECT COALESCE(SUM(sub.balance), 0) as total FROM (
|
||||
SELECT
|
||||
CASE
|
||||
WHEN a.account_type IN ('asset')
|
||||
THEN COALESCE(SUM(jel.debit), 0) - COALESCE(SUM(jel.credit), 0)
|
||||
WHEN a.account_type IN ('expense')
|
||||
THEN -(COALESCE(SUM(jel.debit), 0) - COALESCE(SUM(jel.credit), 0))
|
||||
ELSE COALESCE(SUM(jel.credit), 0) - COALESCE(SUM(jel.debit), 0)
|
||||
END as balance
|
||||
FROM accounts a
|
||||
LEFT JOIN journal_entry_lines jel ON jel.account_id = a.id
|
||||
LEFT JOIN journal_entries je ON je.id = jel.journal_entry_id AND je.is_posted = true AND je.is_void = false
|
||||
WHERE a.fund_type = 'reserve' AND a.account_type IN ('asset') AND a.is_active = true
|
||||
GROUP BY a.id, a.account_type
|
||||
) sub
|
||||
`);
|
||||
|
||||
// Delinquent count (overdue invoices)
|
||||
@@ -434,7 +450,7 @@ export class ReportsService {
|
||||
return {
|
||||
total_cash: totalCash.toFixed(2),
|
||||
total_receivables: ar[0]?.total || '0.00',
|
||||
reserve_fund_balance: reserves[0]?.total || '0.00',
|
||||
reserve_fund_balance: parseFloat(reserves[0]?.total || '0').toFixed(2),
|
||||
delinquent_units: parseInt(delinquent[0]?.count || '0'),
|
||||
recent_transactions: recentTx,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user