diff --git a/backend/src/modules/journal-entries/journal-entries.service.ts b/backend/src/modules/journal-entries/journal-entries.service.ts index 95098a3..51bad9e 100644 --- a/backend/src/modules/journal-entries/journal-entries.service.ts +++ b/backend/src/modules/journal-entries/journal-entries.service.ts @@ -162,26 +162,18 @@ export class JournalEntriesService { if (!je.is_posted) throw new BadRequestException('Cannot void an unposted entry'); if (je.is_void) throw new BadRequestException('Already voided'); - // Reverse account balances - for (const line of je.lines) { - const debit = parseFloat(line.debit) || 0; - const credit = parseFloat(line.credit) || 0; - const reverseAmount = credit - debit; - - await this.tenant.query( - `UPDATE accounts SET balance = balance + $1, updated_at = NOW() WHERE id = $2`, - [reverseAmount, line.account_id], - ); - } - await this.tenant.query( `UPDATE journal_entries SET is_void = true, voided_by = $1, voided_at = NOW(), void_reason = $2 WHERE id = $3`, [userId, reason, id], ); - // Create reversing entry + // Create a reversing entry dated to the original entry's date so historical + // period balances stay accurate. The post() call handles accounts.balance updates — + // we do NOT manually reverse balances here to avoid double-counting. const reverseDto: CreateJournalEntryDto = { - entryDate: new Date().toISOString().split('T')[0], + entryDate: je.entry_date instanceof Date + ? je.entry_date.toISOString().split('T')[0] + : String(je.entry_date).split('T')[0], description: `VOID: ${je.description}`, referenceNumber: `VOID-${je.reference_number || je.id.slice(0, 8)}`, entryType: 'adjustment', diff --git a/backend/src/modules/monthly-actuals/monthly-actuals.service.ts b/backend/src/modules/monthly-actuals/monthly-actuals.service.ts index 00baeb9..1892178 100644 --- a/backend/src/modules/monthly-actuals/monthly-actuals.service.ts +++ b/backend/src/modules/monthly-actuals/monthly-actuals.service.ts @@ -38,6 +38,7 @@ export class MonthlyActualsService { 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 + AND je.entry_type = 'monthly_actual' AND EXTRACT(YEAR FROM je.entry_date) = $1 AND EXTRACT(MONTH FROM je.entry_date) = $2 WHERE a.is_active = true diff --git a/backend/src/modules/reports/reports.service.ts b/backend/src/modules/reports/reports.service.ts index a1e9554..cc9a393 100644 --- a/backend/src/modules/reports/reports.service.ts +++ b/backend/src/modules/reports/reports.service.ts @@ -1089,9 +1089,25 @@ export class ReportsService { else projectIndex[key].reserve += cost; } - // Investment opening balances at start of period (approximate: use current values) - let runOpInv = opInv; - let runResInv = resInv; + // Investment balances at the start of the period — computed from the investment_accounts + // table as of startYear-01-01. We use current_value for all active investments that + // existed before startYear (purchase_date < startYear-01-01). Investments purchased + // after that date will be added when their purchase month is processed in the forecast loop. + const openingInvOp = await this.tenant.query(` + SELECT COALESCE(SUM(current_value), 0) as total + FROM investment_accounts + WHERE fund_type = 'operating' AND is_active = true + AND (purchase_date IS NULL OR purchase_date < $1::date) + `, [`${startYear}-01-01`]); + const openingInvRes = await this.tenant.query(` + SELECT COALESCE(SUM(current_value), 0) as total + FROM investment_accounts + WHERE fund_type = 'reserve' AND is_active = true + AND (purchase_date IS NULL OR purchase_date < $1::date) + `, [`${startYear}-01-01`]); + + let runOpInv = parseFloat(openingInvOp[0]?.total || '0'); + let runResInv = parseFloat(openingInvRes[0]?.total || '0'); // Determine which months have actual journal entries // A month is "actual" only if it's not in the future AND has real journal entry data