feat: dashboard quick stats enhancements and monthly actuals read/edit mode
Dashboard Quick Stats: - Create Capital Projects section with "Planned Capital Spend 2026" - Fix Interest Earned YTD to pull from actual journal entries on interest income accounts instead of unrealized investment gains - Add Interest Earned YoY showing projected current year vs last year actuals with percentage change badge Monthly Actuals: - Default to read-only view when actuals are already reconciled - Show "Edit Actuals" button instead of "Save Actuals" for reconciled months - Add confirmation modal warning that editing will void existing journal entry before allowing edits - New months without actuals open directly in edit mode Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -716,14 +716,38 @@ export class ReportsService {
|
||||
`);
|
||||
const estMonthlyInterest = acctInterestTotal + parseFloat(invInterest[0]?.total || '0');
|
||||
|
||||
// Interest earned YTD: approximate from current_value - principal (unrealized gains)
|
||||
// Interest earned YTD: actual interest income from journal entries for current year
|
||||
const currentYear = new Date().getFullYear();
|
||||
const interestEarned = await this.tenant.query(`
|
||||
SELECT COALESCE(SUM(current_value - principal), 0) as total
|
||||
FROM investment_accounts WHERE is_active = true AND current_value > principal
|
||||
`);
|
||||
SELECT COALESCE(SUM(jel.credit - jel.debit), 0) as total
|
||||
FROM accounts a
|
||||
JOIN journal_entry_lines jel ON jel.account_id = a.id
|
||||
JOIN journal_entries je ON je.id = jel.journal_entry_id
|
||||
AND je.is_posted = true AND je.is_void = false
|
||||
AND EXTRACT(YEAR FROM je.entry_date) = $1
|
||||
WHERE a.account_type = 'income' AND a.is_active = true
|
||||
AND LOWER(a.name) LIKE '%interest%'
|
||||
`, [currentYear]);
|
||||
|
||||
// Interest earned last year (for YoY comparison)
|
||||
const interestLastYear = await this.tenant.query(`
|
||||
SELECT COALESCE(SUM(jel.credit - jel.debit), 0) as total
|
||||
FROM accounts a
|
||||
JOIN journal_entry_lines jel ON jel.account_id = a.id
|
||||
JOIN journal_entries je ON je.id = jel.journal_entry_id
|
||||
AND je.is_posted = true AND je.is_void = false
|
||||
AND EXTRACT(YEAR FROM je.entry_date) = $1
|
||||
WHERE a.account_type = 'income' AND a.is_active = true
|
||||
AND LOWER(a.name) LIKE '%interest%'
|
||||
`, [currentYear - 1]);
|
||||
|
||||
// Projected interest for current year (YTD actual + remaining months estimated)
|
||||
const currentMonth = new Date().getMonth() + 1;
|
||||
const ytdInterest = parseFloat(interestEarned[0]?.total || '0');
|
||||
const monthlyAvg = currentMonth > 0 ? ytdInterest / currentMonth : 0;
|
||||
const projectedInterest = ytdInterest + (monthlyAvg * (12 - currentMonth));
|
||||
|
||||
// Planned capital spend for current year
|
||||
const currentYear = new Date().getFullYear();
|
||||
const capitalSpend = await this.tenant.query(`
|
||||
SELECT COALESCE(SUM(estimated_cost), 0) as total
|
||||
FROM projects WHERE target_year = $1 AND status IN ('planned', 'in_progress') AND is_active = true
|
||||
@@ -749,7 +773,9 @@ export class ReportsService {
|
||||
operating_investments: operatingInvestments.toFixed(2),
|
||||
reserve_investments: reserveInvestments.toFixed(2),
|
||||
est_monthly_interest: estMonthlyInterest.toFixed(2),
|
||||
interest_earned_ytd: interestEarned[0]?.total || '0.00',
|
||||
interest_earned_ytd: ytdInterest.toFixed(2),
|
||||
interest_last_year: parseFloat(interestLastYear[0]?.total || '0').toFixed(2),
|
||||
interest_projected: projectedInterest.toFixed(2),
|
||||
planned_capital_spend: capitalSpend[0]?.total || '0.00',
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user