Modification modale plateforme
This commit is contained in:
1 parent
0c99887846
commit
9ea078302d
7 files changed
+437
-222
No files matched your search
@@ -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;
|
export default db;
|
||||||
@@ -55,6 +55,7 @@ const Schema = z.object({
|
|||||||
taux_fiscalite_locale: z.number().min(0).max(100).nullable().optional(),
|
taux_fiscalite_locale: z.number().min(0).max(100).nullable().optional(),
|
||||||
type_produit_fiscal: z.enum(['2TT', '2TR']).default('2TT'),
|
type_produit_fiscal: z.enum(['2TT', '2TR']).default('2TT'),
|
||||||
categorie_plateforme: z.enum(['psfp', 'psan']).default('psfp'),
|
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'),
|
methode_remboursement: z.enum(['portefeuille', 'compte_courant', 'choix_investisseur']).default('portefeuille'),
|
||||||
investisseur_id: z.number().int().positive().nullable().optional(),
|
investisseur_id: z.number().int().positive().nullable().optional(),
|
||||||
date_ouverture: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).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)
|
// 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.
|
* 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) => {
|
router.get('/referentiel-list', (_req, res) => {
|
||||||
const rows = db.prepare(`
|
const rows = db.prepare(`
|
||||||
SELECT pr.id, pr.nom, pr.url, pr.domiciliation, pr.fiscalite,
|
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
|
pr.methode_remboursement, pr.type_pret_defaut, pr.freq_interets_defaut
|
||||||
FROM plateformes_referentiel pr
|
FROM plateformes_referentiel pr
|
||||||
ORDER BY pr.nom
|
ORDER BY pr.nom
|
||||||
@@ -234,7 +235,7 @@ router.get('/referentiel-list', (_req, res) => {
|
|||||||
router.get('/', (req, res) => {
|
router.get('/', (req, res) => {
|
||||||
const rows = db.prepare(`
|
const rows = db.prepare(`
|
||||||
SELECT p.id, p.nom, p.url, p.notes, p.domiciliation, p.fiscalite, p.taux_fiscalite_locale,
|
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.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.type_pret_defaut, p.freq_interets_defaut,
|
||||||
p.referentiel_id, p.overridden_fields,
|
p.referentiel_id, p.overridden_fields,
|
||||||
@@ -284,15 +285,15 @@ router.post('/', (req, res, next) => {
|
|||||||
|
|
||||||
const r = db.prepare(`
|
const r = db.prepare(`
|
||||||
INSERT INTO plateformes
|
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,
|
methode_remboursement, investisseur_id, date_ouverture,
|
||||||
type_pret_defaut, freq_interets_defaut,
|
type_pret_defaut, freq_interets_defaut,
|
||||||
logo_filename, icone_filename,
|
logo_filename, icone_filename,
|
||||||
referentiel_id, overridden_fields)
|
referentiel_id, overridden_fields)
|
||||||
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
||||||
`).run(
|
`).run(
|
||||||
req.user.id, body.nom, body.url || null, body.notes || null,
|
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.methode_remboursement, body.investisseur_id ?? null, body.date_ouverture || null,
|
||||||
body.type_pret_defaut ?? null, body.freq_interets_defaut ?? null,
|
body.type_pret_defaut ?? null, body.freq_interets_defaut ?? null,
|
||||||
logoFilename, iconeFilename,
|
logoFilename, iconeFilename,
|
||||||
@@ -360,6 +361,7 @@ router.get('/export', (req, res, next) => {
|
|||||||
taux_fiscalite_locale: r.taux_fiscalite_locale,
|
taux_fiscalite_locale: r.taux_fiscalite_locale,
|
||||||
type_produit_fiscal: r.type_produit_fiscal,
|
type_produit_fiscal: r.type_produit_fiscal,
|
||||||
categorie_plateforme: r.categorie_plateforme,
|
categorie_plateforme: r.categorie_plateforme,
|
||||||
|
frais_plateforme: r.frais_plateforme,
|
||||||
methode_remboursement: r.methode_remboursement,
|
methode_remboursement: r.methode_remboursement,
|
||||||
date_ouverture: r.date_ouverture,
|
date_ouverture: r.date_ouverture,
|
||||||
type_pret_defaut: r.type_pret_defaut,
|
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,
|
taux_fiscalite_locale: withAll.taux_fiscalite_locale,
|
||||||
type_produit_fiscal: withAll.type_produit_fiscal,
|
type_produit_fiscal: withAll.type_produit_fiscal,
|
||||||
categorie_plateforme: withAll.categorie_plateforme,
|
categorie_plateforme: withAll.categorie_plateforme,
|
||||||
|
frais_plateforme: withAll.frais_plateforme,
|
||||||
methode_remboursement: withAll.methode_remboursement,
|
methode_remboursement: withAll.methode_remboursement,
|
||||||
date_ouverture: withAll.date_ouverture,
|
date_ouverture: withAll.date_ouverture,
|
||||||
type_pret_defaut: withAll.type_pret_defaut, freq_interets_defaut: withAll.freq_interets_defaut,
|
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 = [
|
const fields = [
|
||||||
p.url || null, p.notes || null,
|
p.url || null, p.notes || null,
|
||||||
p.domiciliation || 'france', fiscalite, taux,
|
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,
|
investisseurId, p.date_ouverture || null,
|
||||||
p.type_pret_defaut || null, p.freq_interets_defaut || null,
|
p.type_pret_defaut || null, p.freq_interets_defaut || null,
|
||||||
p.logo_filename || null, p.icone_filename || 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) {
|
if (existing) {
|
||||||
db.prepare(`
|
db.prepare(`
|
||||||
UPDATE plateformes SET url=?, notes=?, domiciliation=?, fiscalite=?, taux_fiscalite_locale=?,
|
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=?,
|
type_pret_defaut=?, freq_interets_defaut=?,
|
||||||
logo_filename=?, icone_filename=?
|
logo_filename=?, icone_filename=?
|
||||||
WHERE id=? AND user_id=?
|
WHERE id=? AND user_id=?
|
||||||
@@ -578,10 +581,10 @@ router.post('/import-zip', zipUpload.single('file'), async (req, res, next) => {
|
|||||||
const r = db.prepare(`
|
const r = db.prepare(`
|
||||||
INSERT INTO plateformes
|
INSERT INTO plateformes
|
||||||
(user_id, nom, url, notes, domiciliation, fiscalite, taux_fiscalite_locale,
|
(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,
|
type_pret_defaut, freq_interets_defaut,
|
||||||
logo_filename, icone_filename, overridden_fields)
|
logo_filename, icone_filename, overridden_fields)
|
||||||
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
||||||
`).run(req.user.id, p.nom, ...fields, '[]');
|
`).run(req.user.id, p.nom, ...fields, '[]');
|
||||||
platId = r.lastInsertRowid;
|
platId = r.lastInsertRowid;
|
||||||
created++;
|
created++;
|
||||||
@@ -633,6 +636,7 @@ router.put('/:id', (req, res, next) => {
|
|||||||
taux_fiscalite_locale: taux,
|
taux_fiscalite_locale: taux,
|
||||||
type_produit_fiscal: typeProduitFiscal,
|
type_produit_fiscal: typeProduitFiscal,
|
||||||
categorie_plateforme: body.categorie_plateforme,
|
categorie_plateforme: body.categorie_plateforme,
|
||||||
|
frais_plateforme: body.frais_plateforme,
|
||||||
logo_filename: current.logo_filename, // logo géré séparément
|
logo_filename: current.logo_filename, // logo géré séparément
|
||||||
methode_remboursement: body.methode_remboursement,
|
methode_remboursement: body.methode_remboursement,
|
||||||
type_pret_defaut: body.type_pret_defaut ?? null,
|
type_pret_defaut: body.type_pret_defaut ?? null,
|
||||||
@@ -644,13 +648,13 @@ router.put('/:id', (req, res, next) => {
|
|||||||
const r = db.prepare(`
|
const r = db.prepare(`
|
||||||
UPDATE plateformes
|
UPDATE plateformes
|
||||||
SET nom=?, url=?, notes=?, domiciliation=?, fiscalite=?, taux_fiscalite_locale=?,
|
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=?,
|
type_pret_defaut=?, freq_interets_defaut=?,
|
||||||
overridden_fields=?
|
overridden_fields=?
|
||||||
WHERE id=? AND user_id=?
|
WHERE id=? AND user_id=?
|
||||||
`).run(
|
`).run(
|
||||||
body.nom, body.url || null, body.notes || null, body.domiciliation, fiscalite, taux,
|
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,
|
body.type_pret_defaut ?? null, body.freq_interets_defaut ?? null,
|
||||||
JSON.stringify(overriddenFields),
|
JSON.stringify(overriddenFields),
|
||||||
req.params.id, req.user.id
|
req.params.id, req.user.id
|
||||||
@@ -785,12 +789,12 @@ router.post('/:id/reset', (req, res, next) => {
|
|||||||
db.prepare(`
|
db.prepare(`
|
||||||
UPDATE plateformes
|
UPDATE plateformes
|
||||||
SET nom=?, url=?, domiciliation=?, fiscalite=?, taux_fiscalite_locale=?,
|
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='[]'
|
type_pret_defaut=?, freq_interets_defaut=?, overridden_fields='[]'
|
||||||
WHERE id=? AND user_id=?
|
WHERE id=? AND user_id=?
|
||||||
`).run(
|
`).run(
|
||||||
ref.nom, ref.url ?? null, ref.domiciliation, ref.fiscalite, ref.taux_fiscalite_locale ?? null,
|
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,
|
ref.type_pret_defaut ?? null, ref.freq_interets_defaut ?? null,
|
||||||
req.params.id, req.user.id
|
req.params.id, req.user.id
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -78,6 +78,7 @@ const Schema = z.object({
|
|||||||
taux_fiscalite_locale: z.number().min(0).max(100).nullable().optional(),
|
taux_fiscalite_locale: z.number().min(0).max(100).nullable().optional(),
|
||||||
type_produit_fiscal: z.enum(['2TT', '2TR']).default('2TT'),
|
type_produit_fiscal: z.enum(['2TT', '2TR']).default('2TT'),
|
||||||
categorie_plateforme: z.enum(['psfp', 'psan']).default('psfp'),
|
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'),
|
methode_remboursement: z.enum(['portefeuille', 'compte_courant', 'choix_investisseur']).optional().default('portefeuille'),
|
||||||
type_pret_defaut: z.enum(['in_fine', 'amortissable', 'differe']).nullable().optional(),
|
type_pret_defaut: z.enum(['in_fine', 'amortissable', 'differe']).nullable().optional(),
|
||||||
freq_interets_defaut: z.enum(['mensuel', 'trimestriel', 'in_fine']).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,
|
taux_fiscalite_locale: r.taux_fiscalite_locale,
|
||||||
type_produit_fiscal: r.type_produit_fiscal,
|
type_produit_fiscal: r.type_produit_fiscal,
|
||||||
categorie_plateforme: r.categorie_plateforme,
|
categorie_plateforme: r.categorie_plateforme,
|
||||||
|
frais_plateforme: r.frais_plateforme,
|
||||||
description: r.description,
|
description: r.description,
|
||||||
annee_creation: r.annee_creation,
|
annee_creation: r.annee_creation,
|
||||||
investisseurs_types: r.investisseurs_types,
|
investisseurs_types: r.investisseurs_types,
|
||||||
@@ -419,6 +421,7 @@ router.get('/:id/export', (req, res, next) => {
|
|||||||
fiscalite: enriched.fiscalite,
|
fiscalite: enriched.fiscalite,
|
||||||
taux_fiscalite_locale: enriched.taux_fiscalite_locale,
|
taux_fiscalite_locale: enriched.taux_fiscalite_locale,
|
||||||
type_produit_fiscal: enriched.type_produit_fiscal,
|
type_produit_fiscal: enriched.type_produit_fiscal,
|
||||||
|
frais_plateforme: enriched.frais_plateforme,
|
||||||
description: enriched.description,
|
description: enriched.description,
|
||||||
annee_creation: enriched.annee_creation,
|
annee_creation: enriched.annee_creation,
|
||||||
investisseurs_types: enriched.investisseurs_types,
|
investisseurs_types: enriched.investisseurs_types,
|
||||||
@@ -525,7 +528,7 @@ router.post('/import-zip', zipUpload.single('file'), async (req, res, next) => {
|
|||||||
|
|
||||||
const fields = [
|
const fields = [
|
||||||
p.url || null, p.domiciliation || 'france', p.fiscalite || 'flat_tax',
|
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.methode_remboursement || 'portefeuille', p.type_pret_defaut ?? null, p.freq_interets_defaut ?? null,
|
||||||
p.description ?? null, p.annee_creation ?? null,
|
p.description ?? null, p.annee_creation ?? null,
|
||||||
p.investisseurs_types ?? null,
|
p.investisseurs_types ?? null,
|
||||||
@@ -543,7 +546,7 @@ router.post('/import-zip', zipUpload.single('file'), async (req, res, next) => {
|
|||||||
if (existing) {
|
if (existing) {
|
||||||
db.prepare(`
|
db.prepare(`
|
||||||
UPDATE plateformes_referentiel SET
|
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=?,
|
methode_remboursement=?, type_pret_defaut=?, freq_interets_defaut=?,
|
||||||
description=?, annee_creation=?, investisseurs_types=?,
|
description=?, annee_creation=?, investisseurs_types=?,
|
||||||
regulateur=?, numero_licence=?, is_regule=?,
|
regulateur=?, numero_licence=?, is_regule=?,
|
||||||
@@ -561,7 +564,7 @@ router.post('/import-zip', zipUpload.single('file'), async (req, res, next) => {
|
|||||||
} else {
|
} else {
|
||||||
const r = db.prepare(`
|
const r = db.prepare(`
|
||||||
INSERT INTO plateformes_referentiel
|
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,
|
methode_remboursement, type_pret_defaut, freq_interets_defaut,
|
||||||
description, annee_creation, investisseurs_types,
|
description, annee_creation, investisseurs_types,
|
||||||
regulateur, numero_licence, is_regule,
|
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,
|
garantie_rachat, statistiques_publiques, bonus_inscription, marche_secondaire, investissement_auto,
|
||||||
url_trustpilot, url_linkedin,
|
url_trustpilot, url_linkedin,
|
||||||
logo_filename, icone_filename, updated_at)
|
logo_filename, icone_filename, updated_at)
|
||||||
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,datetime('now'))
|
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,datetime('now'))
|
||||||
`).run(p.nom, ...fields);
|
`).run(p.nom, ...fields);
|
||||||
refId = r.lastInsertRowid;
|
refId = r.lastInsertRowid;
|
||||||
created++;
|
created++;
|
||||||
@@ -670,7 +673,7 @@ router.post('/', (req, res, next) => {
|
|||||||
|
|
||||||
const r = db.prepare(`
|
const r = db.prepare(`
|
||||||
INSERT INTO plateformes_referentiel
|
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,
|
methode_remboursement, type_pret_defaut, freq_interets_defaut,
|
||||||
logo_filename, description,
|
logo_filename, description,
|
||||||
annee_creation, investisseurs_types,
|
annee_creation, investisseurs_types,
|
||||||
@@ -682,7 +685,7 @@ router.post('/', (req, res, next) => {
|
|||||||
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?, datetime('now'))
|
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?, datetime('now'))
|
||||||
`).run(
|
`).run(
|
||||||
data.nom, data.url || null, data.domiciliation, data.fiscalite,
|
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.methode_remboursement || 'portefeuille', data.type_pret_defaut ?? null, data.freq_interets_defaut ?? null,
|
||||||
data.logo_filename ?? null, data.description ?? null,
|
data.logo_filename ?? null, data.description ?? null,
|
||||||
data.annee_creation ?? null, data.investisseurs_types ?? null,
|
data.annee_creation ?? null, data.investisseurs_types ?? null,
|
||||||
@@ -724,7 +727,7 @@ router.put('/:id', (req, res, next) => {
|
|||||||
db.prepare(`
|
db.prepare(`
|
||||||
UPDATE plateformes_referentiel
|
UPDATE plateformes_referentiel
|
||||||
SET nom=?, url=?, domiciliation=?, fiscalite=?, taux_fiscalite_locale=?,
|
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=?,
|
logo_filename=?, description=?,
|
||||||
annee_creation=?, investisseurs_types=?,
|
annee_creation=?, investisseurs_types=?,
|
||||||
regulateur=?, numero_licence=?, is_regule=?,
|
regulateur=?, numero_licence=?, is_regule=?,
|
||||||
@@ -737,7 +740,7 @@ router.put('/:id', (req, res, next) => {
|
|||||||
WHERE id=?
|
WHERE id=?
|
||||||
`).run(
|
`).run(
|
||||||
data.nom, data.url || null, data.domiciliation, data.fiscalite,
|
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.methode_remboursement || 'portefeuille', data.type_pret_defaut ?? null, data.freq_interets_defaut ?? null,
|
||||||
data.logo_filename ?? null, data.description ?? null,
|
data.logo_filename ?? null, data.description ?? null,
|
||||||
data.annee_creation ?? null, data.investisseurs_types ?? 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;
|
const force = req.body?.force === true;
|
||||||
|
|
||||||
// Champs scalaires poussables
|
// 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
|
// Catégories/secteurs du référentiel
|
||||||
const refCatIds = db.prepare(
|
const refCatIds = db.prepare(
|
||||||
|
|||||||
@@ -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 <form>) 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 (
|
||||||
|
<Modal
|
||||||
|
open
|
||||||
|
title={title}
|
||||||
|
onClose={onClose}
|
||||||
|
width={width}
|
||||||
|
footer={
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', width: '100%', gap: 8 }}>
|
||||||
|
<div style={{ flex: 1 }}>
|
||||||
|
{onDelete && (
|
||||||
|
<button className="danger" disabled={saving} onClick={onDelete}>Supprimer</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{step === 1 && <button onClick={onClose} disabled={saving}>Annuler</button>}
|
||||||
|
{step > 1 && <button onClick={goPrev} disabled={saving}>Précédent</button>}
|
||||||
|
{step < totalSteps && <button className="primary" onClick={goNext} disabled={saving}>Suivant</button>}
|
||||||
|
{step === totalSteps && <button className="primary" onClick={onSubmit} disabled={saving}>{saving ? '…' : submitLabel}</button>}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{/* ── Indicateur d'étape ── */}
|
||||||
|
<div style={{ marginBottom: 16 }}>
|
||||||
|
<div style={{ fontSize: 13, fontWeight: 600, marginBottom: 8 }}>
|
||||||
|
Étape {step} : {STEP_TITLES[step - 1]}
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'flex', gap: 6 }}>
|
||||||
|
{STEP_TITLES.map((_, i) => (
|
||||||
|
<div key={i} style={{
|
||||||
|
flex: 1, height: 5, borderRadius: 4,
|
||||||
|
background: i < step ? 'var(--primary)' : 'var(--border)',
|
||||||
|
}} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form ref={formRef} onSubmit={onSubmit}>
|
||||||
|
{err && <div className="error" style={{ marginBottom: 12 }}>{err}</div>}
|
||||||
|
{steps.map((content, i) => step === i + 1 && <div key={i}>{content}</div>)}
|
||||||
|
</form>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import { useState, useEffect, useCallback, useRef, useMemo } from 'react';
|
import { useState, useEffect, useCallback, useRef, useMemo } from 'react';
|
||||||
import { useLocation, useNavigate } from 'react-router-dom';
|
import { useLocation, useNavigate } from 'react-router-dom';
|
||||||
import { api } from '../api.js';
|
import { api } from '../api.js';
|
||||||
import InvSelect from '../components/InvSelect.jsx';
|
|
||||||
import ConfirmModal from '../components/ConfirmModal.jsx';
|
import ConfirmModal from '../components/ConfirmModal.jsx';
|
||||||
import Modal from '../components/Modal.jsx';
|
import Modal from '../components/Modal.jsx';
|
||||||
|
import PlateformeFormModal from '../components/PlateformeFormModal.jsx';
|
||||||
import ResultBanner from '../components/ResultBanner.jsx';
|
import ResultBanner from '../components/ResultBanner.jsx';
|
||||||
import CountrySelect, { COUNTRIES, FlagIcon } from '../components/CountrySelect.jsx';
|
import CountrySelect, { COUNTRIES, FlagIcon } from '../components/CountrySelect.jsx';
|
||||||
import { usePagination } from '../hooks/usePagination.js';
|
import { usePagination } from '../hooks/usePagination.js';
|
||||||
@@ -45,7 +45,8 @@ function IconUpload() {
|
|||||||
const countryLabel = code => COUNTRIES.find(c => c.code === code)?.name ?? code ?? '—';
|
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 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 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 ─────────────────────── */
|
/* ── Modal création / édition référentiel ─────────────────────── */
|
||||||
function RefModal({ row, onClose, onSaved }) {
|
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) : '',
|
taux_fiscalite_locale: row.taux_fiscalite_locale != null ? String(row.taux_fiscalite_locale) : '',
|
||||||
type_produit_fiscal: row.type_produit_fiscal || '2TT',
|
type_produit_fiscal: row.type_produit_fiscal || '2TT',
|
||||||
categorie_plateforme: row.categorie_plateforme || 'psfp',
|
categorie_plateforme: row.categorie_plateforme || 'psfp',
|
||||||
|
frais_plateforme: row.frais_plateforme || 'aucun',
|
||||||
methode_remboursement: row.methode_remboursement || 'portefeuille',
|
methode_remboursement: row.methode_remboursement || 'portefeuille',
|
||||||
type_pret_defaut: row.type_pret_defaut || '',
|
type_pret_defaut: row.type_pret_defaut || '',
|
||||||
freq_interets_defaut: row.freq_interets_defaut || '',
|
freq_interets_defaut: row.freq_interets_defaut || '',
|
||||||
@@ -96,6 +98,7 @@ function RefModal({ row, onClose, onSaved }) {
|
|||||||
? Number(form.taux_fiscalite_locale) : null,
|
? Number(form.taux_fiscalite_locale) : null,
|
||||||
type_produit_fiscal: form.type_produit_fiscal,
|
type_produit_fiscal: form.type_produit_fiscal,
|
||||||
categorie_plateforme: form.categorie_plateforme || 'psfp',
|
categorie_plateforme: form.categorie_plateforme || 'psfp',
|
||||||
|
frais_plateforme: form.frais_plateforme || 'aucun',
|
||||||
methode_remboursement: form.methode_remboursement || 'portefeuille',
|
methode_remboursement: form.methode_remboursement || 'portefeuille',
|
||||||
type_pret_defaut: form.type_pret_defaut || null,
|
type_pret_defaut: form.type_pret_defaut || null,
|
||||||
freq_interets_defaut: form.freq_interets_defaut || null,
|
freq_interets_defaut: form.freq_interets_defaut || null,
|
||||||
@@ -143,118 +146,165 @@ function RefModal({ row, onClose, onSaved }) {
|
|||||||
finally { setSaving(false); }
|
finally { setSaving(false); }
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
const step1 = (
|
||||||
<Modal open title={isNew ? 'Ajouter au référentiel' : `Modifier — ${row.nom}`}
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
|
||||||
onClose={onClose} width={640}
|
<div><label>Nom *</label><input value={form.nom} onChange={e => set('nom', e.target.value)} placeholder="Ex : October, Lendosphere…" /></div>
|
||||||
footer={<>
|
<div><label>URL</label><input value={form.url} onChange={e => set('url', e.target.value)} placeholder="https://…" /></div>
|
||||||
<button type="button" onClick={onClose} disabled={saving}>Annuler</button>
|
<div>
|
||||||
<button className="primary" form="ref-form" type="submit" disabled={saving}>{saving ? '…' : isNew ? 'Créer' : 'Enregistrer'}</button>
|
<label>Domiciliation</label>
|
||||||
</>}>
|
<CountrySelect
|
||||||
<form id="ref-form" onSubmit={handleSubmit}>
|
value={form.domiciliation}
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
|
onChange={code => set('domiciliation', code)}
|
||||||
<div><label>Nom *</label><input value={form.nom} onChange={e => set('nom', e.target.value)} placeholder="Ex : October, Lendosphere…" /></div>
|
showCode
|
||||||
<div><label>URL</label><input value={form.url} onChange={e => set('url', e.target.value)} placeholder="https://…" /></div>
|
/>
|
||||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 }}>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label>Domiciliation</label>
|
<label>Catégorie de plateforme</label>
|
||||||
<CountrySelect
|
<select value={form.categorie_plateforme || 'psfp'} onChange={e => set('categorie_plateforme', e.target.value)}>
|
||||||
value={form.domiciliation}
|
<option value="psfp">{CATEGORIE_PLATEFORME_LABELS.psfp}</option>
|
||||||
onChange={code => set('domiciliation', code)}
|
<option value="psan">{CATEGORIE_PLATEFORME_LABELS.psan}</option>
|
||||||
showCode
|
</select>
|
||||||
/>
|
</div>
|
||||||
</div>
|
<div><label>Description</label>
|
||||||
<div>
|
<textarea value={form.description} onChange={e => set('description', e.target.value)}
|
||||||
<label>Fiscalité</label>
|
style={{ minHeight: 180, resize: 'vertical' }} placeholder="Informations générales sur la plateforme…" /></div>
|
||||||
{form.domiciliation === 'FR'
|
</div>
|
||||||
? <input value="Flat tax (automatique)" disabled />
|
);
|
||||||
: <select value={form.fiscalite} onChange={e => set('fiscalite', e.target.value)}>
|
|
||||||
<option value="sans_fiscalite_locale">Sans fiscalité locale</option>
|
const step2 = (
|
||||||
<option value="avec_fiscalite_locale">Avec fiscalité locale</option>
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
|
||||||
</select>}
|
<div>
|
||||||
</div>
|
<label>Fiscalité</label>
|
||||||
</div>
|
{form.domiciliation === 'FR'
|
||||||
{fiscalite === 'avec_fiscalite_locale' && (
|
? <input value="Flat tax (automatique)" disabled />
|
||||||
<div><label>Taux fiscalité locale (%)</label>
|
: <select value={form.fiscalite} onChange={e => set('fiscalite', e.target.value)}>
|
||||||
<input type="number" min="0" max="100" step="0.1" value={form.taux_fiscalite_locale} onChange={e => set('taux_fiscalite_locale', e.target.value)} placeholder="Ex : 15" /></div>
|
<option value="sans_fiscalite_locale">Sans fiscalité locale</option>
|
||||||
)}
|
<option value="avec_fiscalite_locale">Avec fiscalité locale</option>
|
||||||
<div>
|
</select>}
|
||||||
<label>Type produit fiscal</label>
|
</div>
|
||||||
<select value={form.type_produit_fiscal} onChange={e => set('type_produit_fiscal', e.target.value)}>
|
{fiscalite === 'avec_fiscalite_locale' && (
|
||||||
<option value="2TT">2TT — Intérêts</option>
|
<div><label>Taux fiscalité locale (%)</label>
|
||||||
<option value="2TR">2TR — Revenus assimilés</option>
|
<input type="number" min="0" max="100" step="0.1" value={form.taux_fiscalite_locale} onChange={e => set('taux_fiscalite_locale', e.target.value)} placeholder="Ex : 15" /></div>
|
||||||
</select>
|
)}
|
||||||
</div>
|
<div>
|
||||||
<div>
|
<label>Type produit fiscal</label>
|
||||||
<label>Catégorie de plateforme</label>
|
<select value={form.type_produit_fiscal} onChange={e => set('type_produit_fiscal', e.target.value)}>
|
||||||
<select value={form.categorie_plateforme || 'psfp'} onChange={e => set('categorie_plateforme', e.target.value)}>
|
<option value="2TT">2TT — Intérêts</option>
|
||||||
<option value="psfp">{CATEGORIE_PLATEFORME_LABELS.psfp}</option>
|
<option value="2TR">2TR — Revenus assimilés</option>
|
||||||
<option value="psan">{CATEGORIE_PLATEFORME_LABELS.psan}</option>
|
</select>
|
||||||
</select>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 12 }}>
|
);
|
||||||
<div>
|
|
||||||
<label>Méthode de remboursement</label>
|
const step3 = (
|
||||||
<select value={form.methode_remboursement} onChange={e => set('methode_remboursement', e.target.value)}>
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
|
||||||
<option value="portefeuille">Porte-monnaie</option>
|
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 12 }}>
|
||||||
<option value="compte_courant">Compte courant</option>
|
<div>
|
||||||
<option value="choix_investisseur">Choix investisseur</option>
|
<label>Méthode de remboursement</label>
|
||||||
</select>
|
<select value={form.methode_remboursement} onChange={e => set('methode_remboursement', e.target.value)}>
|
||||||
</div>
|
<option value="portefeuille">Porte-monnaie</option>
|
||||||
<div>
|
<option value="compte_courant">Compte courant</option>
|
||||||
<label>Type de prêt (défaut)</label>
|
<option value="choix_investisseur">Choix investisseur</option>
|
||||||
<select value={form.type_pret_defaut} onChange={e => {
|
</select>
|
||||||
const t = e.target.value;
|
|
||||||
set('type_pret_defaut', t);
|
|
||||||
if (t === 'differe') set('freq_interets_defaut', 'in_fine');
|
|
||||||
else if (form.freq_interets_defaut === 'in_fine') set('freq_interets_defaut', 'mensuel');
|
|
||||||
}}>
|
|
||||||
<option value="">— Non défini —</option>
|
|
||||||
<option value="in_fine">In fine</option>
|
|
||||||
<option value="amortissable">Amortissable</option>
|
|
||||||
<option value="differe">Différé</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label>Périodicité (défaut)</label>
|
|
||||||
<select value={form.type_pret_defaut === 'differe' ? 'in_fine' : form.freq_interets_defaut}
|
|
||||||
disabled={form.type_pret_defaut === 'differe'}
|
|
||||||
onChange={e => set('freq_interets_defaut', e.target.value)}>
|
|
||||||
{form.type_pret_defaut === 'differe'
|
|
||||||
? <option value="in_fine">In fine (unique)</option>
|
|
||||||
: <>
|
|
||||||
<option value="">— Non définie —</option>
|
|
||||||
<option value="mensuel">Mensuelle</option>
|
|
||||||
<option value="trimestriel">Trimestrielle</option>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div><label>Description</label>
|
|
||||||
<textarea value={form.description} onChange={e => set('description', e.target.value)}
|
|
||||||
style={{ minHeight: 60, resize: 'vertical' }} placeholder="Informations générales sur la plateforme…" /></div>
|
|
||||||
<div>
|
|
||||||
<label>Catégories d'investissement</label>
|
|
||||||
<InvSelect
|
|
||||||
items={catsInv}
|
|
||||||
selected={form.categories_inv_ids || []}
|
|
||||||
onChange={ids => set('categories_inv_ids', ids)}
|
|
||||||
emptyLabel="Aucune catégorie d'investissement"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label>Secteurs d'investissement</label>
|
|
||||||
<InvSelect
|
|
||||||
items={secteursInv}
|
|
||||||
selected={form.secteurs_inv_ids || []}
|
|
||||||
onChange={ids => set('secteurs_inv_ids', ids)}
|
|
||||||
emptyLabel="Aucun secteur d'investissement"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{err && <div className="error" style={{ marginTop: 12 }}>{err}</div>}
|
<div>
|
||||||
</form>
|
<label>Type de prêt (défaut)</label>
|
||||||
</Modal>
|
<select value={form.type_pret_defaut} onChange={e => {
|
||||||
|
const t = e.target.value;
|
||||||
|
set('type_pret_defaut', t);
|
||||||
|
if (t === 'differe') set('freq_interets_defaut', 'in_fine');
|
||||||
|
else if (form.freq_interets_defaut === 'in_fine') set('freq_interets_defaut', 'mensuel');
|
||||||
|
}}>
|
||||||
|
<option value="">— Non défini —</option>
|
||||||
|
<option value="in_fine">In fine</option>
|
||||||
|
<option value="amortissable">Amortissable</option>
|
||||||
|
<option value="differe">Différé</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label>Périodicité (défaut)</label>
|
||||||
|
<select value={form.type_pret_defaut === 'differe' ? 'in_fine' : form.freq_interets_defaut}
|
||||||
|
disabled={form.type_pret_defaut === 'differe'}
|
||||||
|
onChange={e => set('freq_interets_defaut', e.target.value)}>
|
||||||
|
{form.type_pret_defaut === 'differe'
|
||||||
|
? <option value="in_fine">In fine (unique)</option>
|
||||||
|
: <>
|
||||||
|
<option value="">— Non définie —</option>
|
||||||
|
<option value="mensuel">Mensuelle</option>
|
||||||
|
<option value="trimestriel">Trimestrielle</option>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label>Frais liés à la plateforme</label>
|
||||||
|
<select value={form.frais_plateforme || 'aucun'} onChange={e => set('frais_plateforme', e.target.value)}>
|
||||||
|
<option value="aucun">{FRAIS_PLATEFORME_LABELS.aucun}</option>
|
||||||
|
<option value="entree_gestion_directe">{FRAIS_PLATEFORME_LABELS.entree_gestion_directe}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const step4 = (
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
|
||||||
|
<div>
|
||||||
|
<label>Catégories</label>
|
||||||
|
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
|
||||||
|
{catsInv.map(c => {
|
||||||
|
const checked = (form.categories_inv_ids || []).includes(c.id);
|
||||||
|
return (
|
||||||
|
<label key={c.id} style={{ display: 'flex', alignItems: 'center', gap: 4, cursor: 'pointer' }}>
|
||||||
|
<input type="checkbox" checked={checked} style={{ width: 'auto' }}
|
||||||
|
onChange={e => set('categories_inv_ids', e.target.checked
|
||||||
|
? [...(form.categories_inv_ids || []), c.id]
|
||||||
|
: (form.categories_inv_ids || []).filter(id => id !== c.id))} />
|
||||||
|
<span className="chip-cat" style={{ fontSize: 11 }}>{c.nom}</span>
|
||||||
|
</label>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label>Secteurs</label>
|
||||||
|
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
|
||||||
|
{secteursInv.map(s => {
|
||||||
|
const checked = (form.secteurs_inv_ids || []).includes(s.id);
|
||||||
|
return (
|
||||||
|
<label key={s.id} style={{ display: 'flex', alignItems: 'center', gap: 4, cursor: 'pointer' }}>
|
||||||
|
<input type="checkbox" checked={checked} style={{ width: 'auto' }}
|
||||||
|
onChange={e => set('secteurs_inv_ids', e.target.checked
|
||||||
|
? [...(form.secteurs_inv_ids || []), s.id]
|
||||||
|
: (form.secteurs_inv_ids || []).filter(id => id !== s.id))} />
|
||||||
|
<span className="chip-sect" style={{ fontSize: 11 }}>{s.nom}</span>
|
||||||
|
</label>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const step5 = (
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
|
||||||
|
<div className="text-muted" style={{ fontSize: 13 }}>
|
||||||
|
Le logo et les autres informations de profil (régulateur, statistiques publiques, réseaux sociaux…) se gèrent depuis la fiche détaillée de la plateforme, après création.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PlateformeFormModal
|
||||||
|
title={isNew ? 'Ajouter au référentiel' : `Modifier — ${row.nom}`}
|
||||||
|
onClose={onClose}
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
saving={saving}
|
||||||
|
err={err}
|
||||||
|
submitLabel={isNew ? 'Créer' : 'Enregistrer'}
|
||||||
|
width={640}
|
||||||
|
step1={step1} step2={step2} step3={step3} step4={step4} step5={step5}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -176,6 +176,7 @@ export default function PlatformeProfile() {
|
|||||||
taux_fiscalite_locale: form.taux_fiscalite_locale !== '' && form.taux_fiscalite_locale != null ? Number(form.taux_fiscalite_locale) : null,
|
taux_fiscalite_locale: form.taux_fiscalite_locale !== '' && form.taux_fiscalite_locale != null ? Number(form.taux_fiscalite_locale) : null,
|
||||||
type_produit_fiscal: form.type_produit_fiscal || '2TT',
|
type_produit_fiscal: form.type_produit_fiscal || '2TT',
|
||||||
categorie_plateforme: form.categorie_plateforme || 'psfp',
|
categorie_plateforme: form.categorie_plateforme || 'psfp',
|
||||||
|
frais_plateforme: form.frais_plateforme || 'aucun',
|
||||||
logo_filename: data.logo_filename ?? null,
|
logo_filename: data.logo_filename ?? null,
|
||||||
description: form.description || null,
|
description: form.description || null,
|
||||||
categories: data.categories || [],
|
categories: data.categories || [],
|
||||||
@@ -788,6 +789,7 @@ function ProfilImportBlock({ data, id, onApplied, catsInv = [], secteursInv = []
|
|||||||
taux_fiscalite_locale: data.taux_fiscalite_locale ?? null,
|
taux_fiscalite_locale: data.taux_fiscalite_locale ?? null,
|
||||||
type_produit_fiscal: data.type_produit_fiscal || '2TT',
|
type_produit_fiscal: data.type_produit_fiscal || '2TT',
|
||||||
categorie_plateforme: data.categorie_plateforme || 'psfp',
|
categorie_plateforme: data.categorie_plateforme || 'psfp',
|
||||||
|
frais_plateforme: data.frais_plateforme || 'aucun',
|
||||||
logo_filename: data.logo_filename ?? null,
|
logo_filename: data.logo_filename ?? null,
|
||||||
description: data.description || null,
|
description: data.description || null,
|
||||||
categories: data.categories || [],
|
categories: data.categories || [],
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { useNavigate, useSearchParams } from 'react-router-dom';
|
|||||||
import { api } from '../../api.js';
|
import { api } from '../../api.js';
|
||||||
import InvSelect from '../../components/InvSelect.jsx';
|
import InvSelect from '../../components/InvSelect.jsx';
|
||||||
import Modal from '../../components/Modal.jsx';
|
import Modal from '../../components/Modal.jsx';
|
||||||
|
import PlateformeFormModal from '../../components/PlateformeFormModal.jsx';
|
||||||
import ConfirmModal from '../../components/ConfirmModal.jsx';
|
import ConfirmModal from '../../components/ConfirmModal.jsx';
|
||||||
import CountrySelect, { COUNTRIES, FlagIcon } from '../../components/CountrySelect.jsx';
|
import CountrySelect, { COUNTRIES, FlagIcon } from '../../components/CountrySelect.jsx';
|
||||||
import ResultBanner from '../../components/ResultBanner.jsx';
|
import ResultBanner from '../../components/ResultBanner.jsx';
|
||||||
@@ -25,6 +26,7 @@ function localToday() {
|
|||||||
|
|
||||||
const countryLabel = code => COUNTRIES.find(c => c.code === code)?.name ?? code ?? '—';
|
const countryLabel = code => COUNTRIES.find(c => c.code === code)?.name ?? code ?? '—';
|
||||||
const CATEGORIE_PLATEFORME_LABELS = { psfp: 'Prestataire de Services de Financement Participatif', psan: 'Prestataire de Services sur Actifs Numériques' };
|
const CATEGORIE_PLATEFORME_LABELS = { psfp: 'Prestataire de Services de Financement Participatif', psan: 'Prestataire de Services sur Actifs Numériques' };
|
||||||
|
const FRAIS_PLATEFORME_LABELS = { aucun: 'Aucun frais', entree_gestion_directe: "Frais d'entrée + Frais de gestion (Prélèvement direct)" };
|
||||||
const FISCALITE_LABELS = {
|
const FISCALITE_LABELS = {
|
||||||
flat_tax: 'Flat Tax',
|
flat_tax: 'Flat Tax',
|
||||||
sans_fiscalite_locale: 'Sans fiscalité locale',
|
sans_fiscalite_locale: 'Sans fiscalité locale',
|
||||||
@@ -52,7 +54,7 @@ function fmtFiscalite(p) {
|
|||||||
return base;
|
return base;
|
||||||
}
|
}
|
||||||
|
|
||||||
const EMPTY_PLAT = { nom: '', url: '', domiciliation: 'france', fiscalite: 'flat_tax', taux_fiscalite_locale: '', type_produit_fiscal: '2TT', categorie_plateforme: 'psfp', methode_remboursement: 'portefeuille', investisseur_id: null, date_ouverture: '', logo_filename: null, type_pret_defaut: '', freq_interets_defaut: '', referentiel_id: null };
|
const EMPTY_PLAT = { nom: '', url: '', domiciliation: 'france', fiscalite: 'flat_tax', taux_fiscalite_locale: '', type_produit_fiscal: '2TT', categorie_plateforme: 'psfp', frais_plateforme: 'aucun', methode_remboursement: 'portefeuille', investisseur_id: null, date_ouverture: '', logo_filename: null, type_pret_defaut: '', freq_interets_defaut: '', referentiel_id: null };
|
||||||
|
|
||||||
const LOGO_BASE = (import.meta.env.VITE_API_URL || '/api').replace(/\/api$/, '') + '/api/logos/';
|
const LOGO_BASE = (import.meta.env.VITE_API_URL || '/api').replace(/\/api$/, '') + '/api/logos/';
|
||||||
const logoUrl = (filename) => filename ? LOGO_BASE + filename : null;
|
const logoUrl = (filename) => filename ? LOGO_BASE + filename : null;
|
||||||
@@ -491,7 +493,7 @@ function PlatPickerModal({ open, onClose, referentiel, onSelect, onManual, err }
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* ── Formulaire plateforme partagé (création + édition) ─────────── */
|
/* ── Formulaire plateforme partagé (création + édition) ─────────── */
|
||||||
function PlatForm({ state, setter, logoFile, setLogoFile, logoPreview, setLogoPreview,
|
function platFormSteps({ state, setter, logoFile, setLogoFile, logoPreview, setLogoPreview,
|
||||||
investisseurs, referentiel, categoriesInv, secteursInv, isEdit = false, onDelLogo }) {
|
investisseurs, referentiel, categoriesInv, secteursInv, isEdit = false, onDelLogo }) {
|
||||||
|
|
||||||
const isFrance = state.domiciliation === 'FR';
|
const isFrance = state.domiciliation === 'FR';
|
||||||
@@ -513,14 +515,8 @@ function PlatForm({ state, setter, logoFile, setLogoFile, logoPreview, setLogoPr
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
const step1 = (
|
||||||
<>
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
|
||||||
{/* Nom */}
|
|
||||||
{row('Nom *', (
|
|
||||||
<input required value={state.nom} onChange={e => setter(s => ({ ...s, nom: e.target.value }))}
|
|
||||||
placeholder="Ex: Lendosphere" style={{ width: '100%', boxSizing: 'border-box' }} />
|
|
||||||
))}
|
|
||||||
|
|
||||||
{/* Référentiel */}
|
{/* Référentiel */}
|
||||||
{referentiel.length > 0 && row('Lier au référentiel', (
|
{referentiel.length > 0 && row('Lier au référentiel', (
|
||||||
<select value={state.referentiel_id ?? ''} onChange={e => {
|
<select value={state.referentiel_id ?? ''} onChange={e => {
|
||||||
@@ -533,6 +529,12 @@ function PlatForm({ state, setter, logoFile, setLogoFile, logoPreview, setLogoPr
|
|||||||
</select>
|
</select>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
|
{/* Nom */}
|
||||||
|
{row('Nom *', (
|
||||||
|
<input required value={state.nom} onChange={e => setter(s => ({ ...s, nom: e.target.value }))}
|
||||||
|
placeholder="Ex: Lendosphere" style={{ width: '100%', boxSizing: 'border-box' }} />
|
||||||
|
))}
|
||||||
|
|
||||||
{/* URL */}
|
{/* URL */}
|
||||||
{row('URL', (
|
{row('URL', (
|
||||||
<input type="url" value={state.url || ''} onChange={e => setter(s => ({ ...s, url: e.target.value }))}
|
<input type="url" value={state.url || ''} onChange={e => setter(s => ({ ...s, url: e.target.value }))}
|
||||||
@@ -548,6 +550,36 @@ function PlatForm({ state, setter, logoFile, setLogoFile, logoPreview, setLogoPr
|
|||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
|
{/* Catégorie de plateforme */}
|
||||||
|
{row('Catégorie de plateforme', (
|
||||||
|
<select value={state.categorie_plateforme || 'psfp'} onChange={e => setter(s => ({ ...s, categorie_plateforme: e.target.value }))}
|
||||||
|
style={{ width: '100%' }}>
|
||||||
|
<option value="psfp">{CATEGORIE_PLATEFORME_LABELS.psfp}</option>
|
||||||
|
<option value="psan">{CATEGORIE_PLATEFORME_LABELS.psan}</option>
|
||||||
|
</select>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{/* Détenteur */}
|
||||||
|
{investisseurs.length > 1 && row('Détenteur', (
|
||||||
|
<select value={state.investisseur_id ?? ''} style={{ width: '100%' }}
|
||||||
|
onChange={e => setter(s => ({ ...s, investisseur_id: e.target.value ? Number(e.target.value) : null }))}>
|
||||||
|
<option value="">— Sélectionner —</option>
|
||||||
|
{investisseurs.map(inv => (
|
||||||
|
<option key={inv.id} value={inv.id}>{memberLabel(inv)}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{/* Date d'ouverture */}
|
||||||
|
{row("Date d'ouverture du compte", (
|
||||||
|
<input type="date" value={state.date_ouverture || ''} onChange={e => setter(s => ({ ...s, date_ouverture: e.target.value }))}
|
||||||
|
style={{ width: '100%', boxSizing: 'border-box' }} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const step2 = (
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
|
||||||
{/* Fiscalité */}
|
{/* Fiscalité */}
|
||||||
{row('Fiscalité', (
|
{row('Fiscalité', (
|
||||||
<select value={state.fiscalite || 'flat_tax'} disabled={isFrance}
|
<select value={state.fiscalite || 'flat_tax'} disabled={isFrance}
|
||||||
@@ -582,16 +614,11 @@ function PlatForm({ state, setter, logoFile, setLogoFile, logoPreview, setLogoPr
|
|||||||
<option value="2TY">2TY — Revenus de capitaux (autre)</option>
|
<option value="2TY">2TY — Revenus de capitaux (autre)</option>
|
||||||
</select>
|
</select>
|
||||||
))}
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
{/* Catégorie de plateforme */}
|
const step3 = (
|
||||||
{row('Catégorie de plateforme', (
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
|
||||||
<select value={state.categorie_plateforme || 'psfp'} onChange={e => setter(s => ({ ...s, categorie_plateforme: e.target.value }))}
|
|
||||||
style={{ width: '100%' }}>
|
|
||||||
<option value="psfp">{CATEGORIE_PLATEFORME_LABELS.psfp}</option>
|
|
||||||
<option value="psan">{CATEGORIE_PLATEFORME_LABELS.psan}</option>
|
|
||||||
</select>
|
|
||||||
))}
|
|
||||||
|
|
||||||
{/* Méthode de remboursement */}
|
{/* Méthode de remboursement */}
|
||||||
{row('Versement des remboursements', (
|
{row('Versement des remboursements', (
|
||||||
<select value={state.methode_remboursement || 'portefeuille'} onChange={e => setter(s => ({ ...s, methode_remboursement: e.target.value }))}
|
<select value={state.methode_remboursement || 'portefeuille'} onChange={e => setter(s => ({ ...s, methode_remboursement: e.target.value }))}
|
||||||
@@ -624,22 +651,19 @@ function PlatForm({ state, setter, logoFile, setLogoFile, logoPreview, setLogoPr
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Date d'ouverture */}
|
{/* Frais liés à la plateforme */}
|
||||||
{row("Date d'ouverture du compte", (
|
{row('Frais liés à la plateforme', (
|
||||||
<input type="date" value={state.date_ouverture || ''} onChange={e => setter(s => ({ ...s, date_ouverture: e.target.value }))}
|
<select value={state.frais_plateforme || 'aucun'} onChange={e => setter(s => ({ ...s, frais_plateforme: e.target.value }))}
|
||||||
style={{ width: '100%', boxSizing: 'border-box' }} />
|
style={{ width: '100%' }}>
|
||||||
))}
|
<option value="aucun">{FRAIS_PLATEFORME_LABELS.aucun}</option>
|
||||||
|
<option value="entree_gestion_directe">{FRAIS_PLATEFORME_LABELS.entree_gestion_directe}</option>
|
||||||
{/* Détenteur */}
|
</select>
|
||||||
{investisseurs.length > 1 && row('Détenteur', (
|
|
||||||
<InvSelect
|
|
||||||
value={state.investisseur_id}
|
|
||||||
onChange={id => setter(s => ({ ...s, investisseur_id: id }))}
|
|
||||||
investisseurs={investisseurs}
|
|
||||||
placeholder="— Sélectionner —"
|
|
||||||
/>
|
|
||||||
))}
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const step4 = (
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
|
||||||
{/* Catégories d'investissement (édition seulement) */}
|
{/* Catégories d'investissement (édition seulement) */}
|
||||||
{isEdit && categoriesInv.length > 0 && row('Catégories', (
|
{isEdit && categoriesInv.length > 0 && row('Catégories', (
|
||||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
|
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
|
||||||
@@ -686,6 +710,16 @@ function PlatForm({ state, setter, logoFile, setLogoFile, logoPreview, setLogoPr
|
|||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
|
{!isEdit && (
|
||||||
|
<div className="text-muted" style={{ fontSize: 13 }}>
|
||||||
|
Les catégories et secteurs d'investissement pourront être précisés après la création de la plateforme.
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const step5 = (
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
|
||||||
{/* Logo */}
|
{/* Logo */}
|
||||||
{row('Logo', (
|
{row('Logo', (
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
|
||||||
@@ -699,8 +733,10 @@ function PlatForm({ state, setter, logoFile, setLogoFile, logoPreview, setLogoPr
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return { step1, step2, step3, step4, step5 };
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function PlateformesSection() {
|
export default function PlateformesSection() {
|
||||||
@@ -860,6 +896,7 @@ export default function PlateformesSection() {
|
|||||||
taux_fiscalite_locale: ref.taux_fiscalite_locale ?? null,
|
taux_fiscalite_locale: ref.taux_fiscalite_locale ?? null,
|
||||||
type_produit_fiscal: ref.type_produit_fiscal || '2TT',
|
type_produit_fiscal: ref.type_produit_fiscal || '2TT',
|
||||||
categorie_plateforme: ref.categorie_plateforme || 'psfp',
|
categorie_plateforme: ref.categorie_plateforme || 'psfp',
|
||||||
|
frais_plateforme: ref.frais_plateforme || 'aucun',
|
||||||
methode_remboursement: ref.methode_remboursement || 'portefeuille',
|
methode_remboursement: ref.methode_remboursement || 'portefeuille',
|
||||||
type_pret_defaut: ref.type_pret_defaut || null,
|
type_pret_defaut: ref.type_pret_defaut || null,
|
||||||
freq_interets_defaut: ref.freq_interets_defaut || null,
|
freq_interets_defaut: ref.freq_interets_defaut || null,
|
||||||
@@ -965,6 +1002,7 @@ export default function PlateformesSection() {
|
|||||||
taux_fiscalite_locale: p.taux_fiscalite_locale ?? '',
|
taux_fiscalite_locale: p.taux_fiscalite_locale ?? '',
|
||||||
type_produit_fiscal: p.type_produit_fiscal || '2TT',
|
type_produit_fiscal: p.type_produit_fiscal || '2TT',
|
||||||
categorie_plateforme: p.categorie_plateforme || 'psfp',
|
categorie_plateforme: p.categorie_plateforme || 'psfp',
|
||||||
|
frais_plateforme: p.frais_plateforme || 'aucun',
|
||||||
methode_remboursement: p.methode_remboursement || 'portefeuille',
|
methode_remboursement: p.methode_remboursement || 'portefeuille',
|
||||||
investisseur_id: p.investisseur_id ?? null,
|
investisseur_id: p.investisseur_id ?? null,
|
||||||
date_ouverture: p.date_ouverture || '',
|
date_ouverture: p.date_ouverture || '',
|
||||||
@@ -988,6 +1026,7 @@ export default function PlateformesSection() {
|
|||||||
? Number(editPlat.taux_fiscalite_locale) : null,
|
? Number(editPlat.taux_fiscalite_locale) : null,
|
||||||
type_produit_fiscal: editPlat.type_produit_fiscal || '2TT',
|
type_produit_fiscal: editPlat.type_produit_fiscal || '2TT',
|
||||||
categorie_plateforme: editPlat.categorie_plateforme || 'psfp',
|
categorie_plateforme: editPlat.categorie_plateforme || 'psfp',
|
||||||
|
frais_plateforme: editPlat.frais_plateforme || 'aucun',
|
||||||
methode_remboursement: editPlat.methode_remboursement || 'portefeuille',
|
methode_remboursement: editPlat.methode_remboursement || 'portefeuille',
|
||||||
investisseur_id: editPlat.investisseur_id ? Number(editPlat.investisseur_id) : null,
|
investisseur_id: editPlat.investisseur_id ? Number(editPlat.investisseur_id) : null,
|
||||||
date_ouverture: editPlat.date_ouverture || null,
|
date_ouverture: editPlat.date_ouverture || null,
|
||||||
@@ -1209,61 +1248,51 @@ export default function PlateformesSection() {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{/* ── Modal : Ajouter une plateforme ── */}
|
{/* ── Modal : Ajouter une plateforme ── */}
|
||||||
<Modal
|
{showNewPlat && (() => {
|
||||||
open={showNewPlat}
|
const s = platFormSteps({
|
||||||
title="Nouvelle plateforme"
|
state: newPlat, setter: setNewPlat,
|
||||||
onClose={() => { setShowNewPlat(false); setNewPlat(EMPTY_PLAT); setNewPlatLogoFile(null); setNewPlatLogoPreview(null); setErr(null); }}
|
logoFile: newPlatLogoFile, setLogoFile: setNewPlatLogoFile,
|
||||||
width={600}
|
logoPreview: newPlatLogoPreview, setLogoPreview: setNewPlatLogoPreview,
|
||||||
footer={
|
investisseurs, referentiel,
|
||||||
<div style={{ display: 'flex', justifyContent: 'flex-end', width: '100%', gap: 8 }}>
|
categoriesInv: [], secteursInv: [],
|
||||||
<button type="button" onClick={() => { setShowNewPlat(false); setNewPlat(EMPTY_PLAT); setNewPlatLogoFile(null); setNewPlatLogoPreview(null); setErr(null); }}>Annuler</button>
|
});
|
||||||
<button className="primary" form="form-new-plat" type="submit">Créer</button>
|
return (
|
||||||
</div>
|
<PlateformeFormModal
|
||||||
}
|
title="Nouvelle plateforme"
|
||||||
>
|
onClose={() => { setShowNewPlat(false); setNewPlat(EMPTY_PLAT); setNewPlatLogoFile(null); setNewPlatLogoPreview(null); setErr(null); }}
|
||||||
<form id="form-new-plat" onSubmit={addPlat} style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
|
onSubmit={addPlat}
|
||||||
{err && <div className="error">{err}</div>}
|
err={err}
|
||||||
<PlatForm
|
submitLabel="Créer"
|
||||||
state={newPlat} setter={setNewPlat}
|
width={600}
|
||||||
logoFile={newPlatLogoFile} setLogoFile={setNewPlatLogoFile}
|
step1={s.step1} step2={s.step2} step3={s.step3} step4={s.step4} step5={s.step5}
|
||||||
logoPreview={newPlatLogoPreview} setLogoPreview={setNewPlatLogoPreview}
|
|
||||||
investisseurs={investisseurs} referentiel={referentiel}
|
|
||||||
categoriesInv={[]} secteursInv={[]}
|
|
||||||
/>
|
/>
|
||||||
</form>
|
);
|
||||||
</Modal>
|
})()}
|
||||||
|
|
||||||
{/* ── Modal : Modifier une plateforme ── */}
|
{/* ── Modal : Modifier une plateforme ── */}
|
||||||
<Modal
|
{editPlat && (() => {
|
||||||
open={!!editPlat}
|
const s = platFormSteps({
|
||||||
title="Modifier la plateforme"
|
state: editPlat, setter: setEditPlat,
|
||||||
onClose={() => { setEditPlat(null); setEditPlatLogoFile(null); setEditPlatLogoPreview(null); setErr(null); }}
|
logoFile: editPlatLogoFile, setLogoFile: setEditPlatLogoFile,
|
||||||
width={600}
|
logoPreview: editPlatLogoPreview, setLogoPreview: setEditPlatLogoPreview,
|
||||||
footer={
|
investisseurs, referentiel,
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', width: '100%' }}>
|
categoriesInv, secteursInv,
|
||||||
<button className="danger" type="button" onClick={() => { const p = editPlat; setEditPlat(null); delPlat(p.id); }}>Supprimer</button>
|
isEdit: true,
|
||||||
<div style={{ display: 'flex', gap: 8 }}>
|
onDelLogo: () => delLogoPlat(editPlat.id),
|
||||||
<button type="button" onClick={() => { setEditPlat(null); setEditPlatLogoFile(null); setEditPlatLogoPreview(null); setErr(null); }}>Annuler</button>
|
});
|
||||||
<button className="primary" form="form-edit-plat" type="submit">Enregistrer</button>
|
return (
|
||||||
</div>
|
<PlateformeFormModal
|
||||||
</div>
|
title="Modifier la plateforme"
|
||||||
}
|
onClose={() => { setEditPlat(null); setEditPlatLogoFile(null); setEditPlatLogoPreview(null); setErr(null); }}
|
||||||
>
|
onSubmit={saveEditPlat}
|
||||||
{editPlat && (
|
err={err}
|
||||||
<form id="form-edit-plat" onSubmit={saveEditPlat} style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
|
submitLabel="Enregistrer"
|
||||||
{err && <div className="error">{err}</div>}
|
width={600}
|
||||||
<PlatForm
|
onDelete={() => { const p = editPlat; setEditPlat(null); delPlat(p.id); }}
|
||||||
state={editPlat} setter={setEditPlat}
|
step1={s.step1} step2={s.step2} step3={s.step3} step4={s.step4} step5={s.step5}
|
||||||
logoFile={editPlatLogoFile} setLogoFile={setEditPlatLogoFile}
|
/>
|
||||||
logoPreview={editPlatLogoPreview} setLogoPreview={setEditPlatLogoPreview}
|
);
|
||||||
investisseurs={investisseurs} referentiel={referentiel}
|
})()}
|
||||||
categoriesInv={categoriesInv} secteursInv={secteursInv}
|
|
||||||
isEdit
|
|
||||||
onDelLogo={() => delLogoPlat(editPlat.id)}
|
|
||||||
/>
|
|
||||||
</form>
|
|
||||||
)}
|
|
||||||
</Modal>
|
|
||||||
|
|
||||||
<ConfirmModal
|
<ConfirmModal
|
||||||
open={!!confirmDelete}
|
open={!!confirmDelete}
|
||||||
|
|||||||
Reference in new issue
Block a user