Lien comptes courants / enveloppes

This commit is contained in:
ocroguennec committed 2026-09-28 10:02:22 +02:00
1 parent f507163369
commit 9591ae400e
13 files changed
+202 -79

No files matched your search

@@ -168,7 +168,7 @@ export default function InvestissementFormModal({
const handleMethodeChange = (methode) => {
const defaultCompte = methode === 'compte_courant'
? (comptesInvestisseur.find(c => c.type === 'compte_courant') ?? comptesInvestisseur[0])
? (comptesInvestisseur.find(c => c.compte_reglement_par_defaut) ?? comptesInvestisseur[0])
: null;
onFormPatch({ methode_remboursement: methode, nom_compte_courant: defaultCompte?.nom || '', compte_id: String(defaultCompte?.id || '') });
};
+2 -2
View File
@@ -123,7 +123,7 @@ export default function RembFormModal({
const modalTitle = title ?? (editingId ? 'Modifier le remboursement' : (locked ? 'Saisir le remboursement' : 'Nouveau remboursement'));
const handleMethodeChange = (m) => {
const def = m === 'compte_courant' ? (comptesInvestisseur?.find(c => c.type === 'compte_courant') ?? comptesInvestisseur?.[0]) : null;
const def = m === 'compte_courant' ? (comptesInvestisseur?.find(c => c.compte_reglement_par_defaut) ?? comptesInvestisseur?.[0]) : null;
onFormPatch({ methode_remboursement: m, compte_id: def ? String(def.id) : '' });
};
@@ -133,7 +133,7 @@ export default function RembFormModal({
// 'compte_courant' — même pattern que handleFraisReglementChange côté investissement.
const handleFraisModeChange = (mode) => {
onFieldChange('frais_mode_reglement', mode);
const def = mode === 'compte_courant' ? (comptesInvestisseur?.find(c => c.type === 'compte_courant') ?? comptesInvestisseur?.[0]) : null;
const def = mode === 'compte_courant' ? (comptesInvestisseur?.find(c => c.compte_reglement_par_defaut) ?? comptesInvestisseur?.[0]) : null;
onFormPatch({ frais_compte_id: def ? String(def.id) : '' });
};
+2 -2
View File
@@ -127,7 +127,7 @@ export default function InvestissementDetail() {
.then(comptes => {
setComptesInvestisseur(comptes);
if (form.methode_remboursement === 'compte_courant' && !form.compte_id) {
const def = comptes.find(c => c.type === 'compte_courant') ?? comptes[0];
const def = comptes.find(c => c.compte_reglement_par_defaut) ?? comptes[0];
if (def) setForm(f => ({ ...f, compte_id: String(def.id), nom_compte_courant: def.nom }));
}
})
@@ -148,7 +148,7 @@ export default function InvestissementDetail() {
.then(comptes => {
setComptesRembInvestisseur(comptes);
if (rembForm.methode_remboursement === 'compte_courant' && !rembForm.compte_id) {
const def = comptes.find(c => c.type === 'compte_courant') ?? comptes[0];
const def = comptes.find(c => c.compte_reglement_par_defaut) ?? comptes[0];
if (def) setRembForm(f => ({ ...f, compte_id: String(def.id) }));
}
})
+1 -1
View File
@@ -342,7 +342,7 @@ export default function Investissements() {
.then(comptes => {
setComptesInvestisseur(comptes);
if (form.methode_remboursement === 'compte_courant' && !form.compte_id) {
const def = comptes.find(c => c.type === 'compte_courant') ?? comptes[0];
const def = comptes.find(c => c.compte_reglement_par_defaut) ?? comptes[0];
if (def) setForm(f => ({ ...f, compte_id: String(def.id), nom_compte_courant: def.nom }));
}
})
+1 -1
View File
@@ -727,7 +727,7 @@ export default function Remboursements() {
if (next.methode_remboursement === 'compte_courant') {
// Priorité : compte lié à l'investissement, sinon premier compte_courant
const inherited = inv.compte_id ? comptes.find(c => c.id === inv.compte_id) : null;
const def = inherited ?? comptes.find(c => c.type === 'compte_courant') ?? comptes[0];
const def = inherited ?? comptes.find(c => c.compte_reglement_par_defaut) ?? comptes[0];
if (def) setForm(f => ({ ...f, compte_id: String(def.id) }));
}
})
+42 -38
View File
@@ -9,12 +9,7 @@ const EXONERATION_LABELS = {
aucune: 'Aucune',
pfnl_5ans: 'PFnl 5 ans',
};
const TYPE_COMPTE_LABELS = {
compte_courant: 'Compte courant',
pea_pme: 'PEA-PME',
};
const EMPTY_COMPTE = { nom: '', type: 'compte_courant', investisseur_id: null, institution_id: null, banque: '', enveloppe_id: null, exoneration_fiscale: 'aucune' };
const EMPTY_COMPTE = { nom: '', investisseur_id: null, institution_id: null, banque: '', enveloppe_id: null, exoneration_fiscale: 'aucune', compte_reglement_par_defaut: false };
// Référentiel institutions financières (Phase 2, 26/09/26) — logos servis au même endroit
// que ceux des plateformes (/api/logos/:filename), cf. institutionsReferentiel.js.
@@ -45,31 +40,22 @@ function CompteFormFields({ state, setter, investisseurs, institutions, envelopp
onChange={e => setter({ ...state, nom: e.target.value })}
placeholder="ex. Compte courant BNP" />
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 }}>
<div>
<label>Type *</label>
<select value={state.type} onChange={e => setter({ ...state, type: e.target.value })}>
<option value="compte_courant">Compte courant</option>
<option value="pea_pme">PEA-PME</option>
</select>
</div>
<div>
<label>Banque</label>
<select
value={state.institution_id ?? '__autre__'}
onChange={e => {
const v = e.target.value;
if (v === '__autre__') { setter({ ...state, institution_id: null }); return; }
const inst = institutions.find(i => i.id === Number(v));
setter({ ...state, institution_id: Number(v), banque: inst ? inst.nom : state.banque });
}}
>
<option value="__autre__">Autre (texte libre)</option>
{institutions.map(inst => (
<option key={inst.id} value={inst.id}>{inst.nom}</option>
))}
</select>
</div>
<div>
<label>Banque</label>
<select
value={state.institution_id ?? '__autre__'}
onChange={e => {
const v = e.target.value;
if (v === '__autre__') { setter({ ...state, institution_id: null }); return; }
const inst = institutions.find(i => i.id === Number(v));
setter({ ...state, institution_id: Number(v), banque: inst ? inst.nom : state.banque });
}}
>
<option value="__autre__">Autre (texte libre)</option>
{institutions.map(inst => (
<option key={inst.id} value={inst.id}>{inst.nom}</option>
))}
</select>
</div>
{isAutre && (
<div>
@@ -91,7 +77,7 @@ function CompteFormFields({ state, setter, investisseurs, institutions, envelopp
))}
</select>
<p style={{ margin: '4px 0 0', fontSize: 11, color: 'var(--text-muted)' }}>
Rattachement au référentiel (Admin &gt; Enveloppes &amp; supports) — informatif, sans effet sur le champ Type ci-dessus.
Rattachement au référentiel (Admin &gt; Enveloppes &amp; supports).
</p>
</div>
<div>
@@ -104,6 +90,18 @@ function CompteFormFields({ state, setter, investisseurs, institutions, envelopp
))}
</select>
</div>
<div>
<label style={{ display: 'flex', alignItems: 'center', gap: 6, cursor: 'pointer' }}>
<input type="checkbox" checked={!!state.compte_reglement_par_defaut} style={{ width: 'auto' }}
onChange={e => setter({ ...state, compte_reglement_par_defaut: e.target.checked })} />
Compte de règlement par défaut
</label>
<p style={{ margin: '2px 0 0', fontSize: 11, color: 'var(--text-muted)' }}>
Utilisé pour pré-remplir automatiquement le compte de règlement sur les remboursements
et frais de ce détenteur. Un seul compte par défaut par détenteur (28/09/26 — remplace
l'ancien champ Type).
</p>
</div>
<div>
<label>Exonération fiscale</label>
<select value={state.exoneration_fiscale ?? 'aucune'}
@@ -153,12 +151,12 @@ export default function ComptesSection() {
try {
await api.post('/comptes', {
nom: newCompte.nom,
type: newCompte.type,
banque: newCompte.banque || null,
institution_id: newCompte.institution_id ?? null,
enveloppe_id: newCompte.enveloppe_id ?? null,
investisseur_id: newCompte.investisseur_id ? Number(newCompte.investisseur_id) : null,
exoneration_fiscale: newCompte.exoneration_fiscale ?? 'aucune',
compte_reglement_par_defaut: !!newCompte.compte_reglement_par_defaut,
});
setShowNew(false); setNewCompte(EMPTY_COMPTE); reload();
} catch (ex) { setErr(ex.message); }
@@ -169,12 +167,12 @@ export default function ComptesSection() {
try {
await api.put(`/comptes/${editCompte.id}`, {
nom: editCompte.nom,
type: editCompte.type,
banque: editCompte.banque || null,
institution_id: editCompte.institution_id ?? null,
enveloppe_id: editCompte.enveloppe_id ?? null,
investisseur_id: editCompte.investisseur_id ? Number(editCompte.investisseur_id) : null,
exoneration_fiscale: editCompte.exoneration_fiscale ?? 'aucune',
compte_reglement_par_defaut: !!editCompte.compte_reglement_par_defaut,
});
setEditCompte(null); reload();
} catch (ex) { setErr(ex.message); }
@@ -182,7 +180,7 @@ export default function ComptesSection() {
const openEdit = (c) => {
setErr(null);
setEditCompte({ id: c.id, nom: c.nom, type: c.type, banque: c.banque || '', institution_id: c.institution_id ?? null, enveloppe_id: c.enveloppe_id ?? null, investisseur_id: c.investisseur_id ?? null, exoneration_fiscale: c.exoneration_fiscale ?? 'aucune' });
setEditCompte({ id: c.id, nom: c.nom, banque: c.banque || '', institution_id: c.institution_id ?? null, enveloppe_id: c.enveloppe_id ?? null, investisseur_id: c.investisseur_id ?? null, exoneration_fiscale: c.exoneration_fiscale ?? 'aucune', compte_reglement_par_defaut: !!c.compte_reglement_par_defaut });
};
const del = (c) => {
@@ -219,11 +217,11 @@ export default function ComptesSection() {
<thead>
<tr>
<th>Nom</th>
<th style={{ width: '13%' }}>Type</th>
{multiDetenteur && <th style={{ width: '18%' }}>Détenteur</th>}
<th style={{ width: '14%' }}>Banque</th>
<th style={{ width: '16%' }}>Enveloppe</th>
<th style={{ width: '10%' }}>Exonération</th>
<th style={{ width: '11%' }}>Défaut</th>
<th style={{ width: '9%' }}>Exonération</th>
<th style={{ width: 80 }} />
</tr>
</thead>
@@ -238,7 +236,6 @@ export default function ComptesSection() {
return (
<tr key={c.id}>
<td style={{ fontWeight: 500 }}>{c.nom}</td>
<td><span className="badge">{TYPE_COMPTE_LABELS[c.type] ?? c.type}</span></td>
{multiDetenteur && <td>{inv ? memberLabel(inv) : <span className="text-muted">—</span>}</td>}
<td>
{c.institution_id ? (
@@ -252,6 +249,13 @@ export default function ComptesSection() {
) : (c.banque || <span className="text-muted">—</span>)}
</td>
<td>{c.enveloppe_nom || <span className="text-muted">—</span>}</td>
<td>
{c.compte_reglement_par_defaut ? (
<span style={{ color: 'var(--primary)', fontWeight: 600, fontSize: 'var(--fs-sm)' }}>✓ Défaut</span>
) : (
<span className="text-muted" style={{ fontSize: 'var(--fs-sm)' }}>—</span>
)}
</td>
<td>
{c.exoneration_fiscale && c.exoneration_fiscale !== 'aucune' ? (
<span