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:
@@ -8,22 +8,34 @@ export class AccountsService {
|
||||
constructor(private tenant: TenantService) {}
|
||||
|
||||
async findAll(fundType?: string, includeArchived?: boolean) {
|
||||
let sql = 'SELECT * FROM accounts';
|
||||
const params: any[] = [];
|
||||
const conditions: string[] = [];
|
||||
const params: any[] = [];
|
||||
|
||||
if (!includeArchived) {
|
||||
conditions.push('is_active = true');
|
||||
conditions.push('a.is_active = true');
|
||||
}
|
||||
if (fundType) {
|
||||
params.push(fundType);
|
||||
conditions.push(`fund_type = $${params.length}`);
|
||||
conditions.push(`a.fund_type = $${params.length}`);
|
||||
}
|
||||
|
||||
if (conditions.length) {
|
||||
sql += ' WHERE ' + conditions.join(' AND ');
|
||||
}
|
||||
sql += ' ORDER BY account_number';
|
||||
const whereClause = conditions.length ? 'WHERE ' + conditions.join(' AND ') : '';
|
||||
|
||||
const sql = `
|
||||
SELECT a.*,
|
||||
CASE
|
||||
WHEN a.account_type IN ('asset', '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
|
||||
${whereClause}
|
||||
GROUP BY a.id
|
||||
ORDER BY a.account_number
|
||||
`;
|
||||
return this.tenant.query(sql, params);
|
||||
}
|
||||
|
||||
@@ -79,12 +91,24 @@ export class AccountsService {
|
||||
const acctDebit = isDebitNormal ? absAmount : 0;
|
||||
const acctCredit = isDebitNormal ? 0 : absAmount;
|
||||
|
||||
// Determine equity offset account based on fund type
|
||||
// Determine equity offset account based on fund type (auto-create if missing)
|
||||
const equityAccountNumber = dto.fundType === 'reserve' ? 3100 : 3000;
|
||||
const equityRows = await this.tenant.query(
|
||||
const equityName = dto.fundType === 'reserve' ? 'Reserve Fund Balance' : 'Operating Fund Balance';
|
||||
let equityRows = await this.tenant.query(
|
||||
'SELECT id FROM accounts WHERE account_number = $1',
|
||||
[equityAccountNumber],
|
||||
);
|
||||
if (!equityRows.length) {
|
||||
await this.tenant.query(
|
||||
`INSERT INTO accounts (account_number, name, account_type, fund_type, is_system)
|
||||
VALUES ($1, $2, 'equity', $3, true)`,
|
||||
[equityAccountNumber, equityName, dto.fundType],
|
||||
);
|
||||
equityRows = await this.tenant.query(
|
||||
'SELECT id FROM accounts WHERE account_number = $1',
|
||||
[equityAccountNumber],
|
||||
);
|
||||
}
|
||||
|
||||
// Create the journal entry
|
||||
const jeInsert = await this.tenant.query(
|
||||
|
||||
Reference in New Issue
Block a user