diff --git a/backend/src/routes/dashboard.js b/backend/src/routes/dashboard.js
index 789ab1b..82260f0 100644
--- a/backend/src/routes/dashboard.js
+++ b/backend/src/routes/dashboard.js
@@ -152,6 +152,12 @@ router.get('/', (req, res) => {
cashByPlatformEnriched.reduce((s, p) => s + p.solde_portefeuille, 0) * 100
) / 100;
+ // Total des corrections de solde, toutes plateformes confondues (cumul complet
+ // quand asOf est absent) — utilisé par le KPI "Gain net depuis le début" du Dashboard.
+ const corrections_total = Math.round(
+ correctionsPerPlat.reduce((s, r) => s + r.total_correction, 0) * 100
+ ) / 100;
+
/* ── Portfolio ───────────────────────────────────────────────── */
const portfolio = db.prepare(`
SELECT
@@ -242,7 +248,7 @@ router.get('/', (req, res) => {
ORDER BY i.date_souscription DESC
`).all(...invParams);
- res.json({ cash, cashByPlatform: cashByPlatformEnriched, solde_portefeuille_total, portfolio, interets, interetsParAnnee, upcoming, enDefaut });
+ res.json({ cash, cashByPlatform: cashByPlatformEnriched, solde_portefeuille_total, corrections_total, portfolio, interets, interetsParAnnee, upcoming, enDefaut });
});
diff --git a/frontend/src/pages/Dashboard.jsx b/frontend/src/pages/Dashboard.jsx
index 6d40929..dd3f5da 100644
--- a/frontend/src/pages/Dashboard.jsx
+++ b/frontend/src/pages/Dashboard.jsx
@@ -179,7 +179,7 @@ function KpiCard({ title, value, badge, refValue, onClick, hint }) {
}
/* ── KPI filtrés par année ── */
-function DashboardKpis({ portfolio, netMode, pfuRates, capitalMensuelData, activeId, activeView, soldePortefeuilleLive }) {
+function DashboardKpis({ portfolio, netMode, pfuRates, capitalMensuelData, activeId, activeView, soldePortefeuilleLive, correctionsTotal }) {
const {
annee, modeGlobal, rawDataGlobal, rawData, currentYear, currentMonth,
setInclureInterets, setInclureCapital, setInclureCashback,
@@ -192,26 +192,46 @@ function DashboardKpis({ portfolio, netMode, pfuRates, capitalMensuelData, activ
// (GET /dashboard[?asOf=]), formule unique cf. mémoire
// project_taxreport_interets_plateforme. "Depuis le début" → valeur live
// déjà chargée par le parent ; année précise → snapshot fin d'année.
+ //
+ // Comparaison : en vue live (mode global ou année courante), on compare au
+ // solde de fin du mois calendaire précédent (comme le KPI "Gain net depuis
+ // le début"). En vue d'une année passée, on garde la comparaison fin
+ // d'année vs fin d'année précédente (seule comparaison qui a du sens pour
+ // un snapshot historique figé au 31/12).
+ const prevMonthDatePM = new Date(currentYear, currentMonth - 2, 1);
+ const prevMonthEndDatePM = new Date(prevMonthDatePM.getFullYear(), prevMonthDatePM.getMonth() + 1, 0);
+ const walletPrevMonthKey = `${prevMonthEndDatePM.getFullYear()}-${String(prevMonthEndDatePM.getMonth() + 1).padStart(2, '0')}-${String(prevMonthEndDatePM.getDate()).padStart(2, '0')}`;
+ const walletPrevMonthLabel = `${['jan.','fév.','mar.','avr.','mai','juin','juil.','août','sep.','oct.','nov.','déc.'][prevMonthDatePM.getMonth()]} ${prevMonthDatePM.getFullYear()}`;
+
const [walletAsOf, setWalletAsOf] = useState({});
useEffect(() => {
- if (modeGlobal) return;
if (!activeId && activeView !== 'all') return;
const scopeParams = activeView === 'all' ? { scope: 'all' } : undefined;
- const prevYear = Number(annee) - 1;
- const keys = [`${annee}-12-31`, `${prevYear}-12-31`];
+ const keys = [];
+ if (modeGlobal || isCurrentYear) {
+ keys.push(walletPrevMonthKey);
+ }
+ if (!modeGlobal && !isCurrentYear) {
+ const prevYear = Number(annee) - 1;
+ keys.push(`${annee}-12-31`, `${prevYear}-12-31`);
+ }
+ if (!keys.length) return;
Promise.all(keys.map(asOf =>
api.get('/dashboard', { ...scopeParams, asOf }).then(d => [asOf, d])
)).then(entries => {
setWalletAsOf(prev => ({ ...prev, ...Object.fromEntries(entries) }));
}).catch(e => console.error('walletAsOf load error:', e));
- }, [annee, modeGlobal, activeId, activeView]); // eslint-disable-line
+ }, [annee, modeGlobal, isCurrentYear, walletPrevMonthKey, activeId, activeView]); // eslint-disable-line
- const soldePortefeuilleValue = modeGlobal
+ const soldePortefeuilleValue = (modeGlobal || isCurrentYear)
? soldePortefeuilleLive
: (walletAsOf[`${annee}-12-31`]?.solde_portefeuille_total ?? null);
- const soldePortefeuillePrev = modeGlobal
- ? null
+ const soldePortefeuillePrev = (modeGlobal || isCurrentYear)
+ ? (walletAsOf[walletPrevMonthKey]?.solde_portefeuille_total ?? null)
: (walletAsOf[`${Number(annee) - 1}-12-31`]?.solde_portefeuille_total ?? null);
+ const soldePortefeuillePrevLabel = (modeGlobal || isCurrentYear)
+ ? walletPrevMonthLabel
+ : String(Number(annee) - 1);
// Capital de référence pour l'année sélectionnée
// Pour les années passées : dernier mois de capitalMensuel (= capital déployé fin décembre)
@@ -317,6 +337,32 @@ function DashboardKpis({ portfolio, netMode, pfuRates, capitalMensuelData, activ
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [rawDataGlobal, annee, modeGlobal, pfuRates]);
+ // ── Gain net depuis le début ──────────────────────────────────
+ // Toujours net (indépendant du toggle Brut/Net), toujours cumulé sur tout
+ // l'historique (indépendant du sélecteur d'année) : intérêts nets + cashback
+ // (investissement + bonus parrainage/plateforme, déjà inclus dans le champ
+ // "cashback" de interets-annuels) + corrections de solde. Volontairement
+ // indépendant du capital encore investi et du solde porte-monnaie courant :
+ // reste juste même si tout l'argent est retiré des plateformes.
+ // Voir mémoire project_gain_net_depuis_debut pour la dérivation complète.
+ const gainNetTotal = useMemo(() => {
+ const totals = (rawDataGlobal.rembourses ?? []).reduce((acc, r) => ({
+ interets_nets: acc.interets_nets + (r.interets_nets || 0),
+ cashback: acc.cashback + (r.cashback || 0),
+ }), { interets_nets: 0, cashback: 0 });
+ return totals.interets_nets + totals.cashback + (correctionsTotal || 0);
+ }, [rawDataGlobal, correctionsTotal]);
+
+ // Valeur "Gain net" telle qu'elle aurait été affichée fin du mois précédent —
+ // on retire du cumul total la part perçue ce mois-ci (intérêts nets + cashback,
+ // via thisMonthRow, déjà chargé pour l'année courante). Les corrections de solde
+ // n'ont pas de granularité mensuelle côté backend ; elles restent donc dans le
+ // cumul "avant ce mois", ce qui est sans incidence pratique (montants marginaux).
+ const gainNetPrevMonthEnd = useMemo(() => {
+ const deltaCeMois = (thisMonthRow?.interets_nets || 0) + (thisMonthRow?.cashback || 0);
+ return gainNetTotal - deltaCeMois;
+ }, [gainNetTotal, thisMonthRow]);
+
// ── Valeur affichée ──
const getValue = (field) => {
if (isCurrentYear) return thisMonthRow?.[field] || 0;
@@ -410,11 +456,11 @@ function DashboardKpis({ portfolio, netMode, pfuRates, capitalMensuelData, activ
title="Porte-monnaie"
hint="Cash disponible sur les porte-monnaie des plateformes (hors compte courant)"
value={soldePortefeuilleValue != null ? fmtEUR(soldePortefeuilleValue) : '…'}
- badge={!modeGlobal && soldePortefeuilleValue != null && soldePortefeuillePrev != null
+ badge={soldePortefeuilleValue != null && soldePortefeuillePrev != null
?
: null}
- refValue={!modeGlobal && soldePortefeuillePrev != null
- ? fmtEUR(soldePortefeuillePrev) + ' en ' + (Number(annee) - 1)
+ refValue={soldePortefeuillePrev != null
+ ? fmtEUR(soldePortefeuillePrev) + ' en ' + soldePortefeuillePrevLabel
: null}
/>
{ setInclureInterets(false); setInclureCapital(false); setInclureCashback(true); }}
/>
+ }
+ refValue={fmtEUR(gainNetPrevMonthEnd) + ' en ' + prevMonthLabel}
+ />
);
}
@@ -569,6 +622,7 @@ export default function Dashboard() {