diff --git a/CHANGELOG.md b/CHANGELOG.md index c364550..590862e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,16 @@ O formato segue [Keep a Changelog](https://keepachangelog.com/pt-BR/). Este projeto adota [Versionamento Semântico](https://semver.org/pt-BR/). +## [1.43.24] - 2025-12-16 + +### Fixed +- 🐛 **Projeção de Saldo - Instâncias Recorrentes Vencidas** + - FIX: Widget de projeção agora inclui `recurring_instances` com status pendente e vencidas + - Corrigida query para usar `planned_date` ao invés de `effective_date` nas transações pendentes + - Adicionada query separada para `recurring_instances` vencidas e futuras + - Impacto: Projeção agora reflete corretamente valores como Aluguel (1003.59€) e Bizum Asenergy (94.22€) vencidos + - Cálculo: Saldo Atual - Transações Vencidas - Instâncias Recorrentes Vencidas = Projeção Correta + ## [1.43.23] - 2025-12-16 ### Fixed diff --git a/VERSION b/VERSION index 56c60d6..0932d7d 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.43.23 +1.43.24 diff --git a/backend/app/Http/Controllers/Api/ReportController.php b/backend/app/Http/Controllers/Api/ReportController.php index 41bedc1..47445b0 100644 --- a/backend/app/Http/Controllers/Api/ReportController.php +++ b/backend/app/Http/Controllers/Api/ReportController.php @@ -1153,24 +1153,47 @@ public function projectionChart(Request $request) // Buscar transações agendadas/pendentes (incluindo atrasadas) $scheduledTransactions = DB::select(" SELECT - t.effective_date as date, + COALESCE(t.planned_date, t.effective_date) as date, t.amount, t.type, COALESCE(a.currency, 'EUR') as currency, - CASE WHEN t.effective_date < ? THEN 1 ELSE 0 END as is_overdue + CASE WHEN COALESCE(t.planned_date, t.effective_date) < ? THEN 1 ELSE 0 END as is_overdue FROM transactions t LEFT JOIN accounts a ON t.account_id = a.id WHERE t.user_id = ? AND t.status IN ('pending', 'scheduled') - AND t.effective_date <= ? + AND COALESCE(t.planned_date, t.effective_date) <= ? AND t.deleted_at IS NULL - ORDER BY t.effective_date + AND {$this->excludeTransfers()} + ORDER BY COALESCE(t.planned_date, t.effective_date) + ", [$today->toDateString(), $this->userId, $endDate->toDateString()]); + + // Buscar instâncias de recorrências pendentes (incluindo atrasadas) + $recurringInstances = DB::select(" + SELECT + ri.due_date as date, + ri.planned_amount as amount, + rt.type, + COALESCE(a.currency, 'EUR') as currency, + CASE WHEN ri.due_date < ? THEN 1 ELSE 0 END as is_overdue + FROM recurring_instances ri + JOIN recurring_templates rt ON ri.recurring_template_id = rt.id + LEFT JOIN accounts a ON rt.account_id = a.id + WHERE ri.user_id = ? + AND ri.status = 'pending' + AND ri.due_date <= ? + AND ri.deleted_at IS NULL + ORDER BY ri.due_date ", [$today->toDateString(), $this->userId, $endDate->toDateString()]); // Separar transações atrasadas para processar primeiro $overdueTransactions = array_filter($scheduledTransactions, fn($tx) => $tx->is_overdue); $futureTransactions = array_filter($scheduledTransactions, fn($tx) => !$tx->is_overdue); + // Separar instâncias recorrentes atrasadas + $overdueRecurringInstances = array_filter($recurringInstances, fn($ri) => $ri->is_overdue); + $futureRecurringInstances = array_filter($recurringInstances, fn($ri) => !$ri->is_overdue); + // Processar transações atrasadas ANTES do ponto inicial foreach ($overdueTransactions as $tx) { $amount = $this->convertToPrimaryCurrency(abs($tx->amount), $tx->currency); @@ -1182,13 +1205,24 @@ public function projectionChart(Request $request) $runningBalance = $currentBalance; } + // Processar instâncias recorrentes atrasadas ANTES do ponto inicial + foreach ($overdueRecurringInstances as $ri) { + $amount = $this->convertToPrimaryCurrency(abs($ri->amount), $ri->currency); + if ($ri->type === 'credit') { + $currentBalance += $amount; + } else { + $currentBalance -= $amount; + } + $runningBalance = $currentBalance; + } + // Ponto inicial (já inclui o impacto das transações atrasadas) $dataPoints[] = [ 'date' => $today->toDateString(), 'balance' => round($runningBalance, 2), 'label' => $today->format('d/m'), 'isToday' => true, - 'has_overdue' => count($overdueTransactions) > 0, + 'has_overdue' => count($overdueTransactions) + count($overdueRecurringInstances) > 0, ]; // Gerar pontos até a data final @@ -1237,6 +1271,18 @@ public function projectionChart(Request $request) } } + // Somar instâncias recorrentes neste período (apenas futuras) + foreach ($futureRecurringInstances as $ri) { + if ($ri->date > $periodStart && $ri->date <= $periodEnd) { + $amount = $this->convertToPrimaryCurrency(abs($ri->amount), $ri->currency); + if ($ri->type === 'credit') { + $runningBalance += $amount; + } else { + $runningBalance -= $amount; + } + } + } + $dataPoints[] = [ 'date' => $current->toDateString(), 'balance' => round($runningBalance, 2),