MAJ
This commit is contained in:
1 parent
6bba34396c
commit
1d5737703b
9 files changed
+347
-45
No files matched your search
@@ -33,6 +33,16 @@ import Modal from './Modal.jsx';
|
||||
* InvestissementDetail.jsx, investissement déjà connu du contexte)
|
||||
* investissementOptions — liste pour le <select> investissement (mode non-locked
|
||||
* uniquement) — { id, plateforme_nom, nom_projet }[]
|
||||
* pe — bool (19/09/26, demande Olivier) : true quand ce frais
|
||||
* est rattaché à un deal Private Equity plutôt qu'à un
|
||||
* investissement crowdlending. Ne change que le libellé de
|
||||
* l'option "source" du sélecteur Prélèvement ci-dessous —
|
||||
* côté PE, ce mode réduit le capital investissable du deal
|
||||
* (montant_investi reste le montant BRUT transféré, cf.
|
||||
* InvestissementPeFormModal.jsx / capital_investissable),
|
||||
* alors que côté crowdlending il s'ajoute au capital net
|
||||
* déjà déduit à la souscription — deux mécaniques
|
||||
* différentes qui méritaient des libellés différents.
|
||||
* confirmingDelete, setConfirmingDelete — état de confirmation de suppression (géré par le
|
||||
* parent, remis à false dans son propre onClose)
|
||||
*/
|
||||
@@ -45,6 +55,7 @@ export default function FraisFormModal({
|
||||
investissementOptions = [],
|
||||
confirmingDelete, setConfirmingDelete,
|
||||
submitting = false,
|
||||
pe = false,
|
||||
}) {
|
||||
const modalTitle = editingId ? 'Modifier le frais' : 'Ajouter un frais';
|
||||
|
||||
@@ -118,7 +129,7 @@ export default function FraisFormModal({
|
||||
<label>Prélèvement</label>
|
||||
<select value={form.mode_reglement}
|
||||
onChange={e => onFieldChange('mode_reglement', e.target.value)}>
|
||||
<option value="source">À la source (sans mouvement complémentaire)</option>
|
||||
<option value="source">{pe ? 'Lors de la souscription (diminue le montant investissable)' : 'À la source (sans mouvement complémentaire)'}</option>
|
||||
<option value="portefeuille">Versement complémentaire — Porte-monnaie de la plateforme</option>
|
||||
<option value="compte_courant">Versement complémentaire — Compte courant du détenteur</option>
|
||||
</select>
|
||||
|
||||
@@ -218,7 +218,7 @@ function FraisBlock({ prefix, label, form, setField, handleFraisReglementChange,
|
||||
<div style={{ gridColumn: '1 / -1' }}>
|
||||
<label>Prélèvement</label>
|
||||
<select value={modeReglement} onChange={e => handleFraisReglementChange(prefix, e.target.value)}>
|
||||
<option value="source">À la source (sans mouvement complémentaire)</option>
|
||||
<option value="source">Lors de la souscription (diminue le montant investissable)</option>
|
||||
<option value="portefeuille">Versement complémentaire — Porte-monnaie de la plateforme</option>
|
||||
<option value="compte_courant">Versement complémentaire — Compte courant du détenteur</option>
|
||||
</select>
|
||||
|
||||
@@ -41,7 +41,20 @@ export function signedMontant(r) {
|
||||
// retomber sur interets_nets — le frais doit suivre exactement la même convention, sinon la
|
||||
// chaîne ne retombe jamais à 0 après un retrait AUTO (confirmé par Olivier le 01/09/26 : le
|
||||
// reliquat observé était exactement égal au montant du frais).
|
||||
if (r.type === 'frais') return r.mode_reglement === 'compte_courant' ? 0 : -Math.abs(r.montant);
|
||||
// Frais PE prélevés "à la source" (19/09/26, confirmé par Olivier) : contrairement au
|
||||
// crowdlending où montant_investi est le capital NET (frais 'source' = débit supplémentaire
|
||||
// légitime, cf. commentaire ci-dessus), côté PE le montant_investi affiché en ligne
|
||||
// "souscription" est déjà le montant BRUT transféré à la plateforme, frais compris — un
|
||||
// frais 'source' PE n'est donc PAS un débit supplémentaire du porte-monnaie, sous peine de
|
||||
// compter deux fois la même sortie d'argent. Affiché quand même (traçabilité), à 0 d'effet,
|
||||
// comme 'compte_courant'. Repéré via r._investType === 'pe' (posé par Plateformes.jsx /
|
||||
// mvNormalizedPeMouvements) plutôt que par plateforme : ne dépend pas de la plateforme, mais
|
||||
// du modèle de données PE lui-même (toujours vrai pour tout deal PE).
|
||||
if (r.type === 'frais') {
|
||||
if (r.mode_reglement === 'compte_courant') return 0;
|
||||
if (r._investType === 'pe' && r.mode_reglement === 'source') return 0;
|
||||
return -Math.abs(r.montant);
|
||||
}
|
||||
if (DEBIT_TYPES.has(r.type)) return -Math.abs(r.montant);
|
||||
if (r.type === 'correction') return r.montant;
|
||||
return Math.abs(r.montant);
|
||||
@@ -464,6 +477,16 @@ export function DetailPanel({ row, onEdit, onDeleteCorrection, onViewRemb, onVie
|
||||
const isFraisHorsPM = isFrais && row.mode_reglement === 'compte_courant';
|
||||
const isFraisRembLinked = isFrais && row.mode_reglement === 'remboursement';
|
||||
const isAuto = row.source === 'auto_remboursement';
|
||||
// Mouvement PE fusionné dans le porte-monnaie crowdlending (19/09/26, cf. Plateformes.jsx /
|
||||
// mvNormalizedPeMouvements) : les liens "Voir l'investissement" / "Voir le remboursement" /
|
||||
// "Modifier" ci-dessous supposent tous des ids et des routes du workspace crowdlending
|
||||
// (/investissements/:id, /remboursements?edit-remb=, le modal dépôt/retrait crowdlending) —
|
||||
// inapplicables tels quels à un id de la table PE ou à un dépôt enregistré sous un autre
|
||||
// workspace_id. Plutôt que de risquer une navigation vers le mauvais enregistrement (voire,
|
||||
// en cas de collision d'id entre investissements et investissements_pe, vers un investissement
|
||||
// crowdlending sans rapport), on affiche ici une mention neutre : à modifier depuis son
|
||||
// workspace d'origine.
|
||||
const isForeign = row._investType === 'pe';
|
||||
|
||||
const typeLabel = isCorrection ? 'Correction de solde'
|
||||
: isBonus ? 'Bonus'
|
||||
@@ -638,10 +661,12 @@ export function DetailPanel({ row, onEdit, onDeleteCorrection, onViewRemb, onVie
|
||||
? "Frais réglé via un compte courant du détenteur — n'a jamais d'effet sur le solde du porte-monnaie de la plateforme, affiché ici à titre de traçabilité uniquement."
|
||||
: isFraisRembLinked
|
||||
? "Frais de distribution déduit du coupon brut de ce remboursement, avant calcul des prélèvements — réduit d'autant le montant net reçu (cf. avis d'opéré de la plateforme)."
|
||||
: row.mode_reglement === 'source'
|
||||
? "Frais prélevé à la source par la plateforme sur le montant transféré — sort du porte-monnaie en plus du capital net déjà déduit de la souscription."
|
||||
: "Versement complémentaire réglé depuis le porte-monnaie de la plateforme — sort du porte-monnaie indépendamment du capital investi."}
|
||||
{' '}{isFraisRembLinked ? "Modifiable depuis le remboursement." : "Modifiable depuis la fiche de l'investissement."}
|
||||
: (isForeign && row.mode_reglement === 'source')
|
||||
? "Frais prélevé à la source par la plateforme sur le montant transféré lors de la souscription au deal — déjà inclus dans le montant de la souscription, sans effet supplémentaire sur le solde du porte-monnaie."
|
||||
: row.mode_reglement === 'source'
|
||||
? "Frais prélevé à la source par la plateforme sur le montant transféré — sort du porte-monnaie en plus du capital net déjà déduit de la souscription."
|
||||
: "Versement complémentaire réglé depuis le porte-monnaie de la plateforme — sort du porte-monnaie indépendamment du capital investi."}
|
||||
{' '}{isForeign ? null : isFraisRembLinked ? "Modifiable depuis le remboursement." : "Modifiable depuis la fiche de l'investissement."}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
@@ -656,7 +681,11 @@ export function DetailPanel({ row, onEdit, onDeleteCorrection, onViewRemb, onVie
|
||||
</div>
|
||||
|
||||
<div className="dr-detail-footer">
|
||||
{isAuto ? null : (isRemb || isPrelevement) ? (
|
||||
{isForeign ? (
|
||||
<div style={{ fontSize: 'var(--fs-sm)', color: 'var(--text-muted)', fontStyle: 'italic', padding: '4px 2px' }}>
|
||||
Mouvement Private Equity — modifiable depuis le workspace Private Equity.
|
||||
</div>
|
||||
) : isAuto ? null : (isRemb || isPrelevement) ? (
|
||||
<button className="dr-detail-edit-btn" onClick={() => onViewRemb(row)}>
|
||||
Voir / modifier le remboursement
|
||||
</button>
|
||||
|
||||
@@ -559,6 +559,7 @@ export default function FraisPe() {
|
||||
investissementOptions={investissementOptions}
|
||||
confirmingDelete={confirmingDelete}
|
||||
setConfirmingDelete={setConfirmingDelete}
|
||||
pe
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1015,6 +1015,7 @@ export default function InvestissementPeDetail() {
|
||||
submitting={fraisSubmitting}
|
||||
comptesInvestisseur={comptesInvestisseur}
|
||||
locked
|
||||
pe
|
||||
investissementOptions={[]}
|
||||
confirmingDelete={fraisConfirmingDelete}
|
||||
setConfirmingDelete={setFraisConfirmingDelete}
|
||||
|
||||
@@ -306,6 +306,12 @@ export default function Plateformes() {
|
||||
const [allDepots, setAllDepots] = useState([]);
|
||||
const [allCorrections, setAllCorrections] = useState([]);
|
||||
const [allFraisOps, setAllFraisOps] = useState([]); // frais_operations, tous investisseurs (filtré via mvInvById)
|
||||
// Mouvements porte-monnaie PE (souscriptions/distributions/frais) vivant hors du workspace
|
||||
// crowdlending actif — cf. GET /investissements-pe/mouvements-portefeuille. Une plateforme
|
||||
// (ex. Fundora) peut faire du crowdlending ET du PE avec un porte-monnaie physique unique ;
|
||||
// sans ça, le solde indicatif de l'onglet "Mouvements Porte-monnaie" ignorait tout l'argent
|
||||
// côté PE (bug signalé par Olivier le 19/09/26).
|
||||
const [allPeWalletMouvements, setAllPeWalletMouvements] = useState([]);
|
||||
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
|
||||
@@ -377,7 +383,7 @@ export default function Plateformes() {
|
||||
setLoading(true);
|
||||
try {
|
||||
const scopeParams = activeView === 'all' ? { scope: 'all' } : {};
|
||||
const [rows, p, rembs, reinvests, depots, corrections, simulRembs, pfu, fraisOps] = await Promise.all([
|
||||
const [rows, p, rembs, reinvests, depots, corrections, simulRembs, pfu, fraisOps, peWalletMouvements] = await Promise.all([
|
||||
api.get('/investissements', scopeParams),
|
||||
api.get('/plateformes'),
|
||||
api.get('/remboursements', scopeParams),
|
||||
@@ -389,6 +395,10 @@ export default function Plateformes() {
|
||||
// /frais-operations sans investissement_id ne connaît lui aussi que l'utilisateur —
|
||||
// même pattern que /reinvestissements, filtré côté client via mvInvById.
|
||||
api.get('/frais-operations').catch(() => []),
|
||||
// Toujours hors scopeParams (scope=all/X-Investisseur-Id géré côté backend comme les
|
||||
// autres endpoints ci-dessus) mais jamais borné au workspace actif, volontairement —
|
||||
// cf. commentaire de la route.
|
||||
api.get('/investissements-pe/mouvements-portefeuille', scopeParams).catch(() => []),
|
||||
]);
|
||||
setAllRows(rows);
|
||||
setPlats(p);
|
||||
@@ -399,6 +409,7 @@ export default function Plateformes() {
|
||||
setAllSimulRembs(Array.isArray(simulRembs) ? simulRembs : []);
|
||||
setPfuRates(Array.isArray(pfu) ? pfu : []);
|
||||
setAllFraisOps(Array.isArray(fraisOps) ? fraisOps : []);
|
||||
setAllPeWalletMouvements(Array.isArray(peWalletMouvements) ? peWalletMouvements : []);
|
||||
} finally { setLoading(false); setDrillRefreshKey(k => k + 1); }
|
||||
};
|
||||
|
||||
@@ -1078,7 +1089,7 @@ export default function Plateformes() {
|
||||
allReinvests, allCorrections, plats). Toute évolution de la logique de
|
||||
décomposition des mouvements doit être répercutée dans les deux fichiers.
|
||||
══════════════════════════════════════════════════════════════════════ */
|
||||
const [mvFilter, setMvFilter] = useState({ type: '', plateforme_ids: [], projet: '', year: '', month: '' });
|
||||
const [mvFilter, setMvFilter] = useState({ type: '', investType: '', plateforme_ids: [], projet: '', year: '', month: '' });
|
||||
const [mvListFocused, setMvListFocused] = useState(false);
|
||||
const [mvModalOpen, setMvModalOpen] = useState(false);
|
||||
const [mvEditingId, setMvEditingId] = useState(null);
|
||||
@@ -1327,6 +1338,32 @@ export default function Plateformes() {
|
||||
return true;
|
||||
}), [mvNormalizedFraisMouvements, mvFilter.plateforme_ids, mvFilter.year, mvFilter.month]);
|
||||
|
||||
// Mouvements PE (19/09/26, demande Olivier — porte-monnaie unifié) : déjà mis en forme côté
|
||||
// backend (GET /investissements-pe/mouvements-portefeuille, type/montant/date_operation/
|
||||
// plateforme_id/libelle prêts à l'emploi), on ajoute juste ici _investType (pour le filtre et
|
||||
// le badge) et `source` (même convention que les lignes crowdlending, pour l'export).
|
||||
// Depuis le 19/09/26, allPeWalletMouvements inclut aussi des lignes type='depot'/'retrait'
|
||||
// (dépôts manuels enregistrés côté workspace PE, ex. les 4 x 800 € de Fundora) : on leur
|
||||
// reconstitue un `source` cohérent avec les dépôts crowdlending (mvRows, via raw_source =
|
||||
// valeur brute de depots_retraits.source) pour que le badge "AUTO" et l'export CSV/XLS
|
||||
// restent corrects.
|
||||
const mvNormalizedPeMouvements = useMemo(() => allPeWalletMouvements.map(r => ({
|
||||
...r,
|
||||
_investType: 'pe',
|
||||
source: r.type === 'souscription' ? 'souscription'
|
||||
: r.type === 'frais' ? 'frais_operation'
|
||||
: (r.type === 'depot' || r.type === 'retrait') ? (r.raw_source || 'manuel')
|
||||
: 'remboursement',
|
||||
})), [allPeWalletMouvements]);
|
||||
|
||||
const mvFilteredPeMouvements = useMemo(() => mvNormalizedPeMouvements.filter(r => {
|
||||
if (mvFilter.plateforme_ids.length > 0 && !mvFilter.plateforme_ids.includes(String(r.plateforme_id))) return false;
|
||||
const [y, m] = r.date_operation.split('-');
|
||||
if (mvFilter.year && y !== mvFilter.year) return false;
|
||||
if (mvFilter.month && m !== mvFilter.month.padStart(2,'0')) return false;
|
||||
return true;
|
||||
}), [mvNormalizedPeMouvements, mvFilter.plateforme_ids, mvFilter.year, mvFilter.month]);
|
||||
|
||||
// Projets disponibles pour le filtre "Projet" — dépend de la plateforme (verrouillée) mais
|
||||
// pas des autres filtres (type/année/mois), pour ne pas faire disparaître d'options en cours
|
||||
// d'affinage. Inclut les remboursements/souscriptions/réinvestissements ainsi que les
|
||||
@@ -1338,46 +1375,55 @@ export default function Plateformes() {
|
||||
for (const r of mvNormalizedRembMouvements) if (inScope(r.plateforme_id) && r.libelle) set.add(r.libelle);
|
||||
for (const r of mvNormalizedSouscriptionMouvements) if (inScope(r.plateforme_id) && r.libelle) set.add(r.libelle);
|
||||
for (const r of mvNormalizedFraisMouvements) if (inScope(r.plateforme_id) && r.libelle) set.add(r.libelle);
|
||||
for (const r of mvNormalizedPeMouvements) if (inScope(r.plateforme_id) && r.libelle) set.add(r.libelle);
|
||||
for (const d of allDepots) {
|
||||
if (!inScope(d.plateforme_id) || !d.remboursement_id) continue;
|
||||
const p = mvRembProjetById.get(d.remboursement_id);
|
||||
if (p) set.add(p);
|
||||
}
|
||||
return [...set].sort((a, b) => a.localeCompare(b, 'fr'));
|
||||
}, [mvNormalizedRembMouvements, mvNormalizedSouscriptionMouvements, mvNormalizedFraisMouvements, allDepots, mvRembProjetById, mvFilter.plateforme_ids]);
|
||||
}, [mvNormalizedRembMouvements, mvNormalizedSouscriptionMouvements, mvNormalizedFraisMouvements, mvNormalizedPeMouvements, 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.
|
||||
// mvNormalizedSouscriptionMouvements/mvNormalizedFraisMouvements/mvNormalizedPeMouvements),
|
||||
// pas sur les variantes déjà filtrées année/mois/type/projet/investType — sinon le cumul
|
||||
// démarrerait arbitrairement au milieu de l'historique. mvNormalizedPeMouvements est inclus
|
||||
// ici (19/09/26, demande Olivier) même s'il n'a pas d'onglet dédié pour l'afficher seul : sur
|
||||
// une plateforme partagée, ces lignes PE font partie du MÊME porte-monnaie physique, donc du
|
||||
// même cumul, qu'on les affiche ou non selon le filtre "Type d'investissement" du moment.
|
||||
// Purement indicatif (affichage/export) — ne remplace pas computeSoldePortefeuille.
|
||||
// Voir [[project_mouvements_solde_indicatif]].
|
||||
const mvSoldeIndicatifByRowId = useMemo(() => computeSoldeIndicatifMap([
|
||||
...allDepots, ...mvNormalizedCorrections, ...mvNormalizedRembMouvements, ...mvNormalizedSouscriptionMouvements,
|
||||
...mvNormalizedFraisMouvements,
|
||||
]), [allDepots, mvNormalizedCorrections, mvNormalizedRembMouvements, mvNormalizedSouscriptionMouvements, mvNormalizedFraisMouvements]);
|
||||
...mvNormalizedFraisMouvements, ...mvNormalizedPeMouvements,
|
||||
]), [allDepots, mvNormalizedCorrections, mvNormalizedRembMouvements, mvNormalizedSouscriptionMouvements, mvNormalizedFraisMouvements, mvNormalizedPeMouvements]);
|
||||
|
||||
const mvTableRows = useMemo(() => {
|
||||
const showDR = !mvFilter.type || mvFilter.type === 'depot' || mvFilter.type === 'retrait';
|
||||
const showCorr = !mvFilter.type || mvFilter.type === 'correction';
|
||||
const drPart = showDR
|
||||
? mvRows.map(r => ({ ...r, _projet: r.remboursement_id ? (mvRembProjetById.get(r.remboursement_id) || '') : '' }))
|
||||
const showCl = mvFilter.investType !== 'pe';
|
||||
const showPe = mvFilter.investType !== 'crowdlending';
|
||||
const drPart = showDR && showCl
|
||||
? mvRows.map(r => ({ ...r, _projet: r.remboursement_id ? (mvRembProjetById.get(r.remboursement_id) || '') : '', _investType: 'crowdlending' }))
|
||||
: [];
|
||||
const corrPart = showCorr ? mvFilteredCorrections.map(c => ({ ...c, _projet: '' })) : [];
|
||||
const rembPart = mvFilteredRembMouvements
|
||||
const corrPart = showCorr && showCl ? mvFilteredCorrections.map(c => ({ ...c, _projet: '', _investType: 'crowdlending' })) : [];
|
||||
const rembPart = showCl ? mvFilteredRembMouvements
|
||||
.filter(r => !mvFilter.type || r.type === mvFilter.type)
|
||||
.map(r => ({ ...r, _projet: r.libelle || '' }));
|
||||
const subPart = mvFilteredSouscriptionMouvements
|
||||
.map(r => ({ ...r, _projet: r.libelle || '', _investType: 'crowdlending' })) : [];
|
||||
const subPart = showCl ? mvFilteredSouscriptionMouvements
|
||||
.filter(r => !mvFilter.type || r.type === mvFilter.type)
|
||||
.map(r => ({ ...r, _projet: r.libelle || '' }));
|
||||
const fraisPart = mvFilteredFraisMouvements
|
||||
.map(r => ({ ...r, _projet: r.libelle || '', _investType: 'crowdlending' })) : [];
|
||||
const fraisPart = showCl ? mvFilteredFraisMouvements
|
||||
.filter(r => !mvFilter.type || r.type === mvFilter.type)
|
||||
.map(r => ({ ...r, _projet: r.libelle || '' }));
|
||||
.map(r => ({ ...r, _projet: r.libelle || '', _investType: 'crowdlending' })) : [];
|
||||
const pePart = showPe ? mvFilteredPeMouvements
|
||||
.filter(r => !mvFilter.type || r.type === mvFilter.type)
|
||||
.map(r => ({ ...r, _projet: r.libelle || '' })) : [];
|
||||
return sortMouvements(
|
||||
[...drPart, ...corrPart, ...rembPart, ...subPart, ...fraisPart]
|
||||
[...drPart, ...corrPart, ...rembPart, ...subPart, ...fraisPart, ...pePart]
|
||||
.filter(r => !mvFilter.projet || r._projet === mvFilter.projet)
|
||||
).map(r => ({ ...r, _soldeIndicatif: mvSoldeIndicatifByRowId.get(r.id) }));
|
||||
}, [mvRows, mvFilteredCorrections, mvFilteredRembMouvements, mvFilteredSouscriptionMouvements, mvFilteredFraisMouvements, mvFilter.type, mvFilter.projet, mvRembProjetById, mvSoldeIndicatifByRowId]);
|
||||
}, [mvRows, mvFilteredCorrections, mvFilteredRembMouvements, mvFilteredSouscriptionMouvements, mvFilteredFraisMouvements, mvFilteredPeMouvements, mvFilter.type, mvFilter.projet, mvFilter.investType, mvRembProjetById, mvSoldeIndicatifByRowId]);
|
||||
|
||||
const {
|
||||
pagedItems: mvPagedTableRows, page: mvPage, setPage: setMvPage,
|
||||
@@ -2759,6 +2805,14 @@ export default function Plateformes() {
|
||||
<option value="prelevement_local">Prélèvements étranger</option>
|
||||
</select>
|
||||
</div>
|
||||
<div style={{ flex: 1, minWidth: 130 }}>
|
||||
<label>Type d'investissement</label>
|
||||
<select value={mvFilter.investType} onChange={e => setMvFilter(f => ({ ...f, investType: e.target.value }))}>
|
||||
<option value="">Tous</option>
|
||||
<option value="crowdlending">Crowdlending</option>
|
||||
<option value="pe">Private Equity</option>
|
||||
</select>
|
||||
</div>
|
||||
<div style={{ flex: 2, minWidth: 160 }}>
|
||||
<label>Plateforme</label>
|
||||
<div style={{
|
||||
@@ -2866,7 +2920,7 @@ export default function Plateformes() {
|
||||
color: (isBonus || isCashback) ? 'var(--warning)' : isInteretsPlateforme ? 'var(--primary)' : 'var(--success)',
|
||||
}}>{isBonus ? 'bonus' : isCashback ? 'cashback' : isInteretsPlateforme ? 'intérêts plateforme' : 'remboursement'}</span>
|
||||
</td>
|
||||
<td className="text-muted">{r._projet || '—'}</td>
|
||||
<td className="text-muted">{r._investType === 'pe' && <span style={{ display: 'inline-block', fontSize: 10, fontWeight: 700, letterSpacing: '0.03em', padding: '1px 5px', borderRadius: 4, marginRight: 6, background: 'color-mix(in srgb, var(--primary) 15%, transparent)', color: 'var(--primary)' }} title="Mouvement Private Equity">PE</span>}{r._projet || '—'}</td>
|
||||
<td className="num success">
|
||||
+ {fmtEUR(r.montant)}
|
||||
</td>
|
||||
@@ -2896,7 +2950,7 @@ export default function Plateformes() {
|
||||
color,
|
||||
}}>{label}</span>
|
||||
</td>
|
||||
<td className="text-muted">{r._projet || '—'}</td>
|
||||
<td className="text-muted">{r._investType === 'pe' && <span style={{ display: 'inline-block', fontSize: 10, fontWeight: 700, letterSpacing: '0.03em', padding: '1px 5px', borderRadius: 4, marginRight: 6, background: 'color-mix(in srgb, var(--primary) 15%, transparent)', color: 'var(--primary)' }} title="Mouvement Private Equity">PE</span>}{r._projet || '—'}</td>
|
||||
<td className="num danger">
|
||||
− {fmtEUR(r.montant)}
|
||||
</td>
|
||||
@@ -2933,7 +2987,7 @@ export default function Plateformes() {
|
||||
}}>RÉINVEST.</span>
|
||||
)}
|
||||
</td>
|
||||
<td className="text-muted">{r._projet || '—'}</td>
|
||||
<td className="text-muted">{r._investType === 'pe' && <span style={{ display: 'inline-block', fontSize: 10, fontWeight: 700, letterSpacing: '0.03em', padding: '1px 5px', borderRadius: 4, marginRight: 6, background: 'color-mix(in srgb, var(--primary) 15%, transparent)', color: 'var(--primary)' }} title="Mouvement Private Equity">PE</span>}{r._projet || '—'}</td>
|
||||
<td className="num danger">
|
||||
− {fmtEUR(r.montant)}
|
||||
</td>
|
||||
@@ -2945,11 +2999,17 @@ export default function Plateformes() {
|
||||
if (r.type === 'frais') {
|
||||
const horsPM = r.mode_reglement === 'compte_courant';
|
||||
const rembLinked = r.mode_reglement === 'remboursement';
|
||||
// Frais PE "à la source" (19/09/26) : déjà inclus dans le montant de la
|
||||
// souscription (-800 € par ex.) — pas un débit supplémentaire du
|
||||
// porte-monnaie. Même traitement visuel que HORS PM/DÉDUIT REMB. (grisé,
|
||||
// badge, 0 d'effet sur le solde indicatif via signedMontant).
|
||||
const dejaInclusSouscr = r._investType === 'pe' && r.mode_reglement === 'source';
|
||||
const zeroImpact = horsPM || dejaInclusSouscr;
|
||||
return (
|
||||
<tr key={r.id}
|
||||
className={`dr-row${mvSelectedRow?.id === r.id ? ' dr-row-selected' : ''}`}
|
||||
onClick={() => setMvSelectedRow(mvSelectedRow?.id === r.id ? null : r)}
|
||||
style={{ cursor: 'pointer', opacity: horsPM ? 0.75 : 1 }}>
|
||||
style={{ cursor: 'pointer', opacity: zeroImpact ? 0.75 : 1 }}>
|
||||
<td>{fmtDate(r.date_operation)}</td>
|
||||
<td>{r.plateforme_nom}</td>
|
||||
{multiDetenteur && <td className="text-muted" style={{ fontSize: 'var(--fs-sm)' }}>{r.plateforme_detenteur_nom || '—'}</td>}
|
||||
@@ -2981,9 +3041,19 @@ export default function Plateformes() {
|
||||
verticalAlign: 'middle',
|
||||
}} title="Déjà déduit du montant net du remboursement — aucun effet supplémentaire sur le solde du porte-monnaie">DÉDUIT REMB.</span>
|
||||
)}
|
||||
{dejaInclusSouscr && (
|
||||
<span style={{
|
||||
marginLeft: 6,
|
||||
fontSize: 10, fontWeight: 600, letterSpacing: '0.03em',
|
||||
padding: '1px 5px', borderRadius: 3,
|
||||
background: 'var(--surface-2)',
|
||||
color: 'var(--text-muted)',
|
||||
verticalAlign: 'middle',
|
||||
}} title="Déjà inclus dans le montant de la souscription — aucun effet supplémentaire sur le solde du porte-monnaie">DÉDUIT SOUSCR.</span>
|
||||
)}
|
||||
</td>
|
||||
<td className="text-muted">{r._projet || '—'}</td>
|
||||
<td className={horsPM ? 'num text-muted' : 'num danger'}>
|
||||
<td className="text-muted">{r._investType === 'pe' && <span style={{ display: 'inline-block', fontSize: 10, fontWeight: 700, letterSpacing: '0.03em', padding: '1px 5px', borderRadius: 4, marginRight: 6, background: 'color-mix(in srgb, var(--primary) 15%, transparent)', color: 'var(--primary)' }} title="Mouvement Private Equity">PE</span>}{r._projet || '—'}</td>
|
||||
<td className={zeroImpact ? 'num text-muted' : 'num danger'}>
|
||||
− {fmtEUR(r.montant)}
|
||||
</td>
|
||||
<td className="num text-muted" style={{ fontStyle: 'italic' }}>{r._soldeIndicatif != null ? fmtEUR(r._soldeIndicatif) : '—'}</td>
|
||||
@@ -2992,6 +3062,12 @@ export default function Plateformes() {
|
||||
);
|
||||
}
|
||||
const isAuto = r.source === 'auto_remboursement';
|
||||
// Dépôt/retrait PE (19/09/26) : même rendu que les dépôts crowdlending,
|
||||
// mais SANS le menu d'édition/suppression — cette ligne vit dans la table
|
||||
// depots_retraits sous le workspace PE, et l'éditer/la supprimer depuis ce
|
||||
// menu (pensé pour le workspace crowdlending) réattribuerait sa gestion au
|
||||
// mauvais endroit. Modifiable uniquement depuis le workspace PE d'origine.
|
||||
const isForeign = r._investType === 'pe';
|
||||
return (
|
||||
<tr key={r.id}
|
||||
className={`dr-row${mvSelectedRow?.id === r.id ? ' dr-row-selected' : ''}`}
|
||||
@@ -3013,14 +3089,14 @@ export default function Plateformes() {
|
||||
}}>AUTO</span>
|
||||
)}
|
||||
</td>
|
||||
<td className="text-muted">{r._projet || '—'}</td>
|
||||
<td className="text-muted">{isForeign && <span style={{ display: 'inline-block', fontSize: 10, fontWeight: 700, letterSpacing: '0.03em', padding: '1px 5px', borderRadius: 4, marginRight: 6, background: 'color-mix(in srgb, var(--primary) 15%, transparent)', color: 'var(--primary)' }} title="Mouvement Private Equity">PE</span>}{r._projet || '—'}</td>
|
||||
<td className={`num ${r.type === 'depot' ? 'success' : 'danger'}`}
|
||||
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 && (
|
||||
{!isAuto && !isForeign && (
|
||||
<button
|
||||
style={{ background: 'none', border: 'none', cursor: 'pointer', padding: '2px 6px', borderRadius: 4, color: 'var(--text-muted)', lineHeight: 1, fontSize: 16 }}
|
||||
onClick={e => mvOpenRowMenu(e, r)}>⋮</button>
|
||||
|
||||
Reference in new issue
Block a user