diff --git a/backend/src/db/index.js b/backend/src/db/index.js index a5de225..3a2c945 100644 --- a/backend/src/db/index.js +++ b/backend/src/db/index.js @@ -4086,4 +4086,122 @@ db.prepare(` // stade, la suppression est donc sans risque de perte de données utilisateur. db.prepare("DELETE FROM supports_referentiel WHERE nom = 'Gestion pilotée / mandat'").run(); + +// ── Migration : eligible_compte sur enveloppes_referentiel (Phase 3a, 28/09/26, demande +// Olivier — chantier "rattacher Mes comptes courants au référentiel") ──────────────────── +// Distingue les enveloppes qui ont le profil d'un vrai "compte" (banque/courtier/assureur — +// Livret A, PEA, Assurance-vie, CTO, Compte crypto exchange...) de celles qui n'en ont pas +// (immobilier physique, objets de collection, wallets crypto auto-conservés, placements via +// plateforme déjà couverts par le mécanisme plateformes_referentiel...). Sert à filtrer le +// sélecteur "Enveloppe" de Paramètres > Mes comptes (cf. comptes.enveloppe_id ci-dessous) — +// cadrage validé par Olivier via AskUserQuestion (28/09/26) : sous-ensemble "compte financier", +// pas tout le référentiel. Colonne booléenne plutôt que catégorie existante réutilisée : la +// classification ne suit aucune des 15 catégories actuelles (ex. "Actifs numériques" contient +// à la fois Compte crypto exchange, éligible, et les wallets auto-conservés, non éligibles). +{ + const cols = db.prepare('PRAGMA table_info(enveloppes_referentiel)').all().map(c => c.name); + if (!cols.includes('eligible_compte')) { + db.exec("ALTER TABLE enveloppes_referentiel ADD COLUMN eligible_compte INTEGER NOT NULL DEFAULT 0"); + // Backfill exécuté une seule fois (dans cette même garde de colonne) : classification + // initiale par nom, jamais retouchée ensuite même si l'admin édite ce flag à la main. + const ELIGIBLE_COMPTE_NOMS = [ + // Comptes courants + 'Compte courant non rémunéré', 'Compte courant rémunéré', 'Compte rémunéré nouvelle génération (fintech)', + // Épargne réglementée + 'Livret A', 'LDDS', 'LEP', 'Livret Jeune', 'CEL', 'PEL', + // Épargne bancaire non réglementée + 'Livret bancaire non réglementé', 'Compte à terme (CAT)', + // Assurance-vie et assimilés + 'Assurance-vie monosupport (fonds euros)', 'Assurance-vie multisupport', + 'Contrat de capitalisation', 'Assurance-vie luxembourgeoise', + // Épargne retraite + 'PER individuel', 'PER collectif / obligatoire (PERCOL, article 83)', + // Épargne salariale + 'PEE', + // Enveloppes titres / marchés financiers + 'PEA', 'PEA-PME', 'Compte-titres ordinaire (CTO)', + // Actifs numériques — seul le compte sur exchange a une institution (l'exchange + // lui-même) ; les wallets auto-conservés/conservés par un tiers restent hors périmètre. + 'Compte crypto (exchange)', + // Épargne solidaire + 'Livret solidaire', + ]; + const setEligible = db.prepare('UPDATE enveloppes_referentiel SET eligible_compte = 1 WHERE nom = ?'); + db.transaction(() => { for (const nom of ELIGIBLE_COMPTE_NOMS) setEligible.run(nom); })(); + } +} + +// ── Migration : enveloppe_id sur comptes (Phase 3a, 28/09/26, demande Olivier) ──────────── +// Rattache un compte au référentiel enveloppes_referentiel — nullable, non-destructif, sur le +// même principe que comptes.institution_id (Phase 2, cf. plan de déploiement Institutions +// financières) : le champ `type` existant (compte_courant/pea_pme) reste en base tel quel et +// continue de piloter la logique de remboursement/frais déjà en place (de nombreux écrans +// investissement/remboursement font comptes.find(c => c.type === 'compte_courant') pour +// proposer un compte de règlement par défaut) — enveloppe_id est un enrichissement PUREMENT +// descriptif, ajouté à côté du champ type, jamais en remplacement, pour ne rien casser de cette +// logique existante. Restreint aux enveloppes "compte financier" (eligible_compte = 1) côté +// sélecteur frontend uniquement — pas de contrainte CHECK en base, pour rester simple. +{ + const cols = db.prepare('PRAGMA table_info(comptes)').all().map(c => c.name); + if (!cols.includes('enveloppe_id')) { + db.exec('ALTER TABLE comptes ADD COLUMN enveloppe_id INTEGER REFERENCES enveloppes_referentiel(id) ON DELETE SET NULL'); + } +} + +// ── Extension du 28/09/26 (Phase 3a) — catégorie "Gestion de trésorerie" + Spiko / Regular +// Finance ────────────────────────────────────────────────────────────────────────────────── +// Identifiés pendant le chantier "Référentiel Enveloppes & Supports d'épargne" (cf. "Extension +// du 29/09/26 (suite) — support Fonds monétaires" dans le plan de déploiement dédié) : Spiko et +// Regular Finance ne sont pas des enveloppes mais des plateformes/institutions donnant accès à +// un fonds monétaire (OPCVM monétaire, y compris tokenisé sur blockchain chez Spiko) — elles ont +// vocation à rejoindre institutions_referentiel, pas enveloppes_referentiel. Toujours exécuté, +// INSERT OR IGNORE sur des clés UNIQUE(nom) — s'ajoute sans jamais dupliquer ni écraser le +// référentiel déjà seedé (Phase 1), même principe que les extensions "EXTRA" du chantier +// enveloppes. +{ + db.prepare('INSERT OR IGNORE INTO categories_institutions (nom) VALUES (?)').run('Gestion de trésorerie'); + const insInst = db.prepare('INSERT OR IGNORE INTO institutions_referentiel (nom) VALUES (?)'); + const getInst = db.prepare('SELECT id FROM institutions_referentiel WHERE nom = ?'); + const getCat = db.prepare('SELECT id FROM categories_institutions WHERE nom = ?'); + const linkCat = db.prepare('INSERT OR IGNORE INTO referentiel_categories_institutions (institution_id, categorie_id) VALUES (?, ?)'); + db.transaction(() => { + for (const nom of ['Spiko', 'Regular Finance']) { + insInst.run(nom); + const inst = getInst.get(nom); + const cat = getCat.get('Gestion de trésorerie'); + if (inst && cat) linkCat.run(inst.id, cat.id); + } + })(); +} + +// ── Chantier "Passif — Emprunts" (Phase 3c, 28/09/26, demande Olivier) ─────────────────────── +// Objet séparé de enveloppes_referentiel : un emprunt est structurellement différent d'un actif +// (le capital restant dû DIMINUE avec le temps, porte un taux et un échéancier fixés à la +// souscription) — cf. "Réflexion prospective" du plan de déploiement Enveloppes & Supports (doc +// projet), point 3 ("un passif, pas un actif"). Table dédiée plutôt qu'un champ sur comptes, +// pour la même raison qu'un compte n'est jamais une "enveloppe" : structure et cycle de vie +// différents (un compte se crédite/débite librement, un emprunt s'amortit). Rattachement +// institution optionnel (établissement prêteur) via institutions_referentiel, sur le même +// principe que comptes.institution_id — nullable, non contraignant. +db.exec(` + CREATE TABLE IF NOT EXISTS emprunts ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, + investisseur_id INTEGER REFERENCES investisseurs(id) ON DELETE SET NULL, + institution_id INTEGER REFERENCES institutions_referentiel(id) ON DELETE SET NULL, + nom TEXT NOT NULL, + montant_emprunte REAL NOT NULL, + capital_restant_du REAL NOT NULL, + taux_nominal REAL, + date_debut TEXT, + duree_mois INTEGER, + mensualite REAL, + notes TEXT, + created_at TEXT NOT NULL DEFAULT (datetime('now')), + updated_at TEXT + ) +`); +db.exec('CREATE INDEX IF NOT EXISTS idx_emprunts_user ON emprunts(user_id)'); +db.exec('CREATE INDEX IF NOT EXISTS idx_emprunts_inv ON emprunts(investisseur_id)'); + export default db; diff --git a/backend/src/routes/comptes.js b/backend/src/routes/comptes.js index a0614a8..fc1bebc 100644 --- a/backend/src/routes/comptes.js +++ b/backend/src/routes/comptes.js @@ -11,19 +11,26 @@ const Schema = z.object({ investisseur_id: z.number().int().positive().nullable().optional(), banque: z.string().nullable().optional(), institution_id: z.number().int().positive().nullable().optional(), + // Phase 3a (28/09/26) — rattachement au référentiel enveloppes_referentiel, purement + // descriptif et non-destructif (cf. commentaire de migration dans db/index.js) : n'a aucune + // incidence sur `type`, qui reste le champ utilisé par la logique de remboursement/frais + // existante. Non contraint à eligible_compte=1 côté Zod — le filtrage se fait côté frontend. + enveloppe_id: z.number().int().positive().nullable().optional(), exoneration_fiscale: z.enum(['aucune', 'pfnl_5ans']).default('aucune'), }); router.get('/', (req, res) => { const rows = db.prepare(` - SELECT c.id, c.nom, c.type, c.banque, c.institution_id, c.exoneration_fiscale, c.investisseur_id, c.created_at, + SELECT c.id, c.nom, c.type, c.banque, c.institution_id, c.enveloppe_id, c.exoneration_fiscale, c.investisseur_id, c.created_at, inv.nom AS investisseur_nom, inv.prenom AS investisseur_prenom, inv.type AS investisseur_type, inv.type_fiscal AS investisseur_type_fiscal, ir.nom AS institution_nom, ir.domiciliation AS institution_domiciliation, - ir.logo_filename AS institution_logo_filename, ir.icone_filename AS institution_icone_filename + ir.logo_filename AS institution_logo_filename, ir.icone_filename AS institution_icone_filename, + er.nom AS enveloppe_nom FROM comptes c LEFT JOIN investisseurs inv ON inv.id = c.investisseur_id LEFT JOIN institutions_referentiel ir ON ir.id = c.institution_id + LEFT JOIN enveloppes_referentiel er ON er.id = c.enveloppe_id WHERE c.user_id = ? ORDER BY c.nom `).all(req.user.id); @@ -34,8 +41,8 @@ router.post('/', (req, res, next) => { try { const body = Schema.parse(req.body); const r = db.prepare( - 'INSERT INTO comptes (user_id, nom, type, banque, institution_id, investisseur_id, exoneration_fiscale) VALUES (?,?,?,?,?,?,?)' - ).run(req.user.id, body.nom, body.type, body.banque || null, body.institution_id ?? null, body.investisseur_id ?? null, body.exoneration_fiscale ?? 'aucune'); + 'INSERT INTO comptes (user_id, nom, type, banque, institution_id, enveloppe_id, investisseur_id, exoneration_fiscale) VALUES (?,?,?,?,?,?,?,?)' + ).run(req.user.id, body.nom, body.type, body.banque || null, body.institution_id ?? null, body.enveloppe_id ?? null, body.investisseur_id ?? null, body.exoneration_fiscale ?? 'aucune'); res.status(201).json({ id: r.lastInsertRowid, ...body }); } catch (e) { next(e); } }); @@ -44,8 +51,8 @@ router.put('/:id', (req, res, next) => { try { const body = Schema.parse(req.body); const r = db.prepare( - `UPDATE comptes SET nom=?, type=?, banque=?, institution_id=?, investisseur_id=?, exoneration_fiscale=?, updated_at=datetime('now') WHERE id=? AND user_id=?` - ).run(body.nom, body.type, body.banque || null, body.institution_id ?? null, body.investisseur_id ?? null, body.exoneration_fiscale ?? 'aucune', req.params.id, req.user.id); + `UPDATE comptes SET nom=?, type=?, banque=?, institution_id=?, enveloppe_id=?, investisseur_id=?, exoneration_fiscale=?, updated_at=datetime('now') WHERE id=? AND user_id=?` + ).run(body.nom, body.type, body.banque || null, body.institution_id ?? null, body.enveloppe_id ?? null, body.investisseur_id ?? null, body.exoneration_fiscale ?? 'aucune', req.params.id, req.user.id); if (r.changes === 0) throw new HttpError(404, 'Not found'); res.json({ id: Number(req.params.id), ...body }); } catch (e) { next(e); } diff --git a/backend/src/routes/emprunts.js b/backend/src/routes/emprunts.js new file mode 100644 index 0000000..b89aa1d --- /dev/null +++ b/backend/src/routes/emprunts.js @@ -0,0 +1,96 @@ +import { Router } from 'express'; +import { z } from 'zod'; +import db from '../db/index.js'; +import { HttpError } from '../middleware/errorHandler.js'; + +/** + * routes/emprunts.js — Passif patrimonial (Phase 3c, 28/09/26, demande Olivier). Objet séparé + * de comptes/enveloppes_referentiel : un emprunt est structurellement différent d'un actif (le + * capital restant dû diminue avec le temps, porte un taux et un échéancier fixés à la + * souscription) — cf. "Réflexion prospective" du plan de déploiement Enveloppes & Supports (doc + * projet), point 3. Mêmes conventions que comptes.js : scope par user_id, détenteur + * (investisseur_id) et établissement prêteur (institution_id) optionnels et nullables. + */ + +const router = Router(); + +const Schema = z.object({ + nom: z.string().min(1), + investisseur_id: z.number().int().positive().nullable().optional(), + institution_id: z.number().int().positive().nullable().optional(), + montant_emprunte: z.number().positive(), + capital_restant_du: z.number().nonnegative(), + taux_nominal: z.number().nullable().optional(), + date_debut: z.string().nullable().optional(), + duree_mois: z.number().int().positive().nullable().optional(), + mensualite: z.number().nullable().optional(), + notes: z.string().nullable().optional(), +}); + +router.get('/', (req, res) => { + const rows = db.prepare(` + SELECT e.id, e.nom, e.investisseur_id, e.institution_id, e.montant_emprunte, e.capital_restant_du, + e.taux_nominal, e.date_debut, e.duree_mois, e.mensualite, e.notes, e.created_at, + inv.nom AS investisseur_nom, inv.prenom AS investisseur_prenom, + inv.type AS investisseur_type, inv.type_fiscal AS investisseur_type_fiscal, + ir.nom AS institution_nom, ir.domiciliation AS institution_domiciliation, + ir.logo_filename AS institution_logo_filename, ir.icone_filename AS institution_icone_filename + FROM emprunts e + LEFT JOIN investisseurs inv ON inv.id = e.investisseur_id + LEFT JOIN institutions_referentiel ir ON ir.id = e.institution_id + WHERE e.user_id = ? + ORDER BY e.nom + `).all(req.user.id); + res.json(rows); +}); + +router.post('/', (req, res, next) => { + try { + const body = Schema.parse(req.body); + if (body.capital_restant_du > body.montant_emprunte) { + throw new HttpError(400, 'Le capital restant dû ne peut pas dépasser le montant emprunté'); + } + const r = db.prepare(` + INSERT INTO emprunts (user_id, nom, investisseur_id, institution_id, montant_emprunte, capital_restant_du, taux_nominal, date_debut, duree_mois, mensualite, notes) + VALUES (?,?,?,?,?,?,?,?,?,?,?) + `).run( + req.user.id, body.nom, body.investisseur_id ?? null, body.institution_id ?? null, + body.montant_emprunte, body.capital_restant_du, body.taux_nominal ?? null, + body.date_debut || null, body.duree_mois ?? null, body.mensualite ?? null, body.notes || null, + ); + res.status(201).json({ id: r.lastInsertRowid, ...body }); + } catch (e) { next(e); } +}); + +router.put('/:id', (req, res, next) => { + try { + const body = Schema.parse(req.body); + if (body.capital_restant_du > body.montant_emprunte) { + throw new HttpError(400, 'Le capital restant dû ne peut pas dépasser le montant emprunté'); + } + const r = db.prepare(` + UPDATE emprunts + SET nom=?, investisseur_id=?, institution_id=?, montant_emprunte=?, capital_restant_du=?, + taux_nominal=?, date_debut=?, duree_mois=?, mensualite=?, notes=?, updated_at=datetime('now') + WHERE id=? AND user_id=? + `).run( + body.nom, body.investisseur_id ?? null, body.institution_id ?? null, + body.montant_emprunte, body.capital_restant_du, body.taux_nominal ?? null, + body.date_debut || null, body.duree_mois ?? null, body.mensualite ?? null, body.notes || null, + req.params.id, req.user.id, + ); + if (r.changes === 0) throw new HttpError(404, 'Not found'); + res.json({ id: Number(req.params.id), ...body }); + } catch (e) { next(e); } +}); + +router.delete('/:id', (req, res, next) => { + try { + const r = db.prepare('DELETE FROM emprunts WHERE id=? AND user_id=?') + .run(req.params.id, req.user.id); + if (r.changes === 0) throw new HttpError(404, 'Not found'); + res.status(204).end(); + } catch (e) { next(e); } +}); + +export default router; diff --git a/backend/src/routes/enveloppesReferentiel.js b/backend/src/routes/enveloppesReferentiel.js index 73d78c0..52a0e84 100644 --- a/backend/src/routes/enveloppesReferentiel.js +++ b/backend/src/routes/enveloppesReferentiel.js @@ -25,12 +25,13 @@ const router = Router(); // Toutes ces routes sont montées sous requireAdmin dans server.js const Schema = z.object({ - nom: z.string().min(1).max(200), - description: z.string().nullable().optional(), - plafond_eur: z.number().positive().nullable().optional(), - regime_fiscal: z.string().nullable().optional(), - categories_ids: z.array(z.number().int()).optional().default([]), - supports_ids: z.array(z.number().int()).optional().default([]), + nom: z.string().min(1).max(200), + description: z.string().nullable().optional(), + plafond_eur: z.number().positive().nullable().optional(), + regime_fiscal: z.string().nullable().optional(), + eligible_compte: z.boolean().optional().default(false), + categories_ids: z.array(z.number().int()).optional().default([]), + supports_ids: z.array(z.number().int()).optional().default([]), }); // ── Helpers ──────────────────────────────────────────────────────────────── @@ -114,9 +115,9 @@ router.post('/', (req, res, next) => { if (existing) throw new HttpError(409, `Une enveloppe "${data.nom}" existe déjà`); const r = db.prepare(` - INSERT INTO enveloppes_referentiel (nom, description, plafond_eur, regime_fiscal, updated_at) - VALUES (?,?,?,?, datetime('now')) - `).run(data.nom, data.description || null, data.plafond_eur ?? null, data.regime_fiscal || null); + INSERT INTO enveloppes_referentiel (nom, description, plafond_eur, regime_fiscal, eligible_compte, updated_at) + VALUES (?,?,?,?,?, datetime('now')) + `).run(data.nom, data.description || null, data.plafond_eur ?? null, data.regime_fiscal || null, data.eligible_compte ? 1 : 0); const enveloppeId = r.lastInsertRowid; db.transaction(() => { @@ -144,9 +145,9 @@ router.put('/:id', (req, res, next) => { db.prepare(` UPDATE enveloppes_referentiel - SET nom=?, description=?, plafond_eur=?, regime_fiscal=?, updated_at=datetime('now') + SET nom=?, description=?, plafond_eur=?, regime_fiscal=?, eligible_compte=?, updated_at=datetime('now') WHERE id=? - `).run(data.nom, data.description || null, data.plafond_eur ?? null, data.regime_fiscal || null, row.id); + `).run(data.nom, data.description || null, data.plafond_eur ?? null, data.regime_fiscal || null, data.eligible_compte ? 1 : 0, row.id); db.transaction(() => { saveCategories(row.id, data.categories_ids); diff --git a/backend/src/routes/enveloppesReferentielPublic.js b/backend/src/routes/enveloppesReferentielPublic.js new file mode 100644 index 0000000..6566d65 --- /dev/null +++ b/backend/src/routes/enveloppesReferentielPublic.js @@ -0,0 +1,46 @@ +import { Router } from 'express'; +import db from '../db/index.js'; + +/** + * routes/enveloppesReferentielPublic.js — lecture seule du référentiel "Enveloppes & Supports + * d'épargne" pour tous les utilisateurs authentifiés (pas requireAdmin), sur le modèle de + * institutionsReferentielPublic.js. Catalogue transverse : on expose ici une LISTE complète + * (avec catégories, pour regroupement par familles côté frontend), utilisée par le sélecteur + * "Enveloppe" de Paramètres > Mes comptes (Phase 3a, cf. plan de déploiement Enveloppes & + * Supports). Le champ `eligible_compte` permet au frontend de ne proposer, dans ce sélecteur, + * que le sous-ensemble "compte financier" — cadrage validé par Olivier via AskUserQuestion + * (28/09/26) — sans empêcher un futur écran (Phase 3b) de consommer la liste complète. + */ + +const router = Router(); +// Monté sous requireAuth (sans requireAdmin) — lecture seule. + +function attachCategories(rows) { + if (rows.length === 0) return rows; + const ids = rows.map(r => r.id); + const links = db.prepare(` + SELECT rc.enveloppe_id, c.id, c.nom + FROM referentiel_categories_enveloppes rc + JOIN categories_enveloppes c ON c.id = rc.categorie_id + WHERE rc.enveloppe_id IN (${ids.map(() => '?').join(',')}) + ORDER BY c.nom + `).all(...ids); + const map = {}; + for (const l of links) { + if (!map[l.enveloppe_id]) map[l.enveloppe_id] = []; + map[l.enveloppe_id].push({ id: l.id, nom: l.nom }); + } + return rows.map(r => ({ ...r, categories: map[r.id] || [] })); +} + +/* GET /api/enveloppes-referentiel-public — liste complète (lecture seule) */ +router.get('/', (_req, res) => { + const rows = db.prepare(` + SELECT id, nom, eligible_compte + FROM enveloppes_referentiel + ORDER BY nom + `).all(); + res.json(attachCategories(rows)); +}); + +export default router; diff --git a/backend/src/server.js b/backend/src/server.js index c35dbff..71e49fc 100644 --- a/backend/src/server.js +++ b/backend/src/server.js @@ -54,7 +54,9 @@ import institutionsReferentielRouter from './routes/institutionsReferentiel.js'; import institutionsReferentielPublicRouter from './routes/institutionsReferentielPublic.js'; import refCategoriesInstitutionsRouter from './routes/ref-categories-institutions.js'; import enveloppesReferentielRouter from './routes/enveloppesReferentiel.js'; +import enveloppesReferentielPublicRouter from './routes/enveloppesReferentielPublic.js'; import supportsReferentielRouter from './routes/supportsReferentiel.js'; +import empruntsRouter from './routes/emprunts.js'; import refCategoriesEnveloppesRouter from './routes/ref-categories-enveloppes.js'; import refCategoriesRouter from './routes/ref-categories.js'; import refSecteursRouter from './routes/ref-secteurs.js'; @@ -161,6 +163,7 @@ app.use('/api/reinvestissements', requireAuth, reinvestissementsRouter); app.use('/api/frais-operations', requireAuth, fraisOperationsRouter); app.use('/api/corrections', requireAuth, correctionsRouter); app.use('/api/comptes', requireAuth, comptesRouter); +app.use('/api/emprunts', requireAuth, empruntsRouter); app.use('/api/preferences', requireAuth, preferencesRouter); app.use('/api/icons', requireAuth, iconsRouter); app.use('/api/admin', requireAuth, requireAdmin, adminRouter); @@ -183,6 +186,7 @@ app.use('/api/ref-categories-institutions', requireAuth, requireAdmin, refCateg // catalogue admin transverse aux workspaces, catalogue seul pour l'instant (pas de // rattachement à l'existant), cf. enveloppesReferentiel.js. app.use('/api/enveloppes-referentiel', requireAuth, requireAdmin, enveloppesReferentielRouter); +app.use('/api/enveloppes-referentiel-public', requireAuth, enveloppesReferentielPublicRouter); app.use('/api/supports-referentiel', requireAuth, requireAdmin, supportsReferentielRouter); app.use('/api/ref-categories-enveloppes', requireAuth, requireAdmin, refCategoriesEnveloppesRouter); app.use('/api/ref-categories', requireAuth, requireAdmin, refCategoriesRouter); diff --git a/design/icones institutions/AMUN.PA.svg b/design/icones institutions/AMUN.PA.svg new file mode 100644 index 0000000..9d6aa1d --- /dev/null +++ b/design/icones institutions/AMUN.PA.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/design/icones institutions/AXA_Logo.svg.webp b/design/icones institutions/AXA_Logo.svg.webp new file mode 100644 index 0000000..4213819 Binary files /dev/null and b/design/icones institutions/AXA_Logo.svg.webp differ diff --git a/design/icones institutions/Abeille assurances.png b/design/icones institutions/Abeille assurances.png new file mode 100644 index 0000000..386319e Binary files /dev/null and b/design/icones institutions/Abeille assurances.png differ diff --git a/design/icones institutions/allianz-logo-transparent-free-png.webp b/design/icones institutions/allianz-logo-transparent-free-png.webp new file mode 100644 index 0000000..e779ba3 Binary files /dev/null and b/design/icones institutions/allianz-logo-transparent-free-png.webp differ diff --git a/design/icones institutions/allianz.png b/design/icones institutions/allianz.png new file mode 100644 index 0000000..8ee0455 Binary files /dev/null and b/design/icones institutions/allianz.png differ diff --git a/design/icones institutions/nickel-logo.jpg b/design/icones institutions/nickel-logo.jpg new file mode 100644 index 0000000..c7ff613 Binary files /dev/null and b/design/icones institutions/nickel-logo.jpg differ diff --git a/design/icones institutions/traderepublic.webp b/design/icones institutions/traderepublic.webp new file mode 100644 index 0000000..82e7249 Binary files /dev/null and b/design/icones institutions/traderepublic.webp differ diff --git a/design/icones institutions/wise.webp b/design/icones institutions/wise.webp new file mode 100644 index 0000000..b30e191 Binary files /dev/null and b/design/icones institutions/wise.webp differ diff --git a/frontend/src/pages/AdminEnveloppes.jsx b/frontend/src/pages/AdminEnveloppes.jsx index 087d58b..1708e2e 100644 --- a/frontend/src/pages/AdminEnveloppes.jsx +++ b/frontend/src/pages/AdminEnveloppes.jsx @@ -64,6 +64,7 @@ function EnveloppeModal({ row, onClose, onSaved }) { description: row?.description || '', plafond_eur: row?.plafond_eur != null ? String(row.plafond_eur) : '', regime_fiscal: row?.regime_fiscal || '', + eligible_compte: !!row?.eligible_compte, categories_ids: (row?.categories || []).map(c => c.id), supports_ids: (row?.supports || []).map(s => s.id), }); @@ -87,6 +88,7 @@ function EnveloppeModal({ row, onClose, onSaved }) { description: form.description.trim() || null, plafond_eur: plafond, regime_fiscal: form.regime_fiscal.trim() || null, + eligible_compte: !!form.eligible_compte, categories_ids: form.categories_ids || [], supports_ids: form.supports_ids || [], }; @@ -128,6 +130,18 @@ function EnveloppeModal({ row, onClose, onSaved }) { set('regime_fiscal', e.target.value)} placeholder="Ex : Exonéré IR/PS, Flat tax 30%…" /> +
+ À cocher pour les enveloppes qui ont le profil d'un vrai compte financier (banque, + courtier, assureur…) — pas pour les actifs détenus en direct (immobilier, crypto + auto-conservée, objets de collection…). +
+