Amélioration du traitement des mouvements
This commit is contained in:
1 parent
9540221577
commit
6c96e023a9
8 files changed
+348
-82
No files matched your search
@@ -20,7 +20,7 @@ import ConfirmModal from '../components/ConfirmModal.jsx';
|
||||
import {
|
||||
mouvToCSV, mouvToXLS, mouvToJSON,
|
||||
ExportDropdown, AddDropdown, CorrectionPickerModal, DetailPanel, CorrectionModal, MOIS_FR, emptyMouvementForm,
|
||||
timestampSuffix, dlBlob,
|
||||
timestampSuffix, dlBlob, sortMouvements, computeSoldeIndicatifMap,
|
||||
} from '../components/MouvementsShared.jsx';
|
||||
|
||||
const LOGOS_BASE = '/api/logos/';
|
||||
@@ -1039,6 +1039,7 @@ export default function Plateformes() {
|
||||
const plat = plats.find(p => p.id === r.plateforme_id);
|
||||
const base = {
|
||||
_rembId: r.id,
|
||||
_invId: r.investissement_id ?? null,
|
||||
plateforme_id: r.plateforme_id,
|
||||
plateforme_nom: r.plateforme_nom,
|
||||
plateforme_detenteur_nom: r.plateforme_detenteur_nom,
|
||||
@@ -1177,6 +1178,16 @@ export default function Plateformes() {
|
||||
return [...set].sort((a, b) => a.localeCompare(b, 'fr'));
|
||||
}, [mvNormalizedRembMouvements, mvNormalizedSouscriptionMouvements, allDepots, mvRembProjetById, mvFilter.plateforme_ids]);
|
||||
|
||||
// Solde indicatif du porte-monnaie après chaque mouvement — calculé sur l'historique
|
||||
// COMPLET et NON FILTRÉ (allDepots/mvNormalizedCorrections/mvNormalizedRembMouvements/
|
||||
// mvNormalizedSouscriptionMouvements), pas sur les variantes déjà filtrées année/mois/
|
||||
// type/projet — sinon le cumul démarrerait arbitrairement au milieu de l'historique.
|
||||
// Purement indicatif (affichage/export) — ne remplace pas computeSoldePortefeuille.
|
||||
// Voir [[project_mouvements_solde_indicatif]].
|
||||
const mvSoldeIndicatifByRowId = useMemo(() => computeSoldeIndicatifMap([
|
||||
...allDepots, ...mvNormalizedCorrections, ...mvNormalizedRembMouvements, ...mvNormalizedSouscriptionMouvements,
|
||||
]), [allDepots, mvNormalizedCorrections, mvNormalizedRembMouvements, mvNormalizedSouscriptionMouvements]);
|
||||
|
||||
const mvTableRows = useMemo(() => {
|
||||
const showDR = !mvFilter.type || mvFilter.type === 'depot' || mvFilter.type === 'retrait';
|
||||
const showCorr = !mvFilter.type || mvFilter.type === 'correction';
|
||||
@@ -1190,12 +1201,11 @@ export default function Plateformes() {
|
||||
const subPart = mvFilteredSouscriptionMouvements
|
||||
.filter(r => !mvFilter.type || r.type === mvFilter.type)
|
||||
.map(r => ({ ...r, _projet: r.libelle || '' }));
|
||||
return [...drPart, ...corrPart, ...rembPart, ...subPart]
|
||||
.filter(r => !mvFilter.projet || r._projet === mvFilter.projet)
|
||||
.sort((a, b) =>
|
||||
b.date_operation.localeCompare(a.date_operation) || String(b.id).localeCompare(String(a.id))
|
||||
);
|
||||
}, [mvRows, mvFilteredCorrections, mvFilteredRembMouvements, mvFilteredSouscriptionMouvements, mvFilter.type, mvFilter.projet, mvRembProjetById]);
|
||||
return sortMouvements(
|
||||
[...drPart, ...corrPart, ...rembPart, ...subPart]
|
||||
.filter(r => !mvFilter.projet || r._projet === mvFilter.projet)
|
||||
).map(r => ({ ...r, _soldeIndicatif: mvSoldeIndicatifByRowId.get(r.id) }));
|
||||
}, [mvRows, mvFilteredCorrections, mvFilteredRembMouvements, mvFilteredSouscriptionMouvements, mvFilter.type, mvFilter.projet, mvRembProjetById, mvSoldeIndicatifByRowId]);
|
||||
|
||||
const {
|
||||
pagedItems: mvPagedTableRows, page: mvPage, setPage: setMvPage,
|
||||
@@ -1445,11 +1455,13 @@ export default function Plateformes() {
|
||||
<div className="label">Capital investi</div>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8, margin: '4px 0 0' }}>
|
||||
<span style={{ fontSize: '1.35rem', fontWeight: 700 }}>{fmtEUR(totals.encours)}</span>
|
||||
<TrendBadge current={totals.encours} prev={prevTotals.encours} />
|
||||
</div>
|
||||
<div style={{ fontSize: '0.8em', color: 'var(--text-muted)', marginTop: 5 }}>
|
||||
{fmtEUR(prevTotals.encours)} en {prevYearLabel}
|
||||
{selectedYear && <TrendBadge current={totals.encours} prev={prevTotals.encours} />}
|
||||
</div>
|
||||
{selectedYear && (
|
||||
<div style={{ fontSize: '0.8em', color: 'var(--text-muted)', marginTop: 5 }}>
|
||||
{fmtEUR(prevTotals.encours)} en {prevYearLabel}
|
||||
</div>
|
||||
)}
|
||||
{objectifsActifs && kpiEcartCapitalObjectif != null && (
|
||||
<div style={{
|
||||
fontSize: '0.8em', marginTop: 4, fontWeight: 600,
|
||||
@@ -1471,11 +1483,13 @@ export default function Plateformes() {
|
||||
<span style={{ fontSize: '1.35rem', fontWeight: 700 }} className={totals.defaut > 0 ? 'danger' : ''}>
|
||||
{fmtEUR(totals.defaut)}
|
||||
</span>
|
||||
{totals.defaut > 0 && <TrendBadge current={totals.defaut} prev={prevTotals.defaut} invert={true} />}
|
||||
</div>
|
||||
<div style={{ fontSize: '0.8em', color: 'var(--text-muted)', marginTop: 5 }}>
|
||||
{fmtEUR(prevTotals.defaut)} en {prevYearLabel}
|
||||
{selectedYear && totals.defaut > 0 && <TrendBadge current={totals.defaut} prev={prevTotals.defaut} invert={true} />}
|
||||
</div>
|
||||
{selectedYear && (
|
||||
<div style={{ fontSize: '0.8em', color: 'var(--text-muted)', marginTop: 5 }}>
|
||||
{fmtEUR(prevTotals.defaut)} en {prevYearLabel}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 3 — Porte-monnaie */}
|
||||
@@ -1483,11 +1497,13 @@ export default function Plateformes() {
|
||||
<div className="label">Porte-monnaie</div>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8, margin: '4px 0 0' }}>
|
||||
<span style={{ fontSize: '1.35rem', fontWeight: 700 }}>{fmtEUR(soldePortefeuille)}</span>
|
||||
<TrendBadge current={soldePortefeuille} prev={prevSoldePortefeuille} />
|
||||
</div>
|
||||
<div style={{ fontSize: '0.8em', color: 'var(--text-muted)', marginTop: 5 }}>
|
||||
{fmtEUR(prevSoldePortefeuille)} en {prevYearLabel}
|
||||
{selectedYear && <TrendBadge current={soldePortefeuille} prev={prevSoldePortefeuille} />}
|
||||
</div>
|
||||
{selectedYear && (
|
||||
<div style={{ fontSize: '0.8em', color: 'var(--text-muted)', marginTop: 5 }}>
|
||||
{fmtEUR(prevSoldePortefeuille)} en {prevYearLabel}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 4 — Investissements depuis le début */}
|
||||
@@ -1495,11 +1511,13 @@ export default function Plateformes() {
|
||||
<div className="label">Investissements depuis le début</div>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8, margin: '4px 0 0' }}>
|
||||
<span style={{ fontSize: '1.35rem', fontWeight: 700 }}>{fmtEUR(totals.investi)}</span>
|
||||
<TrendBadge current={totals.investi} prev={prevTotals.investi} />
|
||||
</div>
|
||||
<div style={{ fontSize: '0.8em', color: 'var(--text-muted)', marginTop: 5 }}>
|
||||
{fmtEUR(prevTotals.investi)} en {prevYearLabel}
|
||||
{selectedYear && <TrendBadge current={totals.investi} prev={prevTotals.investi} />}
|
||||
</div>
|
||||
{selectedYear && (
|
||||
<div style={{ fontSize: '0.8em', color: 'var(--text-muted)', marginTop: 5 }}>
|
||||
{fmtEUR(prevTotals.investi)} en {prevYearLabel}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 5 — Capital remboursé */}
|
||||
@@ -1507,11 +1525,13 @@ export default function Plateformes() {
|
||||
<div className="label">Capital remboursé</div>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8, margin: '4px 0 0' }}>
|
||||
<span style={{ fontSize: '1.35rem', fontWeight: 700 }}>{fmtEUR(totals.cap_remb)}</span>
|
||||
<TrendBadge current={totals.cap_remb} prev={prevTotals.cap_remb} />
|
||||
</div>
|
||||
<div style={{ fontSize: '0.8em', color: 'var(--text-muted)', marginTop: 5 }}>
|
||||
{fmtEUR(prevTotals.cap_remb)} en {prevYearLabel}
|
||||
{selectedYear && <TrendBadge current={totals.cap_remb} prev={prevTotals.cap_remb} />}
|
||||
</div>
|
||||
{selectedYear && (
|
||||
<div style={{ fontSize: '0.8em', color: 'var(--text-muted)', marginTop: 5 }}>
|
||||
{fmtEUR(prevTotals.cap_remb)} en {prevYearLabel}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 6 — Intérêts perçus */}
|
||||
@@ -1521,14 +1541,18 @@ export default function Plateformes() {
|
||||
<span style={{ fontSize: '1.35rem', fontWeight: 700 }}>
|
||||
{fmtEUR(netMode ? totals.int_perc_net : totals.int_perc)}
|
||||
</span>
|
||||
<TrendBadge
|
||||
current={netMode ? totals.int_perc_net : totals.int_perc}
|
||||
prev={netMode ? prevTotals.int_perc_net : prevTotals.int_perc}
|
||||
/>
|
||||
</div>
|
||||
<div style={{ fontSize: '0.8em', color: 'var(--text-muted)', marginTop: 5 }}>
|
||||
{fmtEUR(netMode ? prevTotals.int_perc_net : prevTotals.int_perc)} en {prevYearLabel}
|
||||
{selectedYear && (
|
||||
<TrendBadge
|
||||
current={netMode ? totals.int_perc_net : totals.int_perc}
|
||||
prev={netMode ? prevTotals.int_perc_net : prevTotals.int_perc}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{selectedYear && (
|
||||
<div style={{ fontSize: '0.8em', color: 'var(--text-muted)', marginTop: 5 }}>
|
||||
{fmtEUR(netMode ? prevTotals.int_perc_net : prevTotals.int_perc)} en {prevYearLabel}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 7 — XIRR */}
|
||||
@@ -2566,12 +2590,12 @@ export default function Plateformes() {
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th><th>Plateforme</th>{multiDetenteur && <th>Détenteur</th>}<th>Type de mouvement</th><th>Projet</th><th className="num">Montant</th><th style={{ width: 36 }}></th>
|
||||
<th>Date</th><th>Plateforme</th>{multiDetenteur && <th>Détenteur</th>}<th>Type de mouvement</th><th>Projet</th><th className="num">Montant</th><th className="num" title="Solde du porte-monnaie de la plateforme après ce mouvement — calculé à titre indicatif, pas une donnée officielle">Solde indicatif</th><th style={{ width: 36 }}></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{mvTableRows.length === 0 && (
|
||||
<tr><td colSpan={multiDetenteur ? 7 : 6} className="text-muted" style={{ textAlign: 'center', padding: 24 }}>
|
||||
<tr><td colSpan={multiDetenteur ? 8 : 7} className="text-muted" style={{ textAlign: 'center', padding: 24 }}>
|
||||
{loading ? 'Chargement…' : 'Aucun mouvement'}
|
||||
</td></tr>
|
||||
)}
|
||||
@@ -2599,6 +2623,7 @@ export default function Plateformes() {
|
||||
<td className={`num ${isPos ? 'success' : 'danger'}`}>
|
||||
{isPos ? '+' : '−'} {fmtEUR(Math.abs(r.montant))}
|
||||
</td>
|
||||
<td className="num text-muted" style={{ fontStyle: 'italic' }}>{r._soldeIndicatif != null ? fmtEUR(r._soldeIndicatif) : '—'}</td>
|
||||
<td style={{ width: 36, textAlign: 'center' }} onClick={e => e.stopPropagation()}>
|
||||
<button
|
||||
style={{ background: 'none', border: 'none', cursor: 'pointer', padding: '2px 6px', borderRadius: 4, color: 'var(--text-muted)', lineHeight: 1, fontSize: 16 }}
|
||||
@@ -2636,6 +2661,7 @@ export default function Plateformes() {
|
||||
<td className="num success">
|
||||
+ {fmtEUR(r.montant)}
|
||||
</td>
|
||||
<td className="num text-muted" style={{ fontStyle: 'italic' }}>{r._soldeIndicatif != null ? fmtEUR(r._soldeIndicatif) : '—'}</td>
|
||||
<td style={{ width: 36, textAlign: 'center' }} />
|
||||
</tr>
|
||||
);
|
||||
@@ -2665,6 +2691,7 @@ export default function Plateformes() {
|
||||
<td className="num danger">
|
||||
− {fmtEUR(r.montant)}
|
||||
</td>
|
||||
<td className="num text-muted" style={{ fontStyle: 'italic' }}>{r._soldeIndicatif != null ? fmtEUR(r._soldeIndicatif) : '—'}</td>
|
||||
<td style={{ width: 36, textAlign: 'center' }} />
|
||||
</tr>
|
||||
);
|
||||
@@ -2701,6 +2728,7 @@ export default function Plateformes() {
|
||||
<td className="num danger">
|
||||
− {fmtEUR(r.montant)}
|
||||
</td>
|
||||
<td className="num text-muted" style={{ fontStyle: 'italic' }}>{r._soldeIndicatif != null ? fmtEUR(r._soldeIndicatif) : '—'}</td>
|
||||
<td style={{ width: 36, textAlign: 'center' }} />
|
||||
</tr>
|
||||
);
|
||||
@@ -2732,6 +2760,7 @@ export default function Plateformes() {
|
||||
style={ isAuto ? { fontStyle: 'italic' } : undefined }>
|
||||
{r.type === 'depot' ? '+' : '−'} {fmtEUR(r.montant)}
|
||||
</td>
|
||||
<td className="num text-muted" style={{ fontStyle: 'italic' }}>{r._soldeIndicatif != null ? fmtEUR(r._soldeIndicatif) : '—'}</td>
|
||||
<td style={{ width: 36, textAlign: 'center' }} onClick={e => e.stopPropagation()}>
|
||||
{!isAuto && (
|
||||
<button
|
||||
|
||||
Reference in new issue
Block a user