From 337d0c1a2c4a2caf4724c496d416459327d351da Mon Sep 17 00:00:00 2001 From: Olivier Date: Fri, 18 Sep 2026 08:16:01 +0200 Subject: [PATCH] Fix TVPI --- frontend/src/pages/InvestissementPeDetail.jsx | 17 ++++--- frontend/src/pages/InvestissementsPe.jsx | 49 ++++++++++--------- 2 files changed, 36 insertions(+), 30 deletions(-) diff --git a/frontend/src/pages/InvestissementPeDetail.jsx b/frontend/src/pages/InvestissementPeDetail.jsx index 8ecd260..84fb69e 100644 --- a/frontend/src/pages/InvestissementPeDetail.jsx +++ b/frontend/src/pages/InvestissementPeDetail.jsx @@ -11,7 +11,7 @@ import FraisFormModal from '../components/FraisFormModal.jsx'; import { FRAIS_CATEGORIE_LABELS } from '../components/MouvementsShared.jsx'; import { useAsyncAction } from '../hooks/useAsyncAction.js'; import { fmtEUR, fmtDate, fmtOctets, today } from '../utils/format.js'; -import { emptyDealForm, AUTO_VAL_NOTE } from './InvestissementsPe.jsx'; +import { emptyDealForm, AUTO_VAL_NOTE, AUTO_VAL_NOTE_LEGACY } from './InvestissementsPe.jsx'; /** * Fiche détaillée d'un deal Private Equity (chantier "fiche détail PE", 17/09/26 — demande @@ -117,7 +117,7 @@ export default function InvestissementPeDetail() { if (!deal) return; const entree = fraisOps.find(f => f.categorie === 'souscription'); const gestion = fraisOps.find(f => f.categorie === 'gestion' && f.origine === 'souscription'); - const autoVal = valorisations.find(v => v.notes === AUTO_VAL_NOTE); + const autoVal = valorisations.find(v => v.notes === AUTO_VAL_NOTE || v.notes === AUTO_VAL_NOTE_LEGACY); setForm({ ...emptyDealForm, plateforme_id: String(deal.plateforme_id || ''), @@ -178,15 +178,16 @@ export default function InvestissementPeDetail() { } catch { /* non bloquant */ } }; + // Toujours créée, même sans frais (= montant investi tel quel) — décision Olivier 18/09/26, + // cf. InvestissementsPe.jsx::syncValorisationInitiale (même logique, dupliquée ici pour la + // modale d'édition de la fiche détail). const syncValorisationInitiale = async (totalFraisSource, valorisationInitialeId) => { try { if (valorisationInitialeId) await api.del(`/investissements-pe/${id}/valorisations/${valorisationInitialeId}`); - if (totalFraisSource > 0) { - const initiale = Math.max(0, Math.round((Number(form.montant_investi) - totalFraisSource) * 100) / 100); - await api.post(`/investissements-pe/${id}/valorisations`, { - date_valorisation: form.date_souscription, valorisation_nette: initiale, notes: AUTO_VAL_NOTE, - }); - } + const initiale = Math.max(0, Math.round((Number(form.montant_investi) - totalFraisSource) * 100) / 100); + await api.post(`/investissements-pe/${id}/valorisations`, { + date_valorisation: form.date_souscription, valorisation_nette: initiale, notes: AUTO_VAL_NOTE, + }); } catch { /* non bloquant */ } }; diff --git a/frontend/src/pages/InvestissementsPe.jsx b/frontend/src/pages/InvestissementsPe.jsx index e99af17..777810d 100644 --- a/frontend/src/pages/InvestissementsPe.jsx +++ b/frontend/src/pages/InvestissementsPe.jsx @@ -41,19 +41,23 @@ export const emptyDealForm = { // tard depuis la page Frais PE ou la liste des investissements). frais_entree_mode_saisie: 'montant', frais_entree_valeur: '', frais_entree_mode_reglement: 'source', frais_entree_compte_id: '', frais_entree_id: null, frais_gestion_mode_saisie: 'montant', frais_gestion_valeur: '', frais_gestion_mode_reglement: 'source', frais_gestion_compte_id: '', frais_gestion_id: null, - // Id de la valorisation "initiale" auto-générée à la date de souscription pour refléter les - // frais réglés "à la source" (cf. AUTO_VAL_NOTE / submit ci-dessous) — null si aucune n'existe - // encore pour ce deal. Décision Olivier 17/09/26 : le montant investi (souscrit) ne bouge - // JAMAIS, c'est la valorisation qui encaisse immédiatement les frais d'entrée/de gestion à la - // date de souscription — jusqu'à ce qu'une vraie valorisation du fonds vienne la remplacer. + // Id de la valorisation "initiale" auto-générée à la date de souscription — null si aucune + // n'existe encore pour ce deal. Décision Olivier 17/09/26 (puis 18/09/26 — systématique, même + // sans frais) : le montant investi (souscrit) ne bouge JAMAIS, c'est cette valorisation qui + // encaisse immédiatement les frais d'entrée/de gestion éventuels à la date de souscription (ou + // simplement reprend le montant investi tel quel en l'absence de frais) — jusqu'à ce qu'une + // vraie valorisation du fonds vienne la remplacer. valorisation_initiale_id: null, }; -// Marqueur de la valorisation "initiale" auto-générée par l'étape "Frais" de la modale (cf. -// submit ci-dessous) — permet de la retrouver/remplacer sans toucher aux valorisations saisies -// manuellement par ailleurs (modale "Valorisations" annexe). -export const AUTO_VAL_NOTE = 'Valorisation initiale (frais de souscription)'; +// Marqueur de la valorisation "initiale" auto-générée à la souscription (cf. submit ci-dessous) +// — permet de la retrouver/remplacer sans toucher aux valorisations saisies manuellement par +// ailleurs (modale "Valorisations" annexe). Créée systématiquement depuis le 18/09/26 (avant +// cette date, uniquement quand le deal avait des frais réglés "à la source" — cf. +// AUTO_VAL_NOTE_LEGACY pour reconnaître ces anciennes lignes lors du pré-remplissage). +export const AUTO_VAL_NOTE = 'Valorisation initiale (souscription)'; +export const AUTO_VAL_NOTE_LEGACY = 'Valorisation initiale (frais de souscription)'; // Accès rapide "+ frais" depuis une ligne de la liste des investissements (chantier "frais PE", // 17/09/26, demande Olivier) — permet d'ajouter un frais complémentaire (gestion récurrente, @@ -309,7 +313,7 @@ export default function InvestissementsPe() { ]); const entree = fraisOps.find(f => f.categorie === 'souscription'); const gestion = fraisOps.find(f => f.categorie === 'gestion' && f.origine === 'souscription'); - const autoVal = valRows.find(v => v.notes === AUTO_VAL_NOTE); + const autoVal = valRows.find(v => v.notes === AUTO_VAL_NOTE || v.notes === AUTO_VAL_NOTE_LEGACY); setForm(f => ({ ...f, valorisation_initiale_id: autoVal ? autoVal.id : null, @@ -361,23 +365,24 @@ export default function InvestissementsPe() { // Gère la valorisation "initiale" auto-générée à la date de souscription (cf. AUTO_VAL_NOTE) : // le montant investi (souscrit) ne bouge jamais, c'est cette valorisation qui encaisse - // immédiatement les frais réglés "à la source" — décision Olivier 17/09/26. Comme l'API ne - // propose pas de PUT sur une valorisation, on supprime l'éventuelle ligne existante puis on - // recrée à neuf (couvre aussi le cas où la date de souscription a changé). Best-effort, jamais - // bloquant pour l'enregistrement principal du deal. + // immédiatement les frais réglés "à la source" quand il y en a. Créée systématiquement, + // même sans frais (= montant investi tel quel) — décision Olivier 18/09/26, pour que la courbe + // de valorisation et le TVPI démarrent dès la souscription plutôt que rester sans point tant + // qu'aucune valorisation manuelle n'a été saisie. Comme l'API ne propose pas de PUT sur une + // valorisation, on supprime l'éventuelle ligne existante puis on recrée à neuf (couvre aussi le + // cas où le montant investi, les frais ou la date de souscription ont changé). Best-effort, + // jamais bloquant pour l'enregistrement principal du deal. const syncValorisationInitiale = async (dealId, totalFraisSource, valorisationInitialeId) => { try { if (valorisationInitialeId) { await api.del(`/investissements-pe/${dealId}/valorisations/${valorisationInitialeId}`); } - if (totalFraisSource > 0) { - const initiale = Math.max(0, Math.round((Number(form.montant_investi) - totalFraisSource) * 100) / 100); - await api.post(`/investissements-pe/${dealId}/valorisations`, { - date_valorisation: form.date_souscription, - valorisation_nette: initiale, - notes: AUTO_VAL_NOTE, - }); - } + const initiale = Math.max(0, Math.round((Number(form.montant_investi) - totalFraisSource) * 100) / 100); + await api.post(`/investissements-pe/${dealId}/valorisations`, { + date_valorisation: form.date_souscription, + valorisation_nette: initiale, + notes: AUTO_VAL_NOTE, + }); } catch { /* non bloquant — rattrapable depuis la modale Valorisations */ } };