Mias à jour des KPI

This commit is contained in:
ocroguennec committed 2026-08-21 10:38:13 +02:00
1 parent 6c96e023a9
commit b48731ec50
2 files changed
+72 -12

No files matched your search

+7 -1
View File
@@ -152,6 +152,12 @@ router.get('/', (req, res) => {
cashByPlatformEnriched.reduce((s, p) => s + p.solde_portefeuille, 0) * 100 cashByPlatformEnriched.reduce((s, p) => s + p.solde_portefeuille, 0) * 100
) / 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 ───────────────────────────────────────────────── */ /* ── Portfolio ───────────────────────────────────────────────── */
const portfolio = db.prepare(` const portfolio = db.prepare(`
SELECT SELECT
@@ -242,7 +248,7 @@ router.get('/', (req, res) => {
ORDER BY i.date_souscription DESC ORDER BY i.date_souscription DESC
`).all(...invParams); `).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 });
}); });
+65 -11
View File
@@ -179,7 +179,7 @@ function KpiCard({ title, value, badge, refValue, onClick, hint }) {
} }
/* ── KPI filtrés par année ── */ /* ── 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 { const {
annee, modeGlobal, rawDataGlobal, rawData, currentYear, currentMonth, annee, modeGlobal, rawDataGlobal, rawData, currentYear, currentMonth,
setInclureInterets, setInclureCapital, setInclureCashback, setInclureInterets, setInclureCapital, setInclureCashback,
@@ -192,26 +192,46 @@ function DashboardKpis({ portfolio, netMode, pfuRates, capitalMensuelData, activ
// (GET /dashboard[?asOf=]), formule unique cf. mémoire // (GET /dashboard[?asOf=]), formule unique cf. mémoire
// project_taxreport_interets_plateforme. "Depuis le début" → valeur live // 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. // 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({}); const [walletAsOf, setWalletAsOf] = useState({});
useEffect(() => { useEffect(() => {
if (modeGlobal) return;
if (!activeId && activeView !== 'all') return; if (!activeId && activeView !== 'all') return;
const scopeParams = activeView === 'all' ? { scope: 'all' } : undefined; const scopeParams = activeView === 'all' ? { scope: 'all' } : undefined;
const prevYear = Number(annee) - 1; const keys = [];
const keys = [`${annee}-12-31`, `${prevYear}-12-31`]; 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 => Promise.all(keys.map(asOf =>
api.get('/dashboard', { ...scopeParams, asOf }).then(d => [asOf, d]) api.get('/dashboard', { ...scopeParams, asOf }).then(d => [asOf, d])
)).then(entries => { )).then(entries => {
setWalletAsOf(prev => ({ ...prev, ...Object.fromEntries(entries) })); setWalletAsOf(prev => ({ ...prev, ...Object.fromEntries(entries) }));
}).catch(e => console.error('walletAsOf load error:', e)); }).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 ? soldePortefeuilleLive
: (walletAsOf[`${annee}-12-31`]?.solde_portefeuille_total ?? null); : (walletAsOf[`${annee}-12-31`]?.solde_portefeuille_total ?? null);
const soldePortefeuillePrev = modeGlobal const soldePortefeuillePrev = (modeGlobal || isCurrentYear)
? null ? (walletAsOf[walletPrevMonthKey]?.solde_portefeuille_total ?? null)
: (walletAsOf[`${Number(annee) - 1}-12-31`]?.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 // 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) // 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 // eslint-disable-next-line react-hooks/exhaustive-deps
}, [rawDataGlobal, annee, modeGlobal, pfuRates]); }, [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 ── // ── Valeur affichée ──
const getValue = (field) => { const getValue = (field) => {
if (isCurrentYear) return thisMonthRow?.[field] || 0; if (isCurrentYear) return thisMonthRow?.[field] || 0;
@@ -410,11 +456,11 @@ function DashboardKpis({ portfolio, netMode, pfuRates, capitalMensuelData, activ
title="Porte-monnaie" title="Porte-monnaie"
hint="Cash disponible sur les porte-monnaie des plateformes (hors compte courant)" hint="Cash disponible sur les porte-monnaie des plateformes (hors compte courant)"
value={soldePortefeuilleValue != null ? fmtEUR(soldePortefeuilleValue) : '…'} value={soldePortefeuilleValue != null ? fmtEUR(soldePortefeuilleValue) : '…'}
badge={!modeGlobal && soldePortefeuilleValue != null && soldePortefeuillePrev != null badge={soldePortefeuilleValue != null && soldePortefeuillePrev != null
? <TrendBadge current={soldePortefeuilleValue} prev={soldePortefeuillePrev} /> ? <TrendBadge current={soldePortefeuilleValue} prev={soldePortefeuillePrev} />
: null} : null}
refValue={!modeGlobal && soldePortefeuillePrev != null refValue={soldePortefeuillePrev != null
? fmtEUR(soldePortefeuillePrev) + ' en ' + (Number(annee) - 1) ? fmtEUR(soldePortefeuillePrev) + ' en ' + soldePortefeuillePrevLabel
: null} : null}
/> />
<KpiCard <KpiCard
@@ -446,6 +492,13 @@ function DashboardKpis({ portfolio, netMode, pfuRates, capitalMensuelData, activ
refValue={mkRef('cashback')} refValue={mkRef('cashback')}
onClick={() => { setInclureInterets(false); setInclureCapital(false); setInclureCashback(true); }} onClick={() => { setInclureInterets(false); setInclureCapital(false); setInclureCashback(true); }}
/> />
<KpiCard
title="Gain net depuis le début"
hint="Intérêts nets + cashback + bonus parrainage/plateforme + corrections de solde, cumulés depuis le début — indépendant du capital encore investi ou du solde actuel sur les plateformes (reste juste même en cas de retrait complet)."
value={fmtEUR(gainNetTotal)}
badge={<TrendBadge current={gainNetTotal} prev={gainNetPrevMonthEnd} />}
refValue={fmtEUR(gainNetPrevMonthEnd) + ' en ' + prevMonthLabel}
/>
</div> </div>
); );
} }
@@ -569,6 +622,7 @@ export default function Dashboard() {
<DashboardKpis <DashboardKpis
portfolio={portfolio} netMode={netMode} pfuRates={pfuRates} capitalMensuelData={capitalMensuelData} portfolio={portfolio} netMode={netMode} pfuRates={pfuRates} capitalMensuelData={capitalMensuelData}
activeId={activeId} activeView={activeView} soldePortefeuilleLive={data?.solde_portefeuille_total ?? null} activeId={activeId} activeView={activeView} soldePortefeuilleLive={data?.solde_portefeuille_total ?? null}
correctionsTotal={data?.corrections_total ?? 0}
/> />
<YearSelectorKpi /> <YearSelectorKpi />
</div> </div>