Correctif Calcul porte-monnaie
This commit is contained in:
1 parent
b43754fe19
commit
7ee011de09
3 files changed
+96
-134
No files matched your search
@@ -54,15 +54,22 @@ router.get('/', (req, res) => {
|
|||||||
`).all(...invParams, userId);
|
`).all(...invParams, userId);
|
||||||
|
|
||||||
/* ── Solde porte-monnaie par plateforme ──────────────────────── */
|
/* ── Solde porte-monnaie par plateforme ──────────────────────── */
|
||||||
|
// ?asOf=YYYY-MM-DD (optionnel) : cumule uniquement jusqu'à cette date au lieu
|
||||||
|
// de tout l'historique. Utilisé par le frontend pour le solde "à la fin de
|
||||||
|
// l'année sélectionnée", en réutilisant exactement la même formule que le
|
||||||
|
// solde live (source unique de vérité — cf. project_taxreport_interets_plateforme
|
||||||
|
// en mémoire sur les bugs de duplication de cette formule côté frontend).
|
||||||
|
const asOf = /^\d{4}-\d{2}-\d{2}$/.test(req.query.asOf || '') ? req.query.asOf : null;
|
||||||
|
|
||||||
const drManuelPerPlat = db.prepare(`
|
const drManuelPerPlat = db.prepare(`
|
||||||
SELECT plateforme_id,
|
SELECT plateforme_id,
|
||||||
COALESCE(SUM(CASE WHEN type='depot' THEN montant ELSE 0 END), 0) AS depots,
|
COALESCE(SUM(CASE WHEN type='depot' THEN montant ELSE 0 END), 0) AS depots,
|
||||||
COALESCE(SUM(CASE WHEN type='retrait' AND COALESCE(source,'') != 'auto_remboursement'
|
COALESCE(SUM(CASE WHEN type='retrait' AND COALESCE(source,'') != 'auto_remboursement'
|
||||||
THEN montant ELSE 0 END), 0) AS retraits_manuels
|
THEN montant ELSE 0 END), 0) AS retraits_manuels
|
||||||
FROM depots_retraits
|
FROM depots_retraits
|
||||||
WHERE ${invWhere}
|
WHERE ${invWhere}${asOf ? ' AND date_operation <= ?' : ''}
|
||||||
GROUP BY plateforme_id
|
GROUP BY plateforme_id
|
||||||
`).all(...invParams);
|
`).all(...invParams, ...(asOf ? [asOf] : []));
|
||||||
|
|
||||||
const rembWalletPerPlat = db.prepare(`
|
const rembWalletPerPlat = db.prepare(`
|
||||||
SELECT i.plateforme_id,
|
SELECT i.plateforme_id,
|
||||||
@@ -80,8 +87,9 @@ router.get('/', (req, res) => {
|
|||||||
WHERE ${invWhere.replace('investisseur_id', 'i.investisseur_id')}
|
WHERE ${invWhere.replace('investisseur_id', 'i.investisseur_id')}
|
||||||
AND r.type = 'normal'
|
AND r.type = 'normal'
|
||||||
AND r.methode_remboursement = 'portefeuille'
|
AND r.methode_remboursement = 'portefeuille'
|
||||||
|
${asOf ? 'AND r.date_remb <= ?' : ''}
|
||||||
GROUP BY i.plateforme_id
|
GROUP BY i.plateforme_id
|
||||||
`).all(...invParams);
|
`).all(...invParams, ...(asOf ? [asOf] : []));
|
||||||
|
|
||||||
const bonusWalletPerPlat = db.prepare(`
|
const bonusWalletPerPlat = db.prepare(`
|
||||||
SELECT bonus_plateforme_id AS plateforme_id,
|
SELECT bonus_plateforme_id AS plateforme_id,
|
||||||
@@ -89,8 +97,9 @@ router.get('/', (req, res) => {
|
|||||||
FROM remboursements
|
FROM remboursements
|
||||||
WHERE ${invWhere.replace('investisseur_id', 'bonus_investisseur_id')}
|
WHERE ${invWhere.replace('investisseur_id', 'bonus_investisseur_id')}
|
||||||
AND type IN ('bonus_parrainage', 'bonus_plateforme')
|
AND type IN ('bonus_parrainage', 'bonus_plateforme')
|
||||||
|
${asOf ? 'AND date_remb <= ?' : ''}
|
||||||
GROUP BY bonus_plateforme_id
|
GROUP BY bonus_plateforme_id
|
||||||
`).all(...invParams);
|
`).all(...invParams, ...(asOf ? [asOf] : []));
|
||||||
|
|
||||||
// Intérêts plateforme : comme les bonus, ils créditent directement le
|
// Intérêts plateforme : comme les bonus, ils créditent directement le
|
||||||
// porte-monnaie de la plateforme (net_recu tient déjà compte des prélèvements).
|
// porte-monnaie de la plateforme (net_recu tient déjà compte des prélèvements).
|
||||||
@@ -100,28 +109,30 @@ router.get('/', (req, res) => {
|
|||||||
FROM remboursements
|
FROM remboursements
|
||||||
WHERE ${invWhere.replace('investisseur_id', 'bonus_investisseur_id')}
|
WHERE ${invWhere.replace('investisseur_id', 'bonus_investisseur_id')}
|
||||||
AND type = 'interets_plateforme'
|
AND type = 'interets_plateforme'
|
||||||
|
${asOf ? 'AND date_remb <= ?' : ''}
|
||||||
GROUP BY bonus_plateforme_id
|
GROUP BY bonus_plateforme_id
|
||||||
`).all(...invParams);
|
`).all(...invParams, ...(asOf ? [asOf] : []));
|
||||||
|
|
||||||
const capitalInvestiPerPlat = db.prepare(`
|
const capitalInvestiPerPlat = db.prepare(`
|
||||||
SELECT i.plateforme_id,
|
SELECT i.plateforme_id,
|
||||||
COALESCE(SUM(
|
COALESCE(SUM(
|
||||||
i.montant_investi
|
i.montant_investi
|
||||||
+ COALESCE((SELECT SUM(rv.montant) FROM reinvestissements rv WHERE rv.investissement_id = i.id), 0)
|
+ COALESCE((SELECT SUM(rv.montant) FROM reinvestissements rv WHERE rv.investissement_id = i.id${asOf ? ' AND rv.date_reinvestissement <= ?' : ''}), 0)
|
||||||
), 0) AS capital_investi
|
), 0) AS capital_investi
|
||||||
FROM investissements i
|
FROM investissements i
|
||||||
WHERE ${invWhere.replace('investisseur_id', 'i.investisseur_id')}
|
WHERE ${invWhere.replace('investisseur_id', 'i.investisseur_id')}
|
||||||
|
${asOf ? 'AND i.date_souscription <= ?' : ''}
|
||||||
GROUP BY i.plateforme_id
|
GROUP BY i.plateforme_id
|
||||||
`).all(...invParams);
|
`).all(...(asOf ? [asOf] : []), ...invParams, ...(asOf ? [asOf] : []));
|
||||||
|
|
||||||
// Corrections de solde (micro-ecarts fiscaux sur plateformes flat_tax)
|
// Corrections de solde (micro-ecarts fiscaux sur plateformes flat_tax)
|
||||||
const correctionsPerPlat = db.prepare(`
|
const correctionsPerPlat = db.prepare(`
|
||||||
SELECT plateforme_id,
|
SELECT plateforme_id,
|
||||||
COALESCE(SUM(montant), 0) AS total_correction
|
COALESCE(SUM(montant), 0) AS total_correction
|
||||||
FROM corrections_solde
|
FROM corrections_solde
|
||||||
WHERE ${invWhere}
|
WHERE ${invWhere}${asOf ? ' AND date <= ?' : ''}
|
||||||
GROUP BY plateforme_id
|
GROUP BY plateforme_id
|
||||||
`).all(...invParams);
|
`).all(...invParams, ...(asOf ? [asOf] : []));
|
||||||
|
|
||||||
// Fusion en une map plateforme_id → solde_portefeuille
|
// Fusion en une map plateforme_id → solde_portefeuille
|
||||||
const walletMap = {};
|
const walletMap = {};
|
||||||
|
|||||||
@@ -101,6 +101,11 @@ export default function DepotsRetraits() {
|
|||||||
const [allRows, setAllRows] = useState([]);
|
const [allRows, setAllRows] = useState([]);
|
||||||
const [plats, setPlats] = useState([]);
|
const [plats, setPlats] = useState([]);
|
||||||
const [dashData, setDashData] = useState(null);
|
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
|
// 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 currentYear = String(new Date().getFullYear());
|
||||||
const [filter, setFilter] = useState({ type: '', plateforme_ids: [], year: currentYear, month: '' });
|
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]);
|
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
|
// Ferme tous les menus contextuels au scroll
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const closeAll = () => {
|
const closeAll = () => {
|
||||||
@@ -459,52 +480,20 @@ export default function DepotsRetraits() {
|
|||||||
else map[key].retraits += r.montant;
|
else map[key].retraits += r.montant;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Solde porte-monnaie = cumul jusqu'au 31/12 de l'année sélectionnée
|
// Solde porte-monnaie = cumul jusqu'au 31/12 de l'année sélectionnée, calculé
|
||||||
// = dépôts/retraits + remboursements crédités au portefeuille
|
// côté backend (GET /dashboard?asOf=) pour rester la même formule que le solde
|
||||||
// Règle fiscale : flat_tax → net_recu (PFU prélevé à la source) ;
|
// "live" — bonus, intérêts plateforme et corrections de solde inclus.
|
||||||
// hors France → capital + cashback + interets_bruts (pas de PFU à la source)
|
const soldeMap = {};
|
||||||
const yearEnd = `${drPlatYear}-12-31`;
|
for (const p of dashDataAsOf[`${drPlatYear}-12-31`]?.cashByPlatform ?? []) {
|
||||||
const cumul = {};
|
soldeMap[String(p.plateforme_id)] = p.solde_portefeuille;
|
||||||
|
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Object.values(map).map(p => {
|
return Object.values(map).map(p => {
|
||||||
p.solde_net = p.depots - p.retraits;
|
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;
|
return p;
|
||||||
}).sort((a, b) => b.solde_net - a.solde_net);
|
}).sort((a, b) => b.solde_net - a.solde_net);
|
||||||
}, [allRows, allRemb, allInv, dashData, drPlatYear]);
|
}, [allRows, dashDataAsOf, drPlatYear]);
|
||||||
|
|
||||||
const drPlatTotals = useMemo(() =>
|
const drPlatTotals = useMemo(() =>
|
||||||
drPlatData.reduce((acc, p) => {
|
drPlatData.reduce((acc, p) => {
|
||||||
@@ -525,7 +514,18 @@ export default function DepotsRetraits() {
|
|||||||
|
|
||||||
// Solde porte-monnaie pour le KPI :
|
// Solde porte-monnaie pour le KPI :
|
||||||
// - sans filtre année → live depuis le backend (inclut déjà les remboursements au portefeuille)
|
// - 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 = (() => {
|
const kpiSoldePortefeuille = (() => {
|
||||||
if (!drPlatYear) {
|
if (!drPlatYear) {
|
||||||
if (filter.plateforme_ids.length === 1 && dashData) {
|
if (filter.plateforme_ids.length === 1 && dashData) {
|
||||||
@@ -534,26 +534,7 @@ export default function DepotsRetraits() {
|
|||||||
}
|
}
|
||||||
return dashData?.solde_portefeuille_total ?? null;
|
return dashData?.solde_portefeuille_total ?? null;
|
||||||
}
|
}
|
||||||
const yearEnd = `${drPlatYear}-12-31`;
|
return soldeFromSnapshot(dashDataAsOf[`${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;
|
|
||||||
})();
|
})();
|
||||||
|
|
||||||
// Totaux KPI et solde porte-monnaie pour l'année N-1 (TrendBadge)
|
// 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 });
|
}, { depots: 0, retraits: 0 });
|
||||||
})();
|
})();
|
||||||
|
|
||||||
const prevKpiSoldePortefeuille = (() => {
|
const prevKpiSoldePortefeuille = soldeFromSnapshot(dashDataAsOf[`${prevYear}-12-31`]);
|
||||||
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;
|
|
||||||
})();
|
|
||||||
|
|
||||||
/* ── Enrichissement du KPI "Diff. Dépôts vs Retraits" avec l'objectif de l'année ──
|
/* ── 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),
|
Toujours calculé sur le portefeuille entier (ignore le filtre plateforme),
|
||||||
|
|||||||
@@ -299,6 +299,11 @@ export default function Plateformes() {
|
|||||||
const [allDepots, setAllDepots] = useState([]);
|
const [allDepots, setAllDepots] = useState([]);
|
||||||
const [allCorrections, setAllCorrections] = useState([]);
|
const [allCorrections, setAllCorrections] = useState([]);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
// Snapshots backend du solde porte-monnaie par plateforme, clé 'live' ou 'YYYY-12-31'
|
||||||
|
// (GET /dashboard[?asOf=]) — remplace le recalcul client historique de
|
||||||
|
// computeSoldePortefeuille, cf. dashDataAsOf dans DepotsRetraits.jsx et mémoire
|
||||||
|
// project_taxreport_interets_plateforme (source unique de vérité).
|
||||||
|
const [dashDataAsOf, setDashDataAsOf] = useState({});
|
||||||
|
|
||||||
/* ── État sélecteurs ── */
|
/* ── État sélecteurs ── */
|
||||||
/* selectedPlatName et activeTab sont dans l'URL pour survivre au retour arrière */
|
/* selectedPlatName et activeTab sont dans l'URL pour survivre au retour arrière */
|
||||||
@@ -379,6 +384,22 @@ export default function Plateformes() {
|
|||||||
|
|
||||||
useEffect(() => { load(); /* eslint-disable-next-line */ }, [activeId, activeView]);
|
useEffect(() => { load(); /* eslint-disable-next-line */ }, [activeId, activeView]);
|
||||||
|
|
||||||
|
// Snapshots backend du solde porte-monnaie : 'live' (aucune coupure) + l'année
|
||||||
|
// sélectionnée (si filtre actif) + l'année précédente (toujours nécessaire pour
|
||||||
|
// le TrendBadge). Même endpoint que DepotsRetraits.jsx (GET /dashboard[?asOf=]).
|
||||||
|
useEffect(() => {
|
||||||
|
if (!activeId && activeView !== 'all') return;
|
||||||
|
const scopeParams = activeView === 'all' ? { scope: 'all' } : undefined;
|
||||||
|
const prevYear = selectedYear ? Number(selectedYear) - 1 : new Date().getFullYear() - 1;
|
||||||
|
const keys = new Set(['live', `${prevYear}-12-31`]);
|
||||||
|
if (selectedYear) keys.add(`${selectedYear}-12-31`);
|
||||||
|
Promise.all([...keys].map(k =>
|
||||||
|
api.get('/dashboard', { ...scopeParams, ...(k === 'live' ? {} : { asOf: k }) }).then(d => [k, d])
|
||||||
|
)).then(entries => {
|
||||||
|
setDashDataAsOf(prev => ({ ...prev, ...Object.fromEntries(entries) }));
|
||||||
|
}).catch(e => console.error('dashDataAsOf load error (Plateformes):', e));
|
||||||
|
}, [selectedYear, activeView, activeId]); // eslint-disable-line
|
||||||
|
|
||||||
/* ── Section "Mois en cours" (DrillCellPanel) — saisie/édition de remboursements ── */
|
/* ── Section "Mois en cours" (DrillCellPanel) — saisie/édition de remboursements ── */
|
||||||
const [drillRefreshKey, setDrillRefreshKey] = useState(0);
|
const [drillRefreshKey, setDrillRefreshKey] = useState(0);
|
||||||
const [rembModalOpen, setRembModalOpen] = useState(false);
|
const [rembModalOpen, setRembModalOpen] = useState(false);
|
||||||
@@ -827,53 +848,23 @@ export default function Plateformes() {
|
|||||||
const netMode = displayMode === 'net';
|
const netMode = displayMode === 'net';
|
||||||
const prevYearLabel = selectedYear ? Number(selectedYear) - 1 : new Date().getFullYear() - 1;
|
const prevYearLabel = selectedYear ? Number(selectedYear) - 1 : new Date().getFullYear() - 1;
|
||||||
|
|
||||||
/* ── Solde porte-monnaie (cf. formule dashboard.js) ──────────────
|
/* ── Solde porte-monnaie (source unique : GET /dashboard[?asOf=], cf. dashboard.js) ──
|
||||||
dépôts − retraits manuels + remboursements crédités au portefeuille
|
Snapshot backend filtré sur la sélection plateforme(s)/détenteur (filteredPlatIds). */
|
||||||
+ bonus − capital investi + corrections de solde, cumulés sur la
|
const soldeFromSnapshot = (snap) => {
|
||||||
sélection plateforme(s)/détenteur, jusqu'à une date de coupure. */
|
if (!snap) return null;
|
||||||
const computeSoldePortefeuille = (cutoff) => {
|
const total = (snap.cashByPlatform ?? [])
|
||||||
let balance = 0;
|
.filter(c => filteredPlatIds.has(c.plateforme_id))
|
||||||
for (const d of allDepots) {
|
.reduce((s, c) => s + (c.solde_portefeuille ?? 0), 0);
|
||||||
if (!filteredPlatIds.has(d.plateforme_id)) continue;
|
return Math.round(total * 100) / 100;
|
||||||
if (cutoff && d.date_operation > cutoff) continue;
|
|
||||||
if (d.type === 'depot') balance += d.montant || 0;
|
|
||||||
else if (d.source !== 'auto_remboursement') balance -= d.montant || 0;
|
|
||||||
}
|
|
||||||
for (const r of allRembs) {
|
|
||||||
if (!filteredPlatIds.has(r.plateforme_id)) continue;
|
|
||||||
if (r.methode_remboursement !== 'portefeuille') continue;
|
|
||||||
if (!r.date_remb || (cutoff && r.date_remb > cutoff)) continue;
|
|
||||||
if (r.type === 'normal') {
|
|
||||||
const plat = plats.find(p => p.id === r.plateforme_id);
|
|
||||||
balance += plat?.fiscalite === 'flat_tax'
|
|
||||||
? (r.net_recu || 0)
|
|
||||||
: (r.capital || 0) + (r.cashback || 0) + (r.interets_bruts || 0);
|
|
||||||
} else if (r.type === 'bonus_parrainage' || r.type === 'bonus_plateforme') {
|
|
||||||
balance += r.cashback || 0;
|
|
||||||
} else if (r.type === 'interets_plateforme') {
|
|
||||||
balance += r.net_recu || 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (const inv of allRows) {
|
|
||||||
if (!filteredPlatIds.has(inv.plateforme_id)) continue;
|
|
||||||
if (!inv.date_souscription || (cutoff && inv.date_souscription > cutoff)) continue;
|
|
||||||
balance -= inv.capital_total ?? inv.montant_investi ?? 0;
|
|
||||||
}
|
|
||||||
for (const c of allCorrections) {
|
|
||||||
if (!filteredPlatIds.has(c.plateforme_id)) continue;
|
|
||||||
if (cutoff && c.date > cutoff) continue;
|
|
||||||
balance += c.montant || 0;
|
|
||||||
}
|
|
||||||
return Math.round(balance * 100) / 100;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const soldePortefeuille = useMemo(
|
const soldePortefeuille = useMemo(
|
||||||
() => computeSoldePortefeuille(selectedYear ? `${selectedYear}-12-31` : null),
|
() => soldeFromSnapshot(dashDataAsOf[selectedYear ? `${selectedYear}-12-31` : 'live']),
|
||||||
[allDepots, allRembs, allRows, allCorrections, plats, filteredPlatIds, selectedYear]
|
[dashDataAsOf, filteredPlatIds, selectedYear]
|
||||||
);
|
);
|
||||||
const prevSoldePortefeuille = useMemo(
|
const prevSoldePortefeuille = useMemo(
|
||||||
() => computeSoldePortefeuille(`${prevYearLabel}-12-31`),
|
() => soldeFromSnapshot(dashDataAsOf[`${prevYearLabel}-12-31`]),
|
||||||
[allDepots, allRembs, allRows, allCorrections, plats, filteredPlatIds, prevYearLabel]
|
[dashDataAsOf, filteredPlatIds, prevYearLabel]
|
||||||
);
|
);
|
||||||
|
|
||||||
/* ══════════════════════════════════════════════════════════════════════
|
/* ══════════════════════════════════════════════════════════════════════
|
||||||
|
|||||||
Reference in new issue
Block a user