Math.round(n * 100) / 100;
+const fmtRatePfu = (n) => n != null ? n.toFixed(1).replace('.', ',') + ' %' : '…';
+const getRatesForYearPe = (dateStr, pfuRates) => {
+ if (!dateStr || !pfuRates?.length) return null;
+ const year = parseInt(dateStr.slice(0, 4), 10);
+ return pfuRates.find(r => r.annee === year)
+ ?? pfuRates.reduce((best, r) => r.annee > best.annee ? r : best, pfuRates[0]);
+};
+
/** Formulaire d'ajout/édition — factorisé pour être identique dans les deux
* points d'entrée (page avec mouvements existants, et état vide). Champs
* strictement alignés sur le Schema de backend/src/routes/remboursements.js,
* limités aux types bonus_parrainage / bonus_plateforme / interets_plateforme
* (le type "normal", lié à un investissement, reste crowdlending uniquement). */
-function RemboursementForm({ form, setForm, plats, investisseurs, deals, multiDetenteur, err, runSubmit, submit, investisseurForPlat, editingId }) {
+function RemboursementForm({ form, setForm, plats, investisseurs, deals, multiDetenteur, err, runSubmit, submit, investisseurForPlat, editingId, pfuRates }) {
const isInterets = form.type === 'interets_plateforme';
// Deals PE de la plateforme sélectionnée (un remboursement bonus/intérêts
@@ -784,13 +823,54 @@ function RemboursementForm({ form, setForm, plats, investisseurs, deals, multiDe
// qu'un seul deal, ou aucun deal PE suivi pour l'instant.
const dealsForPlat = deals.filter(d => String(d.plateforme_id) === String(form.bonus_plateforme_id));
+ // ── Fiscalité (même logique que RembFormModal.jsx crowdlending) ────────
+ const currentPlat = plats.find(p => String(p.id) === String(form.bonus_plateforme_id));
+ const hasLocalTax = isInterets && currentPlat?.fiscalite === 'avec_fiscalite_locale' && currentPlat?.taux_fiscalite_locale;
+ // Pas d'équivalent fiscalite_override côté investissements_pe (pas de notion
+ // d'exonération par deal) : seule la fiscalité de la plateforme détermine le
+ // caractère indicatif, contrairement à crowdlending qui croise aussi
+ // investissement.fiscalite_override — cf. table investissements_pe (db/index.js).
+ const isIndicatif = isInterets && Boolean(currentPlat && currentPlat.fiscalite !== 'flat_tax');
+ const rates = isInterets ? getRatesForYearPe(form.date_remb, pfuRates) : null;
+ const labelPS = `Prélèvements sociaux${rates ? ` (${fmtRatePfu(rates.prelev_sociaux)})` : ''} (€)`;
+ const labelIR = `Impôt sur le revenu${rates ? ` (${fmtRatePfu(rates.impot_revenu)})` : ''} (€)`;
+
+ // Recalcule automatiquement taxe locale / prélèvements PFU quand un champ
+ // "moteur" change — l'utilisateur garde la main pour corriger prelev_sociaux/
+ // prelev_forfaitaire ensuite (ces deux champs ne redéclenchent pas ce recalcul,
+ // exactement comme setField()/onFormPatch() dans Remboursements.jsx).
+ const setFiscalField = (key, value) => {
+ const next = { ...form, [key]: value };
+ if (key === 'interets_bruts_avant_local') {
+ const taux = hasLocalTax ? (currentPlat.taux_fiscalite_locale || 0) : 0;
+ next.taxe_locale = round2(Number(value) * taux / 100);
+ next.interets_bruts = round2(Number(value) - next.taxe_locale);
+ }
+ if (key === 'taxe_locale') {
+ next.interets_bruts = round2(Number(next.interets_bruts_avant_local || 0) - Number(value));
+ }
+ if (['date_remb', 'interets_bruts', 'interets_bruts_avant_local', 'taxe_locale', 'frais_ttc'].includes(key)) {
+ const dateStr = key === 'date_remb' ? value : next.date_remb;
+ const base = round2((Number(next.interets_bruts) || 0) - (Number(next.frais_ttc) || 0));
+ const r = getRatesForYearPe(dateStr, pfuRates);
+ if (r) {
+ next.prelev_sociaux = round2(base * r.prelev_sociaux / 100);
+ next.prelev_forfaitaire = round2(base * r.impot_revenu / 100);
+ }
+ }
+ setForm(next);
+ };
+
+ const interetsNets = round2(
+ (Number(form.interets_bruts) || 0) - (Number(form.frais_ttc) || 0)
+ - (Number(form.prelev_sociaux) || 0) - (Number(form.prelev_forfaitaire) || 0)
+ );
+
const previewNet = () => {
if (isInterets) {
- const bruts = Number(form.interets_bruts) || 0;
- const frais = Number(form.frais_ttc) || 0;
- const ps = Number(form.prelev_sociaux) || 0;
- const pf = Number(form.prelev_forfaitaire) || 0;
- return Math.round((bruts - frais - ps - pf) * 100) / 100;
+ return isIndicatif
+ ? round2((Number(form.interets_bruts) || 0) - (Number(form.frais_ttc) || 0))
+ : interetsNets;
}
return Number(form.cashback) || 0;
};
@@ -842,29 +922,61 @@ function RemboursementForm({ form, setForm, plats, investisseurs, deals, multiDe
{isInterets && (
<>
+ {hasLocalTax && (
+
+ Intérêts bruts avant retenue locale ({currentPlat.taux_fiscalite_locale} %) (€)
+ setFiscalField('interets_bruts_avant_local', e.target.value)} />
+
+ )}
+ {hasLocalTax && (
+
+ Retenue à la source locale (€)
+ setFiscalField('taxe_locale', e.target.value)} />
+
+ )}
- Intérêts bruts (€) *
- setForm({ ...form, interets_bruts: e.target.value })} />
-
-
- Intérêts bruts avant taxe locale (€)
- setForm({ ...form, interets_bruts_avant_local: e.target.value })} />
-
-
- Taxe locale (€)
- setForm({ ...form, taxe_locale: e.target.value })} />
+ {hasLocalTax ? 'Intérêts bruts après retenue locale (€)' : 'Intérêts bruts (€) *'}
+ !hasLocalTax && setFiscalField('interets_bruts', e.target.value)} />
Frais TTC (€)
- setForm({ ...form, frais_ttc: e.target.value })} />
+ setFiscalField('frais_ttc', e.target.value)} />
+
+
+
+
+ Imposition{isIndicatif ? ' — indicatif' : ''}
+
- Prélèvements sociaux (€)
- setForm({ ...form, prelev_sociaux: e.target.value })} />
+ {labelPS}
+ !isIndicatif && setForm({ ...form, prelev_sociaux: e.target.value })} />
- Prélèvement forfaitaire (€)
- setForm({ ...form, prelev_forfaitaire: e.target.value })} />
+ {labelIR}
+ !isIndicatif && setForm({ ...form, prelev_forfaitaire: e.target.value })} />
+
+
+ Montant net perçu (€)
+
+
+
+ {isIndicatif
+ ? "L'imposition n'est pas déduite du montant net perçu (fiscalité appliquée à titre indicatif — plateforme hors flat tax)."
+ : "L'imposition est déduite du montant net perçu (intérêts bruts − frais − prélèvements)."}
>
)}
@@ -902,9 +1014,11 @@ function RemboursementForm({ form, setForm, plats, investisseurs, deals, multiDe
Notes
setForm({ ...form, notes: e.target.value })} />
-
- Net perçu estimé : {fmtEUR(previewNet())}
-
+ {!isInterets && (
+
+ Net perçu estimé : {fmtEUR(previewNet())}
+
+ )}
);