fix: cash flow forecast drops investments purchased within the charted window
The Issue 2 fix made the opening investment balance point-in-time (only CDs purchased before startYear-01-01), with a comment promising that later purchases would be re-added "when their purchase month is processed in the forecast loop" — but that loop code never existed. The loop only ever subtracted maturing CDs, never added purchased ones. Result: every CD bought during the charted window vanished from the chart. For Pine Creek (all 5 CDs purchased in 2026) the operating investment line showed $0 instead of $65,000 and reserve showed $10,000 instead of $60,032. Fix: build a purchaseIndex (mirroring maturityIndex) of investments purchased on/after startYear-01-01, keyed by purchase year-month, and credit each CD's value to the running investment balance in its purchase month — applied before the historical/forecast branch so it works for both actual and projected months. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1008,6 +1008,19 @@ export class ReportsService {
|
|||||||
WHERE is_active = true AND maturity_date IS NOT NULL AND maturity_date > CURRENT_DATE
|
WHERE is_active = true AND maturity_date IS NOT NULL AND maturity_date > CURRENT_DATE
|
||||||
`);
|
`);
|
||||||
|
|
||||||
|
// ── 5b) Get investment purchases (cash converts to an investment balance in the
|
||||||
|
// month the CD is bought). Only investments purchased on/after startYear-01-01 are
|
||||||
|
// indexed here — anything earlier is already counted in the opening investment
|
||||||
|
// balance below. Without this, point-in-time opening balances would silently drop
|
||||||
|
// every CD bought during the charted window.
|
||||||
|
const purchases = await this.tenant.query(`
|
||||||
|
SELECT fund_type, current_value, purchase_date
|
||||||
|
FROM investment_accounts
|
||||||
|
WHERE is_active = true
|
||||||
|
AND purchase_date IS NOT NULL
|
||||||
|
AND purchase_date >= $1::date
|
||||||
|
`, [`${startYear}-01-01`]);
|
||||||
|
|
||||||
// ── 6) Get capital project planned expenses ──
|
// ── 6) Get capital project planned expenses ──
|
||||||
const projectExpenses = await this.tenant.query(`
|
const projectExpenses = await this.tenant.query(`
|
||||||
SELECT estimated_cost, target_year, target_month, fund_source
|
SELECT estimated_cost, target_year, target_month, fund_source
|
||||||
@@ -1077,6 +1090,19 @@ export class ReportsService {
|
|||||||
else maturityIndex[key].reserve += maturityTotal;
|
else maturityIndex[key].reserve += maturityTotal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Index investment purchases by year-month — added to the running investment
|
||||||
|
// balance in the month the CD was bought (applies to both historical & forecast
|
||||||
|
// months, since a purchase is a real event regardless of where "now" falls).
|
||||||
|
const purchaseIndex: Record<string, { operating: number; reserve: number }> = {};
|
||||||
|
for (const inv of purchases) {
|
||||||
|
const d = new Date(inv.purchase_date);
|
||||||
|
const key = `${d.getFullYear()}-${d.getMonth() + 1}`;
|
||||||
|
if (!purchaseIndex[key]) purchaseIndex[key] = { operating: 0, reserve: 0 };
|
||||||
|
const val = parseFloat(inv.current_value) || 0;
|
||||||
|
if (inv.fund_type === 'operating') purchaseIndex[key].operating += val;
|
||||||
|
else purchaseIndex[key].reserve += val;
|
||||||
|
}
|
||||||
|
|
||||||
// Index project expenses by year-month
|
// Index project expenses by year-month
|
||||||
const projectIndex: Record<string, { operating: number; reserve: number }> = {};
|
const projectIndex: Record<string, { operating: number; reserve: number }> = {};
|
||||||
for (const p of projectExpenses) {
|
for (const p of projectExpenses) {
|
||||||
@@ -1129,6 +1155,12 @@ export class ReportsService {
|
|||||||
const isHistorical = isPastMonth && hasActuals;
|
const isHistorical = isPastMonth && hasActuals;
|
||||||
const label = `${monthLabels[month - 1]} ${year}`;
|
const label = `${monthLabels[month - 1]} ${year}`;
|
||||||
|
|
||||||
|
// Apply investment purchases for this month before branching — a CD bought
|
||||||
|
// this month raises the investment balance whether the month is actual or forecast.
|
||||||
|
const purchased = purchaseIndex[key] || { operating: 0, reserve: 0 };
|
||||||
|
runOpInv += purchased.operating;
|
||||||
|
runResInv += purchased.reserve;
|
||||||
|
|
||||||
if (isHistorical) {
|
if (isHistorical) {
|
||||||
// Use actual journal entry changes from asset accounts
|
// Use actual journal entry changes from asset accounts
|
||||||
const opChange = histIndex[`${year}-${month}-operating`] || 0;
|
const opChange = histIndex[`${year}-${month}-operating`] || 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user