diff --git a/backend/src/db/index.js b/backend/src/db/index.js index 488d735..d0ce85a 100644 --- a/backend/src/db/index.js +++ b/backend/src/db/index.js @@ -2595,4 +2595,22 @@ if (!smtpCols.includes('documents_quota_count')) { } } +// ── Migration : frais liés à la plateforme ────────────────────────────────── +// Peu de plateformes appliquent des frais ; ce champ permet de capter ce +// comportement applicatif. Valeur définie au niveau du référentiel admin et +// répliquée sur les plateformes utilisateur à la création ; modifiable +// ensuite par l'utilisateur (Paramètres > Plateformes) comme les autres +// champs hérités (cf. HERITABLE_FIELDS / overridden_fields). Demande Olivier +// 30/08/26. +{ + const refColsFraisPlat = db.prepare('PRAGMA table_info(plateformes_referentiel)').all().map(c => c.name); + if (!refColsFraisPlat.includes('frais_plateforme')) { + db.exec("ALTER TABLE plateformes_referentiel ADD COLUMN frais_plateforme TEXT NOT NULL DEFAULT 'aucun'"); + } + const platColsFraisPlat = db.prepare('PRAGMA table_info(plateformes)').all().map(c => c.name); + if (!platColsFraisPlat.includes('frais_plateforme')) { + db.exec("ALTER TABLE plateformes ADD COLUMN frais_plateforme TEXT NOT NULL DEFAULT 'aucun'"); + } +} + export default db; diff --git a/backend/src/routes/plateformes.js b/backend/src/routes/plateformes.js index e33274f..99b97c1 100644 --- a/backend/src/routes/plateformes.js +++ b/backend/src/routes/plateformes.js @@ -55,6 +55,7 @@ const Schema = z.object({ taux_fiscalite_locale: z.number().min(0).max(100).nullable().optional(), type_produit_fiscal: z.enum(['2TT', '2TR']).default('2TT'), categorie_plateforme: z.enum(['psfp', 'psan']).default('psfp'), + frais_plateforme: z.enum(['aucun', 'entree_gestion_directe']).default('aucun'), methode_remboursement: z.enum(['portefeuille', 'compte_courant', 'choix_investisseur']).default('portefeuille'), investisseur_id: z.number().int().positive().nullable().optional(), date_ouverture: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).nullable().optional(), @@ -64,7 +65,7 @@ const Schema = z.object({ }); // Champs hérités du référentiel (comparés pour calculer overridden_fields) -const HERITABLE_FIELDS = ['nom', 'url', 'domiciliation', 'fiscalite', 'taux_fiscalite_locale', 'type_produit_fiscal', 'categorie_plateforme', 'logo_filename', 'icone_filename', 'methode_remboursement', 'type_pret_defaut', 'freq_interets_defaut']; +const HERITABLE_FIELDS = ['nom', 'url', 'domiciliation', 'fiscalite', 'taux_fiscalite_locale', 'type_produit_fiscal', 'categorie_plateforme', 'frais_plateforme', 'logo_filename', 'icone_filename', 'methode_remboursement', 'type_pret_defaut', 'freq_interets_defaut']; /** * Calcule overridden_fields : champs dont la valeur diffère du référentiel. @@ -223,7 +224,7 @@ function syncCategories(platId, catIds) { router.get('/referentiel-list', (_req, res) => { const rows = db.prepare(` SELECT pr.id, pr.nom, pr.url, pr.domiciliation, pr.fiscalite, - pr.taux_fiscalite_locale, pr.type_produit_fiscal, pr.categorie_plateforme, pr.logo_filename, pr.icone_filename, + pr.taux_fiscalite_locale, pr.type_produit_fiscal, pr.categorie_plateforme, pr.frais_plateforme, pr.logo_filename, pr.icone_filename, pr.methode_remboursement, pr.type_pret_defaut, pr.freq_interets_defaut FROM plateformes_referentiel pr ORDER BY pr.nom @@ -234,7 +235,7 @@ router.get('/referentiel-list', (_req, res) => { router.get('/', (req, res) => { const rows = db.prepare(` SELECT p.id, p.nom, p.url, p.notes, p.domiciliation, p.fiscalite, p.taux_fiscalite_locale, - p.type_produit_fiscal, p.categorie_plateforme, + p.type_produit_fiscal, p.categorie_plateforme, p.frais_plateforme, p.methode_remboursement, p.investisseur_id, p.date_ouverture, p.logo_filename, p.icone_filename, p.created_at, p.type_pret_defaut, p.freq_interets_defaut, p.referentiel_id, p.overridden_fields, @@ -284,15 +285,15 @@ router.post('/', (req, res, next) => { const r = db.prepare(` INSERT INTO plateformes - (user_id, nom, url, notes, domiciliation, fiscalite, taux_fiscalite_locale, type_produit_fiscal, categorie_plateforme, + (user_id, nom, url, notes, domiciliation, fiscalite, taux_fiscalite_locale, type_produit_fiscal, categorie_plateforme, frais_plateforme, methode_remboursement, investisseur_id, date_ouverture, type_pret_defaut, freq_interets_defaut, logo_filename, icone_filename, referentiel_id, overridden_fields) - VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) + VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) `).run( req.user.id, body.nom, body.url || null, body.notes || null, - body.domiciliation, fiscalite, taux, typeProduitFiscal, body.categorie_plateforme, + body.domiciliation, fiscalite, taux, typeProduitFiscal, body.categorie_plateforme, body.frais_plateforme, body.methode_remboursement, body.investisseur_id ?? null, body.date_ouverture || null, body.type_pret_defaut ?? null, body.freq_interets_defaut ?? null, logoFilename, iconeFilename, @@ -360,6 +361,7 @@ router.get('/export', (req, res, next) => { taux_fiscalite_locale: r.taux_fiscalite_locale, type_produit_fiscal: r.type_produit_fiscal, categorie_plateforme: r.categorie_plateforme, + frais_plateforme: r.frais_plateforme, methode_remboursement: r.methode_remboursement, date_ouverture: r.date_ouverture, type_pret_defaut: r.type_pret_defaut, @@ -429,6 +431,7 @@ router.get('/:id/export', (req, res, next) => { taux_fiscalite_locale: withAll.taux_fiscalite_locale, type_produit_fiscal: withAll.type_produit_fiscal, categorie_plateforme: withAll.categorie_plateforme, + frais_plateforme: withAll.frais_plateforme, methode_remboursement: withAll.methode_remboursement, date_ouverture: withAll.date_ouverture, type_pret_defaut: withAll.type_pret_defaut, freq_interets_defaut: withAll.freq_interets_defaut, @@ -558,7 +561,7 @@ router.post('/import-zip', zipUpload.single('file'), async (req, res, next) => { const fields = [ p.url || null, p.notes || null, p.domiciliation || 'france', fiscalite, taux, - p.type_produit_fiscal || '2TT', p.categorie_plateforme || 'psfp', p.methode_remboursement || 'portefeuille', + p.type_produit_fiscal || '2TT', p.categorie_plateforme || 'psfp', p.frais_plateforme || 'aucun', p.methode_remboursement || 'portefeuille', investisseurId, p.date_ouverture || null, p.type_pret_defaut || null, p.freq_interets_defaut || null, p.logo_filename || null, p.icone_filename || null, @@ -567,7 +570,7 @@ router.post('/import-zip', zipUpload.single('file'), async (req, res, next) => { if (existing) { db.prepare(` UPDATE plateformes SET url=?, notes=?, domiciliation=?, fiscalite=?, taux_fiscalite_locale=?, - type_produit_fiscal=?, categorie_plateforme=?, methode_remboursement=?, investisseur_id=?, date_ouverture=?, + type_produit_fiscal=?, categorie_plateforme=?, frais_plateforme=?, methode_remboursement=?, investisseur_id=?, date_ouverture=?, type_pret_defaut=?, freq_interets_defaut=?, logo_filename=?, icone_filename=? WHERE id=? AND user_id=? @@ -578,10 +581,10 @@ router.post('/import-zip', zipUpload.single('file'), async (req, res, next) => { const r = db.prepare(` INSERT INTO plateformes (user_id, nom, url, notes, domiciliation, fiscalite, taux_fiscalite_locale, - type_produit_fiscal, categorie_plateforme, methode_remboursement, investisseur_id, date_ouverture, + type_produit_fiscal, categorie_plateforme, frais_plateforme, methode_remboursement, investisseur_id, date_ouverture, type_pret_defaut, freq_interets_defaut, logo_filename, icone_filename, overridden_fields) - VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) + VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) `).run(req.user.id, p.nom, ...fields, '[]'); platId = r.lastInsertRowid; created++; @@ -633,6 +636,7 @@ router.put('/:id', (req, res, next) => { taux_fiscalite_locale: taux, type_produit_fiscal: typeProduitFiscal, categorie_plateforme: body.categorie_plateforme, + frais_plateforme: body.frais_plateforme, logo_filename: current.logo_filename, // logo géré séparément methode_remboursement: body.methode_remboursement, type_pret_defaut: body.type_pret_defaut ?? null, @@ -644,13 +648,13 @@ router.put('/:id', (req, res, next) => { const r = db.prepare(` UPDATE plateformes SET nom=?, url=?, notes=?, domiciliation=?, fiscalite=?, taux_fiscalite_locale=?, - type_produit_fiscal=?, categorie_plateforme=?, methode_remboursement=?, investisseur_id=?, date_ouverture=?, + type_produit_fiscal=?, categorie_plateforme=?, frais_plateforme=?, methode_remboursement=?, investisseur_id=?, date_ouverture=?, type_pret_defaut=?, freq_interets_defaut=?, overridden_fields=? WHERE id=? AND user_id=? `).run( body.nom, body.url || null, body.notes || null, body.domiciliation, fiscalite, taux, - typeProduitFiscal, body.categorie_plateforme, body.methode_remboursement, body.investisseur_id ?? null, body.date_ouverture || null, + typeProduitFiscal, body.categorie_plateforme, body.frais_plateforme, body.methode_remboursement, body.investisseur_id ?? null, body.date_ouverture || null, body.type_pret_defaut ?? null, body.freq_interets_defaut ?? null, JSON.stringify(overriddenFields), req.params.id, req.user.id @@ -785,12 +789,12 @@ router.post('/:id/reset', (req, res, next) => { db.prepare(` UPDATE plateformes SET nom=?, url=?, domiciliation=?, fiscalite=?, taux_fiscalite_locale=?, - type_produit_fiscal=?, categorie_plateforme=?, logo_filename=?, methode_remboursement=?, + type_produit_fiscal=?, categorie_plateforme=?, frais_plateforme=?, logo_filename=?, methode_remboursement=?, type_pret_defaut=?, freq_interets_defaut=?, overridden_fields='[]' WHERE id=? AND user_id=? `).run( ref.nom, ref.url ?? null, ref.domiciliation, ref.fiscalite, ref.taux_fiscalite_locale ?? null, - ref.type_produit_fiscal, ref.categorie_plateforme || 'psfp', ref.logo_filename ?? null, ref.methode_remboursement || 'portefeuille', + ref.type_produit_fiscal, ref.categorie_plateforme || 'psfp', ref.frais_plateforme || 'aucun', ref.logo_filename ?? null, ref.methode_remboursement || 'portefeuille', ref.type_pret_defaut ?? null, ref.freq_interets_defaut ?? null, req.params.id, req.user.id ); diff --git a/backend/src/routes/referentiel.js b/backend/src/routes/referentiel.js index 19610ca..4816633 100644 --- a/backend/src/routes/referentiel.js +++ b/backend/src/routes/referentiel.js @@ -78,6 +78,7 @@ const Schema = z.object({ taux_fiscalite_locale: z.number().min(0).max(100).nullable().optional(), type_produit_fiscal: z.enum(['2TT', '2TR']).default('2TT'), categorie_plateforme: z.enum(['psfp', 'psan']).default('psfp'), + frais_plateforme: z.enum(['aucun', 'entree_gestion_directe']).default('aucun'), methode_remboursement: z.enum(['portefeuille', 'compte_courant', 'choix_investisseur']).optional().default('portefeuille'), type_pret_defaut: z.enum(['in_fine', 'amortissable', 'differe']).nullable().optional(), freq_interets_defaut: z.enum(['mensuel', 'trimestriel', 'in_fine']).nullable().optional(), @@ -323,6 +324,7 @@ router.get('/export', (req, res, next) => { taux_fiscalite_locale: r.taux_fiscalite_locale, type_produit_fiscal: r.type_produit_fiscal, categorie_plateforme: r.categorie_plateforme, + frais_plateforme: r.frais_plateforme, description: r.description, annee_creation: r.annee_creation, investisseurs_types: r.investisseurs_types, @@ -419,6 +421,7 @@ router.get('/:id/export', (req, res, next) => { fiscalite: enriched.fiscalite, taux_fiscalite_locale: enriched.taux_fiscalite_locale, type_produit_fiscal: enriched.type_produit_fiscal, + frais_plateforme: enriched.frais_plateforme, description: enriched.description, annee_creation: enriched.annee_creation, investisseurs_types: enriched.investisseurs_types, @@ -525,7 +528,7 @@ router.post('/import-zip', zipUpload.single('file'), async (req, res, next) => { const fields = [ p.url || null, p.domiciliation || 'france', p.fiscalite || 'flat_tax', - p.taux_fiscalite_locale ?? null, p.type_produit_fiscal || '2TT', p.categorie_plateforme || 'psfp', + p.taux_fiscalite_locale ?? null, p.type_produit_fiscal || '2TT', p.categorie_plateforme || 'psfp', p.frais_plateforme || 'aucun', p.methode_remboursement || 'portefeuille', p.type_pret_defaut ?? null, p.freq_interets_defaut ?? null, p.description ?? null, p.annee_creation ?? null, p.investisseurs_types ?? null, @@ -543,7 +546,7 @@ router.post('/import-zip', zipUpload.single('file'), async (req, res, next) => { if (existing) { db.prepare(` UPDATE plateformes_referentiel SET - url=?, domiciliation=?, fiscalite=?, taux_fiscalite_locale=?, type_produit_fiscal=?, categorie_plateforme=?, + url=?, domiciliation=?, fiscalite=?, taux_fiscalite_locale=?, type_produit_fiscal=?, categorie_plateforme=?, frais_plateforme=?, methode_remboursement=?, type_pret_defaut=?, freq_interets_defaut=?, description=?, annee_creation=?, investisseurs_types=?, regulateur=?, numero_licence=?, is_regule=?, @@ -561,7 +564,7 @@ router.post('/import-zip', zipUpload.single('file'), async (req, res, next) => { } else { const r = db.prepare(` INSERT INTO plateformes_referentiel - (nom, url, domiciliation, fiscalite, taux_fiscalite_locale, type_produit_fiscal, categorie_plateforme, + (nom, url, domiciliation, fiscalite, taux_fiscalite_locale, type_produit_fiscal, categorie_plateforme, frais_plateforme, methode_remboursement, type_pret_defaut, freq_interets_defaut, description, annee_creation, investisseurs_types, regulateur, numero_licence, is_regule, @@ -571,7 +574,7 @@ router.post('/import-zip', zipUpload.single('file'), async (req, res, next) => { garantie_rachat, statistiques_publiques, bonus_inscription, marche_secondaire, investissement_auto, url_trustpilot, url_linkedin, logo_filename, icone_filename, updated_at) - VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,datetime('now')) + VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,datetime('now')) `).run(p.nom, ...fields); refId = r.lastInsertRowid; created++; @@ -670,7 +673,7 @@ router.post('/', (req, res, next) => { const r = db.prepare(` INSERT INTO plateformes_referentiel - (nom, url, domiciliation, fiscalite, taux_fiscalite_locale, type_produit_fiscal, categorie_plateforme, + (nom, url, domiciliation, fiscalite, taux_fiscalite_locale, type_produit_fiscal, categorie_plateforme, frais_plateforme, methode_remboursement, type_pret_defaut, freq_interets_defaut, logo_filename, description, annee_creation, investisseurs_types, @@ -682,7 +685,7 @@ router.post('/', (req, res, next) => { VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?, datetime('now')) `).run( data.nom, data.url || null, data.domiciliation, data.fiscalite, - data.taux_fiscalite_locale ?? null, data.type_produit_fiscal, data.categorie_plateforme, + data.taux_fiscalite_locale ?? null, data.type_produit_fiscal, data.categorie_plateforme, data.frais_plateforme, data.methode_remboursement || 'portefeuille', data.type_pret_defaut ?? null, data.freq_interets_defaut ?? null, data.logo_filename ?? null, data.description ?? null, data.annee_creation ?? null, data.investisseurs_types ?? null, @@ -724,7 +727,7 @@ router.put('/:id', (req, res, next) => { db.prepare(` UPDATE plateformes_referentiel SET nom=?, url=?, domiciliation=?, fiscalite=?, taux_fiscalite_locale=?, - type_produit_fiscal=?, categorie_plateforme=?, methode_remboursement=?, type_pret_defaut=?, freq_interets_defaut=?, + type_produit_fiscal=?, categorie_plateforme=?, frais_plateforme=?, methode_remboursement=?, type_pret_defaut=?, freq_interets_defaut=?, logo_filename=?, description=?, annee_creation=?, investisseurs_types=?, regulateur=?, numero_licence=?, is_regule=?, @@ -737,7 +740,7 @@ router.put('/:id', (req, res, next) => { WHERE id=? `).run( data.nom, data.url || null, data.domiciliation, data.fiscalite, - data.taux_fiscalite_locale ?? null, data.type_produit_fiscal, data.categorie_plateforme, + data.taux_fiscalite_locale ?? null, data.type_produit_fiscal, data.categorie_plateforme, data.frais_plateforme, data.methode_remboursement || 'portefeuille', data.type_pret_defaut ?? null, data.freq_interets_defaut ?? null, data.logo_filename ?? null, data.description ?? null, data.annee_creation ?? null, data.investisseurs_types ?? null, @@ -816,7 +819,7 @@ router.post('/:id/push', (req, res, next) => { const force = req.body?.force === true; // Champs scalaires poussables - const PUSHABLE = ['nom', 'url', 'domiciliation', 'fiscalite', 'taux_fiscalite_locale', 'type_produit_fiscal', 'categorie_plateforme', 'logo_filename', 'methode_remboursement', 'type_pret_defaut', 'freq_interets_defaut']; + const PUSHABLE = ['nom', 'url', 'domiciliation', 'fiscalite', 'taux_fiscalite_locale', 'type_produit_fiscal', 'categorie_plateforme', 'frais_plateforme', 'logo_filename', 'methode_remboursement', 'type_pret_defaut', 'freq_interets_defaut']; // Catégories/secteurs du référentiel const refCatIds = db.prepare( diff --git a/frontend/src/components/PlateformeFormModal.jsx b/frontend/src/components/PlateformeFormModal.jsx new file mode 100644 index 0000000..e1197c9 --- /dev/null +++ b/frontend/src/components/PlateformeFormModal.jsx @@ -0,0 +1,109 @@ +import { useRef, useState } from 'react'; +import Modal from './Modal.jsx'; + +const STEP_TITLES = [ + "Informations générales sur la plateforme", + 'Fiscalité', + 'Fonctionnement & frais', + 'Classification', + 'Autres', +]; + +/** + * PlateformeFormModal — assistant partagé de création/édition d'une plateforme. + * + * Utilisé à la fois par le référentiel Admin (AdminPlateformes.jsx, ex-RefModal) et + * par Paramètres > Mes plateformes (PlateformesSection.jsx, ex-PlatForm). Sur le + * modèle d'InvestissementFormModal : indicateur d'étape (barre segmentée + « Étape + * X : titre »), pied de modale Annuler/Précédent/Suivant/Créer ou Enregistrer. + * + * Ce composant est volontairement une simple "coquille" d'assistant : il ne connaît + * ni les champs ni leur logique (cascades domiciliation/fiscalité, CountrySelect, + * upload logo, InvSelect catégories/secteurs...) — chaque appelant continue de + * posséder son propre state, ses effets de bord, et fournit le contenu de chaque + * étape via les props step1..step5. Ça permet de partager la mécanique de + * l'assistant sans dupliquer ni réécrire les composants déjà en place. + * + * Comme InvestissementFormModal, seule l'étape affichée est montée dans le DOM + * (jamais display:none) afin que "Suivant" (reportValidity() sur le
) ne + * valide que les champs réellement visibles. + * + * Props : + * title, onClose — titre de la modale, fermeture + * onSubmit — handler de soumission finale (form onSubmit ET + * onClick du bouton Créer/Enregistrer — doit donc + * appeler e.preventDefault() en premier, comme les + * handlers existants) + * saving, err — état de sauvegarde / message d'erreur + * submitLabel — libellé du bouton final ('Créer' | 'Enregistrer') + * onDelete — optionnel : si fourni, affiche un bouton + * "Supprimer" en bas à gauche, visible sur les 5 étapes + * width — largeur de la modale (défaut 640) + * step1..step5 — contenu JSX de chaque étape (l'appelant + * enveloppe généralement ses champs dans une + * div className="form-grid") + */ +export default function PlateformeFormModal({ + title, + onClose, + onSubmit, + saving = false, + err = null, + submitLabel = 'Enregistrer', + onDelete = null, + width = 640, + step1, step2, step3, step4, step5, +}) { + const formRef = useRef(null); + const [step, setStep] = useState(1); + const totalSteps = STEP_TITLES.length; + const steps = [step1, step2, step3, step4, step5]; + + const goNext = () => { + if (formRef.current && !formRef.current.reportValidity()) return; + setStep(s => Math.min(totalSteps, s + 1)); + }; + const goPrev = () => setStep(s => Math.max(1, s - 1)); + + return ( + +
+ {onDelete && ( + + )} +
+ {step === 1 && } + {step > 1 && } + {step < totalSteps && } + {step === totalSteps && } + + } + > + {/* ── Indicateur d'étape ── */} +
+
+ Étape {step} : {STEP_TITLES[step - 1]} +
+
+ {STEP_TITLES.map((_, i) => ( +
+ ))} +
+
+ + + {err &&
{err}
} + {steps.map((content, i) => step === i + 1 &&
{content}
)} + + + ); +} diff --git a/frontend/src/pages/AdminPlateformes.jsx b/frontend/src/pages/AdminPlateformes.jsx index 408a683..ca4a472 100644 --- a/frontend/src/pages/AdminPlateformes.jsx +++ b/frontend/src/pages/AdminPlateformes.jsx @@ -1,9 +1,9 @@ import { useState, useEffect, useCallback, useRef, useMemo } from 'react'; import { useLocation, useNavigate } from 'react-router-dom'; import { api } from '../api.js'; -import InvSelect from '../components/InvSelect.jsx'; import ConfirmModal from '../components/ConfirmModal.jsx'; import Modal from '../components/Modal.jsx'; +import PlateformeFormModal from '../components/PlateformeFormModal.jsx'; import ResultBanner from '../components/ResultBanner.jsx'; import CountrySelect, { COUNTRIES, FlagIcon } from '../components/CountrySelect.jsx'; import { usePagination } from '../hooks/usePagination.js'; @@ -45,7 +45,8 @@ function IconUpload() { const countryLabel = code => COUNTRIES.find(c => c.code === code)?.name ?? code ?? '—'; const FISCALITE_LABELS = { flat_tax: 'Flat tax (30%)', sans_fiscalite_locale: 'Sans fiscalité locale', avec_fiscalite_locale: 'Avec fiscalité locale' }; const CATEGORIE_PLATEFORME_LABELS = { psfp: 'Prestataire de Services de Financement Participatif', psan: 'Prestataire de Services sur Actifs Numériques' }; -const EMPTY_REF = { nom: '', url: '', domiciliation: 'france', fiscalite: 'flat_tax', taux_fiscalite_locale: '', type_produit_fiscal: '2TT', categorie_plateforme: 'psfp', methode_remboursement: 'portefeuille', type_pret_defaut: '', freq_interets_defaut: '', description: '', categories: [], categories_inv_ids: [], secteurs_inv_ids: [] }; +const FRAIS_PLATEFORME_LABELS = { aucun: 'Aucun frais', entree_gestion_directe: "Frais d'entrée + Frais de gestion (Prélèvement direct)" }; +const EMPTY_REF = { nom: '', url: '', domiciliation: 'france', fiscalite: 'flat_tax', taux_fiscalite_locale: '', type_produit_fiscal: '2TT', categorie_plateforme: 'psfp', frais_plateforme: 'aucun', methode_remboursement: 'portefeuille', type_pret_defaut: '', freq_interets_defaut: '', description: '', categories: [], categories_inv_ids: [], secteurs_inv_ids: [] }; /* ── Modal création / édition référentiel ─────────────────────── */ function RefModal({ row, onClose, onSaved }) { @@ -61,6 +62,7 @@ function RefModal({ row, onClose, onSaved }) { taux_fiscalite_locale: row.taux_fiscalite_locale != null ? String(row.taux_fiscalite_locale) : '', type_produit_fiscal: row.type_produit_fiscal || '2TT', categorie_plateforme: row.categorie_plateforme || 'psfp', + frais_plateforme: row.frais_plateforme || 'aucun', methode_remboursement: row.methode_remboursement || 'portefeuille', type_pret_defaut: row.type_pret_defaut || '', freq_interets_defaut: row.freq_interets_defaut || '', @@ -96,6 +98,7 @@ function RefModal({ row, onClose, onSaved }) { ? Number(form.taux_fiscalite_locale) : null, type_produit_fiscal: form.type_produit_fiscal, categorie_plateforme: form.categorie_plateforme || 'psfp', + frais_plateforme: form.frais_plateforme || 'aucun', methode_remboursement: form.methode_remboursement || 'portefeuille', type_pret_defaut: form.type_pret_defaut || null, freq_interets_defaut: form.freq_interets_defaut || null, @@ -143,118 +146,165 @@ function RefModal({ row, onClose, onSaved }) { finally { setSaving(false); } }; - return ( - - - - }> -
-
-
set('nom', e.target.value)} placeholder="Ex : October, Lendosphere…" />
-
set('url', e.target.value)} placeholder="https://…" />
-
-
- - set('domiciliation', code)} - showCode - /> -
-
- - {form.domiciliation === 'FR' - ? - : } -
-
- {fiscalite === 'avec_fiscalite_locale' && ( -
- set('taux_fiscalite_locale', e.target.value)} placeholder="Ex : 15" />
- )} -
- - -
-
- - -
-
-
- - -
-
- - -
-
- - -
-
-
-