v1.38.0 - Projeção de Saldo considera transações em atraso + alerta visual
This commit is contained in:
parent
b9592fc915
commit
f4a33b88bc
12
CHANGELOG.md
12
CHANGELOG.md
@ -5,6 +5,18 @@ O formato segue [Keep a Changelog](https://keepachangelog.com/pt-BR/).
|
|||||||
Este projeto adota [Versionamento Semântico](https://semver.org/pt-BR/).
|
Este projeto adota [Versionamento Semântico](https://semver.org/pt-BR/).
|
||||||
|
|
||||||
|
|
||||||
|
## [1.38.0] - 2025-12-15
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- **Projeção de Saldo** - Agora considera transações em atraso
|
||||||
|
- Backend busca todas as transações pendentes (incluindo atrasadas)
|
||||||
|
- Processa transações atrasadas ANTES do ponto inicial da projeção
|
||||||
|
- Adiciona informações de transações em atraso no summary da API
|
||||||
|
- Frontend exibe alerta amarelo quando há transações em atraso incluídas
|
||||||
|
- Alerta mostra quantidade de transações e impacto no saldo
|
||||||
|
- Traduções completas: overdueIncluded, overdueTransactions, includedInProjection (pt-BR, en, es)
|
||||||
|
- Melhora precisão da projeção ao incluir todas as pendências
|
||||||
|
|
||||||
## [1.37.0] - 2025-12-15
|
## [1.37.0] - 2025-12-15
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
@ -942,29 +942,45 @@ public function projectionChart(Request $request)
|
|||||||
ORDER BY li.due_date
|
ORDER BY li.due_date
|
||||||
", [$this->userId, $today->toDateString(), $endDate->toDateString()]);
|
", [$this->userId, $today->toDateString(), $endDate->toDateString()]);
|
||||||
|
|
||||||
// Buscar transações agendadas/pendentes
|
// Buscar transações agendadas/pendentes (incluindo atrasadas)
|
||||||
$scheduledTransactions = DB::select("
|
$scheduledTransactions = DB::select("
|
||||||
SELECT
|
SELECT
|
||||||
t.effective_date as date,
|
t.effective_date as date,
|
||||||
t.amount,
|
t.amount,
|
||||||
t.type,
|
t.type,
|
||||||
COALESCE(a.currency, 'EUR') as currency
|
COALESCE(a.currency, 'EUR') as currency,
|
||||||
|
CASE WHEN t.effective_date < ? THEN 1 ELSE 0 END as is_overdue
|
||||||
FROM transactions t
|
FROM transactions t
|
||||||
LEFT JOIN accounts a ON t.account_id = a.id
|
LEFT JOIN accounts a ON t.account_id = a.id
|
||||||
WHERE t.user_id = ?
|
WHERE t.user_id = ?
|
||||||
AND t.status IN ('pending', 'scheduled')
|
AND t.status IN ('pending', 'scheduled')
|
||||||
AND t.effective_date >= ?
|
|
||||||
AND t.effective_date <= ?
|
AND t.effective_date <= ?
|
||||||
AND t.deleted_at IS NULL
|
AND t.deleted_at IS NULL
|
||||||
ORDER BY t.effective_date
|
ORDER BY t.effective_date
|
||||||
", [$this->userId, $today->toDateString(), $endDate->toDateString()]);
|
", [$today->toDateString(), $this->userId, $endDate->toDateString()]);
|
||||||
|
|
||||||
// Ponto inicial
|
// 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);
|
||||||
|
|
||||||
|
// Processar transações atrasadas ANTES do ponto inicial
|
||||||
|
foreach ($overdueTransactions as $tx) {
|
||||||
|
$amount = $this->convertToPrimaryCurrency(abs($tx->amount), $tx->currency);
|
||||||
|
if ($tx->type === 'credit') {
|
||||||
|
$currentBalance += $amount;
|
||||||
|
} else {
|
||||||
|
$currentBalance -= $amount;
|
||||||
|
}
|
||||||
|
$runningBalance = $currentBalance;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ponto inicial (já inclui o impacto das transações atrasadas)
|
||||||
$dataPoints[] = [
|
$dataPoints[] = [
|
||||||
'date' => $today->toDateString(),
|
'date' => $today->toDateString(),
|
||||||
'balance' => round($runningBalance, 2),
|
'balance' => round($runningBalance, 2),
|
||||||
'label' => $today->format('d/m'),
|
'label' => $today->format('d/m'),
|
||||||
'isToday' => true,
|
'isToday' => true,
|
||||||
|
'has_overdue' => count($overdueTransactions) > 0,
|
||||||
];
|
];
|
||||||
|
|
||||||
// Gerar pontos até a data final
|
// Gerar pontos até a data final
|
||||||
@ -1001,8 +1017,8 @@ public function projectionChart(Request $request)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Somar transações agendadas neste período
|
// Somar transações agendadas neste período (apenas futuras)
|
||||||
foreach ($scheduledTransactions as $tx) {
|
foreach ($futureTransactions as $tx) {
|
||||||
if ($tx->date > $periodStart && $tx->date <= $periodEnd) {
|
if ($tx->date > $periodStart && $tx->date <= $periodEnd) {
|
||||||
$amount = $this->convertToPrimaryCurrency(abs($tx->amount), $tx->currency);
|
$amount = $this->convertToPrimaryCurrency(abs($tx->amount), $tx->currency);
|
||||||
if ($tx->type === 'credit') {
|
if ($tx->type === 'credit') {
|
||||||
@ -1048,6 +1064,11 @@ public function projectionChart(Request $request)
|
|||||||
'change' => round($finalBalance - $currentBalance, 2),
|
'change' => round($finalBalance - $currentBalance, 2),
|
||||||
'change_percent' => $currentBalance != 0 ? round((($finalBalance - $currentBalance) / abs($currentBalance)) * 100, 1) : 0,
|
'change_percent' => $currentBalance != 0 ? round((($finalBalance - $currentBalance) / abs($currentBalance)) * 100, 1) : 0,
|
||||||
'negative_month' => $negativeMonth,
|
'negative_month' => $negativeMonth,
|
||||||
|
'overdue_count' => count($overdueTransactions),
|
||||||
|
'overdue_impact' => round(array_reduce($overdueTransactions, function($carry, $tx) {
|
||||||
|
$amount = $this->convertToPrimaryCurrency(abs($tx->amount), $tx->currency);
|
||||||
|
return $carry + ($tx->type === 'credit' ? $amount : -$amount);
|
||||||
|
}, 0), 2),
|
||||||
],
|
],
|
||||||
'period' => [
|
'period' => [
|
||||||
'start' => $today->toDateString(),
|
'start' => $today->toDateString(),
|
||||||
|
|||||||
@ -228,6 +228,23 @@ const BalanceProjectionChart = () => {
|
|||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
{/* Summary Stats */}
|
{/* Summary Stats */}
|
||||||
{summary && (
|
{summary && (
|
||||||
|
<>
|
||||||
|
{/* Alert if overdue transactions included */}
|
||||||
|
{summary.overdue_count > 0 && (
|
||||||
|
<div className="alert alert-warning d-flex align-items-center mb-3" role="alert">
|
||||||
|
<i className="bi bi-exclamation-circle-fill me-2"></i>
|
||||||
|
<div>
|
||||||
|
<strong>{t('reports.projectionChart.overdueIncluded') || 'Atenção'}:</strong>{' '}
|
||||||
|
{summary.overdue_count} {t('reports.projectionChart.overdueTransactions') || 'transação(ões) em atraso'}{' '}
|
||||||
|
{t('reports.projectionChart.includedInProjection') || 'já incluída(s) no saldo atual'}.
|
||||||
|
{' '}
|
||||||
|
<span className={summary.overdue_impact < 0 ? 'text-danger fw-bold' : 'text-success fw-bold'}>
|
||||||
|
({summary.overdue_impact >= 0 ? '+' : ''}{currency(summary.overdue_impact, data?.currency)})
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="row g-3 mb-4">
|
<div className="row g-3 mb-4">
|
||||||
<div className="col-6 col-md-3">
|
<div className="col-6 col-md-3">
|
||||||
<div className="p-3 rounded bg-slate-700">
|
<div className="p-3 rounded bg-slate-700">
|
||||||
@ -262,6 +279,7 @@ const BalanceProjectionChart = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Alert if negative balance predicted */}
|
{/* Alert if negative balance predicted */}
|
||||||
|
|||||||
@ -1922,7 +1922,10 @@
|
|||||||
"2months": "2 months",
|
"2months": "2 months",
|
||||||
"3months": "3 months",
|
"3months": "3 months",
|
||||||
"6months": "6 months",
|
"6months": "6 months",
|
||||||
"12months": "12 months"
|
"12months": "12 months",
|
||||||
|
"overdueIncluded": "Overdue Transactions Included",
|
||||||
|
"overdueTransactions": "overdue transaction(s)",
|
||||||
|
"includedInProjection": "already included in balance projection"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"months": {
|
"months": {
|
||||||
|
|||||||
@ -1905,7 +1905,10 @@
|
|||||||
"2months": "2 meses",
|
"2months": "2 meses",
|
||||||
"3months": "3 meses",
|
"3months": "3 meses",
|
||||||
"6months": "6 meses",
|
"6months": "6 meses",
|
||||||
"12months": "12 meses"
|
"12months": "12 meses",
|
||||||
|
"overdueIncluded": "Transacciones Vencidas Incluidas",
|
||||||
|
"overdueTransactions": "transacción(es) vencida(s)",
|
||||||
|
"includedInProjection": "ya incluida(s) en la proyección del saldo"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"months": {
|
"months": {
|
||||||
|
|||||||
@ -1924,7 +1924,10 @@
|
|||||||
"2months": "2 meses",
|
"2months": "2 meses",
|
||||||
"3months": "3 meses",
|
"3months": "3 meses",
|
||||||
"6months": "6 meses",
|
"6months": "6 meses",
|
||||||
"12months": "12 meses"
|
"12months": "12 meses",
|
||||||
|
"overdueIncluded": "Transações em Atraso Incluídas",
|
||||||
|
"overdueTransactions": "transação(ões) em atraso",
|
||||||
|
"includedInProjection": "já incluída(s) na projeção do saldo"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"months": {
|
"months": {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user