Correctif Calcul porte-monnaie
This commit is contained in:
1 parent
b43754fe19
commit
7ee011de09
3 files changed
+96
-134
No files matched your search
@@ -101,6 +101,11 @@ export default function DepotsRetraits() {
|
||||
const [allRows, setAllRows] = useState([]);
|
||||
const [plats, setPlats] = useState([]);
|
||||
const [dashData, setDashData] = useState(null);
|
||||
// Snapshots du solde porte-monnaie "à la fin de l'année N" (GET /dashboard?asOf=),
|
||||
// clé = 'YYYY-12-31'. Remplace un ancien recalcul client-side qui avait divergé
|
||||
// de la formule backend (bonus/intérêts plateforme/corrections manquants) —
|
||||
// cf. mémoire project_taxreport_interets_plateforme.
|
||||
const [dashDataAsOf, setDashDataAsOf] = useState({});
|
||||
// Année sélectionnée, partagée entre l'onglet Plateformes et le filtre "Année" de Mouvements
|
||||
const currentYear = String(new Date().getFullYear());
|
||||
const [filter, setFilter] = useState({ type: '', plateforme_ids: [], year: currentYear, month: '' });
|
||||
@@ -186,6 +191,22 @@ export default function DepotsRetraits() {
|
||||
|
||||
useEffect(() => { load(); /* eslint-disable-next-line */ }, [activeId, activeView]);
|
||||
|
||||
// Snapshots "fin d'année" du solde porte-monnaie, calculés côté backend
|
||||
// (GET /dashboard?asOf=YYYY-12-31) : année sélectionnée (si filtre actif) +
|
||||
// année précédente (toujours nécessaire pour le TrendBadge).
|
||||
useEffect(() => {
|
||||
if (!activeId && activeView !== 'all') return;
|
||||
const scopeParams = activeView === 'all' ? { scope: 'all' } : undefined;
|
||||
const prevYear = String(Number(drPlatYear || new Date().getFullYear()) - 1);
|
||||
const dates = new Set([`${prevYear}-12-31`]);
|
||||
if (drPlatYear) dates.add(`${drPlatYear}-12-31`);
|
||||
Promise.all([...dates].map(asOf =>
|
||||
api.get('/dashboard', { ...scopeParams, asOf }).then(d => [asOf, d])
|
||||
)).then(entries => {
|
||||
setDashDataAsOf(prev => ({ ...prev, ...Object.fromEntries(entries) }));
|
||||
}).catch(e => console.error('dashDataAsOf load error:', e));
|
||||
}, [drPlatYear, activeView, activeId]); // eslint-disable-line
|
||||
|
||||
// Ferme tous les menus contextuels au scroll
|
||||
useEffect(() => {
|
||||
const closeAll = () => {
|
||||
@@ -459,52 +480,20 @@ export default function DepotsRetraits() {
|
||||
else map[key].retraits += r.montant;
|
||||
}
|
||||
|
||||
// Solde porte-monnaie = cumul jusqu'au 31/12 de l'année sélectionnée
|
||||
// = dépôts/retraits + remboursements crédités au portefeuille
|
||||
// Règle fiscale : flat_tax → net_recu (PFU prélevé à la source) ;
|
||||
// hors France → capital + cashback + interets_bruts (pas de PFU à la source)
|
||||
const yearEnd = `${drPlatYear}-12-31`;
|
||||
const cumul = {};
|
||||
|
||||
// 1. Dépôts et retraits manuels (hors auto-retraits — générés par remboursements
|
||||
// sur compte courant, ils ne représentent pas un mouvement du porte-monnaie)
|
||||
for (const r of allRows) {
|
||||
if (r.date_operation > yearEnd) continue;
|
||||
if (r.source === 'auto_remboursement') continue;
|
||||
const key = String(r.plateforme_id);
|
||||
cumul[key] = (cumul[key] ?? 0) + (r.type === 'depot' ? r.montant : -r.montant);
|
||||
}
|
||||
|
||||
// 2. Remboursements crédités au portefeuille
|
||||
// Les remboursements vers compte_courant ne crédient pas le porte-monnaie.
|
||||
// Pour flat_tax : net_recu (PFU déjà prélevé à la source).
|
||||
// Pour hors France : capital + cashback + interets_bruts (pas de PFU à la source ;
|
||||
// interets_bruts est déjà net de la retenue locale si applicable).
|
||||
for (const r of allRemb) {
|
||||
if (!r.date_remb || r.date_remb > yearEnd) continue;
|
||||
if (r.methode_remboursement !== 'portefeuille') continue;
|
||||
const key = String(r.plateforme_id);
|
||||
const plat = plats.find(p => p.id === r.plateforme_id);
|
||||
const credit = plat?.fiscalite === 'flat_tax'
|
||||
? (r.net_recu ?? 0)
|
||||
: (r.capital ?? 0) + (r.cashback ?? 0) + (r.interets_bruts ?? 0);
|
||||
cumul[key] = (cumul[key] ?? 0) + credit;
|
||||
}
|
||||
|
||||
// 3. Déduire le capital investi (capital_total = montant_investi + réinvestissements)
|
||||
// Le capital revient progressivement via net_recu (étape 2) au fil des remboursements
|
||||
for (const inv of allInv) {
|
||||
if (!inv.date_souscription || inv.date_souscription > yearEnd) continue;
|
||||
const key = String(inv.plateforme_id);
|
||||
cumul[key] = (cumul[key] ?? 0) - (inv.capital_total ?? inv.montant_investi);
|
||||
// Solde porte-monnaie = cumul jusqu'au 31/12 de l'année sélectionnée, calculé
|
||||
// côté backend (GET /dashboard?asOf=) pour rester la même formule que le solde
|
||||
// "live" — bonus, intérêts plateforme et corrections de solde inclus.
|
||||
const soldeMap = {};
|
||||
for (const p of dashDataAsOf[`${drPlatYear}-12-31`]?.cashByPlatform ?? []) {
|
||||
soldeMap[String(p.plateforme_id)] = p.solde_portefeuille;
|
||||
}
|
||||
|
||||
return Object.values(map).map(p => {
|
||||
p.solde_net = p.depots - p.retraits;
|
||||
p.solde_portefeuille = cumul[String(p.plateforme_id)] ?? 0;
|
||||
p.solde_portefeuille = soldeMap[String(p.plateforme_id)] ?? 0;
|
||||
return p;
|
||||
}).sort((a, b) => b.solde_net - a.solde_net);
|
||||
}, [allRows, allRemb, allInv, dashData, drPlatYear]);
|
||||
}, [allRows, dashDataAsOf, drPlatYear]);
|
||||
|
||||
const drPlatTotals = useMemo(() =>
|
||||
drPlatData.reduce((acc, p) => {
|
||||
@@ -525,7 +514,18 @@ export default function DepotsRetraits() {
|
||||
|
||||
// Solde porte-monnaie pour le KPI :
|
||||
// - sans filtre année → live depuis le backend (inclut déjà les remboursements au portefeuille)
|
||||
// - avec filtre année → cumul jusqu'au 31/12 de l'année (dépôts/retraits + remboursements au portefeuille)
|
||||
// - avec filtre année → snapshot backend "fin d'année" (GET /dashboard?asOf=), même
|
||||
// formule que le solde live (dashDataAsOf, cf. effet plus haut)
|
||||
const soldeFromSnapshot = (snap) => {
|
||||
if (!snap) return null;
|
||||
if (filter.plateforme_ids.length > 0) {
|
||||
return (snap.cashByPlatform ?? [])
|
||||
.filter(c => filter.plateforme_ids.includes(String(c.plateforme_id)))
|
||||
.reduce((s, c) => s + (c.solde_portefeuille ?? 0), 0);
|
||||
}
|
||||
return snap.solde_portefeuille_total ?? null;
|
||||
};
|
||||
|
||||
const kpiSoldePortefeuille = (() => {
|
||||
if (!drPlatYear) {
|
||||
if (filter.plateforme_ids.length === 1 && dashData) {
|
||||
@@ -534,26 +534,7 @@ export default function DepotsRetraits() {
|
||||
}
|
||||
return dashData?.solde_portefeuille_total ?? null;
|
||||
}
|
||||
const yearEnd = `${drPlatYear}-12-31`;
|
||||
let balance = 0;
|
||||
for (const r of allRows) {
|
||||
if (r.date_operation > yearEnd) continue;
|
||||
if (r.source === 'auto_remboursement') continue;
|
||||
if (filter.plateforme_ids.length > 0 && !filter.plateforme_ids.includes(String(r.plateforme_id))) continue;
|
||||
balance += r.type === 'depot' ? r.montant : -r.montant;
|
||||
}
|
||||
for (const r of allRemb) {
|
||||
if (!r.date_remb || r.date_remb > yearEnd) continue;
|
||||
if (r.methode_remboursement !== 'portefeuille') continue;
|
||||
if (filter.plateforme_ids.length > 0 && !filter.plateforme_ids.includes(String(r.plateforme_id))) continue;
|
||||
balance += r.net_recu ?? 0;
|
||||
}
|
||||
for (const inv of allInv) {
|
||||
if (!inv.date_souscription || inv.date_souscription > yearEnd) continue;
|
||||
if (filter.plateforme_ids.length > 0 && !filter.plateforme_ids.includes(String(inv.plateforme_id))) continue;
|
||||
balance -= inv.capital_total ?? inv.montant_investi;
|
||||
}
|
||||
return balance;
|
||||
return soldeFromSnapshot(dashDataAsOf[`${drPlatYear}-12-31`]);
|
||||
})();
|
||||
|
||||
// Totaux KPI et solde porte-monnaie pour l'année N-1 (TrendBadge)
|
||||
@@ -566,28 +547,7 @@ export default function DepotsRetraits() {
|
||||
}, { depots: 0, retraits: 0 });
|
||||
})();
|
||||
|
||||
const prevKpiSoldePortefeuille = (() => {
|
||||
const yearEnd = `${prevYear}-12-31`;
|
||||
let balance = 0;
|
||||
for (const r of allRows) {
|
||||
if (r.date_operation > yearEnd) continue;
|
||||
if (r.source === 'auto_remboursement') continue;
|
||||
if (filter.plateforme_ids.length > 0 && !filter.plateforme_ids.includes(String(r.plateforme_id))) continue;
|
||||
balance += r.type === 'depot' ? r.montant : -r.montant;
|
||||
}
|
||||
for (const r of allRemb) {
|
||||
if (!r.date_remb || r.date_remb > yearEnd) continue;
|
||||
if (r.methode_remboursement !== 'portefeuille') continue;
|
||||
if (filter.plateforme_ids.length > 0 && !filter.plateforme_ids.includes(String(r.plateforme_id))) continue;
|
||||
balance += r.net_recu ?? 0;
|
||||
}
|
||||
for (const inv of allInv) {
|
||||
if (!inv.date_souscription || inv.date_souscription > yearEnd) continue;
|
||||
if (filter.plateforme_ids.length > 0 && !filter.plateforme_ids.includes(String(inv.plateforme_id))) continue;
|
||||
balance -= inv.capital_total ?? inv.montant_investi;
|
||||
}
|
||||
return balance;
|
||||
})();
|
||||
const prevKpiSoldePortefeuille = soldeFromSnapshot(dashDataAsOf[`${prevYear}-12-31`]);
|
||||
|
||||
/* ── Enrichissement du KPI "Diff. Dépôts vs Retraits" avec l'objectif de l'année ──
|
||||
Toujours calculé sur le portefeuille entier (ignore le filtre plateforme),
|
||||
|
||||
Reference in new issue
Block a user