Catégorisation des platformes et application de la fiscalité adapté (Impact PSAN , régime micro-BIC)

This commit is contained in:
ocroguennec committed 2026-08-30 16:53:04 +02:00
1 parent 8953d0bb35
commit 33fee587aa
10 files changed
+610 -53

No files matched your search

+11 -1
View File
@@ -44,7 +44,8 @@ function IconUpload() {
/* ── Constantes ───────────────────────────────────────────────── */
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 EMPTY_REF = { nom: '', url: '', domiciliation: 'france', fiscalite: 'flat_tax', taux_fiscalite_locale: '', type_produit_fiscal: '2TT', methode_remboursement: 'portefeuille', type_pret_defaut: '', freq_interets_defaut: '', description: '', categories: [], categories_inv_ids: [], secteurs_inv_ids: [] };
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: [] };
/* ── Modal création / édition référentiel ─────────────────────── */
function RefModal({ row, onClose, onSaved }) {
@@ -59,6 +60,7 @@ function RefModal({ row, onClose, onSaved }) {
fiscalite: row.fiscalite || 'flat_tax',
taux_fiscalite_locale: row.taux_fiscalite_locale != null ? String(row.taux_fiscalite_locale) : '',
type_produit_fiscal: row.type_produit_fiscal || '2TT',
categorie_plateforme: row.categorie_plateforme || 'psfp',
methode_remboursement: row.methode_remboursement || 'portefeuille',
type_pret_defaut: row.type_pret_defaut || '',
freq_interets_defaut: row.freq_interets_defaut || '',
@@ -93,6 +95,7 @@ function RefModal({ row, onClose, onSaved }) {
taux_fiscalite_locale: fiscalite === 'avec_fiscalite_locale' && form.taux_fiscalite_locale !== ''
? Number(form.taux_fiscalite_locale) : null,
type_produit_fiscal: form.type_produit_fiscal,
categorie_plateforme: form.categorie_plateforme || 'psfp',
methode_remboursement: form.methode_remboursement || 'portefeuille',
type_pret_defaut: form.type_pret_defaut || null,
freq_interets_defaut: form.freq_interets_defaut || null,
@@ -181,6 +184,13 @@ function RefModal({ row, onClose, onSaved }) {
<option value="2TR">2TR — Revenus assimilés</option>
</select>
</div>
<div>
<label>Catégorie de plateforme</label>
<select value={form.categorie_plateforme || 'psfp'} onChange={e => set('categorie_plateforme', e.target.value)}>
<option value="psfp">{CATEGORIE_PLATEFORME_LABELS.psfp}</option>
<option value="psan">{CATEGORIE_PLATEFORME_LABELS.psan}</option>
</select>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 12 }}>
<div>
<label>Méthode de remboursement</label>
+6
View File
@@ -35,6 +35,7 @@ const INV_TYPES = {
les_deux: 'Particulier & Entreprise',
};
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' };
// ── Composants helpers ────────────────────────────────────────────────────────
@@ -174,6 +175,7 @@ export default function PlatformeProfile() {
fiscalite: form.fiscalite,
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',
categorie_plateforme: form.categorie_plateforme || 'psfp',
logo_filename: data.logo_filename ?? null,
description: form.description || null,
categories: data.categories || [],
@@ -275,6 +277,9 @@ export default function PlatformeProfile() {
{countryLabel(data.domiciliation)}
</span>
)}
<span title="Modifiable depuis la fiche du référentiel (liste Plateformes)" style={{ padding: '3px 10px', borderRadius: 20, background: 'var(--surface-2)', color: 'var(--text-muted)', fontSize: 12, flexShrink: 0 }}>
{CATEGORIE_PLATEFORME_LABELS[data.categorie_plateforme] ?? CATEGORIE_PLATEFORME_LABELS.psfp}
</span>
</div>
{editing
? <textarea value={form.description ?? ''} onChange={setInput('description')}
@@ -782,6 +787,7 @@ function ProfilImportBlock({ data, id, onApplied, catsInv = [], secteursInv = []
fiscalite: data.fiscalite,
taux_fiscalite_locale: data.taux_fiscalite_locale ?? null,
type_produit_fiscal: data.type_produit_fiscal || '2TT',
categorie_plateforme: data.categorie_plateforme || 'psfp',
logo_filename: data.logo_filename ?? null,
description: data.description || null,
categories: data.categories || [],
+45 -14
View File
@@ -13,6 +13,7 @@ import CerfaRecapTable from '../components/CerfaRecapTable.jsx';
import Cerfa2778Preview from '../components/Cerfa2778Preview.jsx';
import Cerfa2047Preview from '../components/Cerfa2047Preview.jsx';
import Cerfa2042Preview from '../components/Cerfa2042Preview.jsx';
import Cerfa2042CProPreview from '../components/Cerfa2042CProPreview.jsx';
/* ── YearSelector ────────────────────────────────────────────── */
function YearSelector({ annee, setAnnee, availableYears }) {
@@ -229,13 +230,15 @@ export default function TaxReport() {
const [detailExpanded, setDetailExpanded] = useState(false);
const [cerfaExpanded, setCerfaExpanded] = useState(false);
const [hasPlateformes, setHasPlateformes] = useState(true); // true par défaut pour éviter un flash du mauvais message
const [hasEtranger, setHasEtranger] = useState(false); // au moins une plateforme étrangère configurée (pour l'onglet CERFA 2047 — indépendant de l'année sélectionnée, même principe que le 2778-SD)
const [hasEtranger, setHasEtranger] = useState(false); // au moins une plateforme étrangère PSFP configurée (pour l'onglet CERFA 2047 — indépendant de l'année sélectionnée, même principe que le 2778-SD ; les plateformes PSAN ne sont pas concernées)
const [hasPsan, setHasPsan] = useState(false); // au moins une plateforme PSAN (crypto) configurée, quelle que soit sa domiciliation (pour l'onglet CERFA 2042 C PRO — indépendant de l'année sélectionnée, même principe que hasEtranger)
/* ── Plateformes configurées ? (pour distinguer "pas de plateforme" de "pas encore de données") ── */
useEffect(() => {
api.get('/plateformes').then(p => {
setHasPlateformes((p || []).length > 0);
setHasEtranger((p || []).some(pl => pl.domiciliation && pl.domiciliation !== 'FR'));
setHasEtranger((p || []).some(pl => pl.domiciliation && pl.domiciliation !== 'FR' && (pl.categorie_plateforme ?? 'psfp') === 'psfp'));
setHasPsan((p || []).some(pl => (pl.categorie_plateforme ?? 'psfp') === 'psan'));
}).catch(() => {});
}, []);
@@ -366,19 +369,35 @@ export default function TaxReport() {
<div className="card text-muted">Chargement…</div>
) : (
<>
{!detailExpanded && !cerfaExpanded && data2042 && <div className="card">
<h3 style={{ marginTop: 0 }}>Cases fiscales 2042 — synthèse {annee}</h3>
<div className="kpi-grid">
{data2042.case_2TT > 0 && <div className="kpi"><div className="label">Case 2TT — Prêts participatifs (FR)</div><div className="value">{fmtEUR(data2042.case_2TT)}</div></div>}
{data2042.case_2TR > 0 && <div className="kpi"><div className="label">Case 2TR — Revenus fixes (FR + étranger)</div><div className="value">{fmtEUR(data2042.case_2TR)}</div></div>}
<div className="kpi"><div className="label">Case 2BH — Base CSG/CRDS</div><div className="value">{fmtEUR(data2042.case_2BH)}</div></div>
<div className="kpi"><div className="label">Case 2CK — Crédit d'impôt</div><div className="value" style={{ color: 'var(--success)' }}>{fmtEUR(data2042.case_2CK)}</div></div>
{data2042.case_2TY > 0 && <div className="kpi"><div className="label">Case 2TY — Pertes en capital</div><div className="value danger">{fmtEUR(data2042.case_2TY)}</div></div>}
{!detailExpanded && !cerfaExpanded && data2042 && (
<div style={{ display: 'flex', gap: 16, flexWrap: 'wrap', alignItems: 'stretch' }}>
<div className="card" style={{ flex: '3 1 420px' }}>
<h3 style={{ marginTop: 0 }}>Cases fiscales 2042 — synthèse {annee}</h3>
<div className="kpi-grid">
{data2042.case_2TT > 0 && <div className="kpi"><div className="label">Case 2TT — Prêts participatifs (FR)</div><div className="value">{fmtEUR(data2042.case_2TT)}</div></div>}
{data2042.case_2TR > 0 && <div className="kpi"><div className="label">Case 2TR — Revenus fixes (FR + étranger)</div><div className="value">{fmtEUR(data2042.case_2TR)}</div></div>}
<div className="kpi"><div className="label">Case 2BH — Base CSG/CRDS</div><div className="value">{fmtEUR(data2042.case_2BH)}</div></div>
<div className="kpi"><div className="label">Case 2CK — Crédit d'impôt</div><div className="value" style={{ color: 'var(--success)' }}>{fmtEUR(data2042.case_2CK)}</div></div>
{data2042.case_2TY > 0 && <div className="kpi"><div className="label">Case 2TY — Pertes en capital</div><div className="value danger">{fmtEUR(data2042.case_2TY)}</div></div>}
</div>
<p className="text-muted" style={{ marginTop: 12, fontSize: 12 }}>
⚠ Cases indicatives combinant plateformes françaises (IFU automatique) et étrangères. Référez-vous à la notice 2041-GFI.
</p>
</div>
{hasPsan && data?.cerfa2042cpro?.donnees2042cpro && (
<div className="card" style={{ flex: '1 1 220px' }}>
<h3 style={{ marginTop: 0 }}>Cases fiscales 2042 C PRO — synthèse {annee}</h3>
<div className="kpi-grid">
<div className="kpi"><div className="label">Case 5HQ — Micro-BNC (PSAN)</div><div className="value">{fmtEUR(data.cerfa2042cpro.donnees2042cpro.case_5HQ)}</div></div>
</div>
<p className="text-muted" style={{ marginTop: 12, fontSize: 12 }}>
⚠ Montant brut ; l'abattement de 34 % (régime micro-BNC) est appliqué automatiquement par l'administration.
</p>
</div>
)}
</div>
<p className="text-muted" style={{ marginTop: 12, fontSize: 12 }}>
⚠ Cases indicatives combinant plateformes françaises (IFU automatique) et étrangères. Référez-vous à la notice 2041-GFI.
</p>
</div>}
)}
{!detailExpanded && !cerfaExpanded && data2042 && (
<div className="card" style={{ marginTop: 16 }}>
@@ -409,6 +428,13 @@ export default function TaxReport() {
texte="Les intérêts perçus via des plateformes étrangères sont soumis à un prélèvement forfaitaire obligatoire (acompte) si vous avez un revenu fiscal de référence de l'avant-dernière année de 25 000 € pour un personne seule ou 50 000 € pour couple marié ou pacsé. Malgré la déclaration mensuelle aux autorités fiscales, la déclaration annuelle au travers du CERFA 2047 reste obligatoire."
/>
)}
{hasPsan && (
<InfoLigneCerfa
code="2042-C PRO"
titre="Intérêts d'emprunts sur une plateforme d'actif numérique (Crowdlending sur RWA)"
texte="Les intérêts perçus via des plateformes de type « Prestataire de Services sur Actifs Numériques » pour des prêts de type Crowdlending sur des RWA (Real World Asset) sont des bénéfices non commerciaux (BNC), quelle que soit leur localisation. Pas de déclaration CERFA 2047, ni de crédit d'impôt possible. Une déclaration directe en 2042-C PRO en régime micro-BNC (case 5HQ)."
/>
)}
</div>
<div style={{ flex: '1 1 320px', minWidth: 280, display: 'flex', justifyContent: 'center' }}>
<CerfaFlowDiagram hasEtranger={hasEtranger} />
@@ -445,6 +471,7 @@ export default function TaxReport() {
{!detailExpanded && !cerfaExpanded && (
<div className="dr-tabs">
<button className={`dr-tab${activeTab === '2042' ? ' active' : ''}`} onClick={() => setActiveTab('2042')}>CERFA 2042</button>
{hasPsan && <button className={`dr-tab${activeTab === '2042cpro' ? ' active' : ''}`} onClick={() => setActiveTab('2042cpro')}>CERFA 2042 C PRO</button>}
<button className={`dr-tab${activeTab === 'cerfa' ? ' active' : ''}`} onClick={() => setActiveTab('cerfa')}>CERFA 2561 (IFU)</button>
{hasEtranger && <button className={`dr-tab${activeTab === '2047' ? ' active' : ''}`} onClick={() => setActiveTab('2047')}>CERFA 2047</button>}
{pfoAssujetti && <button className={`dr-tab${activeTab === '2778' ? ' active' : ''}`} onClick={() => setActiveTab('2778')}>CERFA 2778-SD</button>}
@@ -473,6 +500,10 @@ export default function TaxReport() {
{activeTab === '2778' && pfoAssujetti && !detailExpanded && !cerfaExpanded && (
<Cerfa2778Preview annee={annee} activeView={activeView} />
)}
{activeTab === '2042cpro' && hasPsan && !detailExpanded && !cerfaExpanded && (
<Cerfa2042CProPreview annee={annee} activeView={activeView} />
)}
</>
);
}
@@ -24,6 +24,7 @@ function localToday() {
}
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 FISCALITE_LABELS = {
flat_tax: 'Flat Tax',
sans_fiscalite_locale: 'Sans fiscalité locale',
@@ -51,7 +52,7 @@ function fmtFiscalite(p) {
return base;
}
const EMPTY_PLAT = { nom: '', url: '', domiciliation: 'france', fiscalite: 'flat_tax', taux_fiscalite_locale: '', type_produit_fiscal: '2TT', 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', 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 logoUrl = (filename) => filename ? LOGO_BASE + filename : null;
@@ -211,6 +212,7 @@ function PlatDetailPanel({ plat, onEdit }) {
plat.date_ouverture && { label: "Date d'ouverture", value: fmtDate(plat.date_ouverture) },
{ label: 'Domiciliation', value: plat.domiciliation ? <span style={{ display:'inline-flex', alignItems:'center', gap:5 }}><FlagIcon code={plat.domiciliation} size={16} />{countryLabel(plat.domiciliation)}</span> : '—' },
{ label: 'Fiscalité', value: fmtFiscalite(plat) },
{ label: 'Catégorie de plateforme', value: CATEGORIE_PLATEFORME_LABELS[plat.categorie_plateforme] ?? CATEGORIE_PLATEFORME_LABELS.psfp },
plat.domiciliation === 'FR' && {
label: 'Déclaration 2561',
value: (plat.type_produit_fiscal ?? '2TT') === '2TR'
@@ -581,6 +583,15 @@ function PlatForm({ state, setter, logoFile, setLogoFile, logoPreview, setLogoPr
</select>
))}
{/* 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>
))}
{/* Méthode de remboursement */}
{row('Versement des remboursements', (
<select value={state.methode_remboursement || 'portefeuille'} onChange={e => setter(s => ({ ...s, methode_remboursement: e.target.value }))}
@@ -848,6 +859,7 @@ export default function PlateformesSection() {
fiscalite: ref.fiscalite || 'flat_tax',
taux_fiscalite_locale: ref.taux_fiscalite_locale ?? null,
type_produit_fiscal: ref.type_produit_fiscal || '2TT',
categorie_plateforme: ref.categorie_plateforme || 'psfp',
methode_remboursement: ref.methode_remboursement || 'portefeuille',
type_pret_defaut: ref.type_pret_defaut || null,
freq_interets_defaut: ref.freq_interets_defaut || null,
@@ -952,6 +964,7 @@ export default function PlateformesSection() {
fiscalite: p.fiscalite || 'flat_tax',
taux_fiscalite_locale: p.taux_fiscalite_locale ?? '',
type_produit_fiscal: p.type_produit_fiscal || '2TT',
categorie_plateforme: p.categorie_plateforme || 'psfp',
methode_remboursement: p.methode_remboursement || 'portefeuille',
investisseur_id: p.investisseur_id ?? null,
date_ouverture: p.date_ouverture || '',
@@ -974,6 +987,7 @@ export default function PlateformesSection() {
taux_fiscalite_locale: editPlat.fiscalite === 'avec_fiscalite_locale' && editPlat.taux_fiscalite_locale !== ''
? Number(editPlat.taux_fiscalite_locale) : null,
type_produit_fiscal: editPlat.type_produit_fiscal || '2TT',
categorie_plateforme: editPlat.categorie_plateforme || 'psfp',
methode_remboursement: editPlat.methode_remboursement || 'portefeuille',
investisseur_id: editPlat.investisseur_id ? Number(editPlat.investisseur_id) : null,
date_ouverture: editPlat.date_ouverture || null,