Ajout du type Intérêt plateforme pour les remboursements
This commit is contained in:
1 parent
37f9f8b8a7
commit
1bab97b5b3
9 files changed
+248
-66
No files matched your search
@@ -15,7 +15,7 @@ import { fmtEUR } from '../utils/format.js';
|
||||
* en dessous, et se ferme au scroll/resize (repositionnement non géré en direct, comme InvSelect).
|
||||
*
|
||||
* Props :
|
||||
* value : '' | 'BONUS_PARRAINAGE' | 'BONUS_PLATEFORME' | id (string/number)
|
||||
* value : '' | 'BONUS_PARRAINAGE' | 'BONUS_PLATEFORME' | 'INTERETS_PLATEFORME' | id (string/number)
|
||||
* onChange : (value) => void
|
||||
* options : investissementOptions — { id, plateforme_nom, nom_projet, montant_investi }[]
|
||||
*/
|
||||
@@ -29,6 +29,7 @@ export default function InvestissementSelect({ value, onChange, options = [] })
|
||||
const selectedOption = options.find(i => String(i.id) === String(value));
|
||||
const label = value === 'BONUS_PARRAINAGE' ? '🎁 Bonus Parrainage'
|
||||
: value === 'BONUS_PLATEFORME' ? '🏆 Bonus Plateforme'
|
||||
: value === 'INTERETS_PLATEFORME' ? '🏦 Intérêts plateforme'
|
||||
: selectedOption ? `${selectedOption.plateforme_nom} — ${selectedOption.nom_projet} (${fmtEUR(selectedOption.montant_investi)})`
|
||||
: null;
|
||||
|
||||
@@ -123,6 +124,7 @@ export default function InvestissementSelect({ value, onChange, options = [] })
|
||||
<button type="button" style={rowStyle(value === '')} onClick={() => select('')}>—</button>
|
||||
<button type="button" style={rowStyle(value === 'BONUS_PARRAINAGE')} onClick={() => select('BONUS_PARRAINAGE')}>🎁 Bonus Parrainage</button>
|
||||
<button type="button" style={rowStyle(value === 'BONUS_PLATEFORME')} onClick={() => select('BONUS_PLATEFORME')}>🏆 Bonus Plateforme</button>
|
||||
<button type="button" style={rowStyle(value === 'INTERETS_PLATEFORME')} onClick={() => select('INTERETS_PLATEFORME')}>🏦 Intérêts plateforme</button>
|
||||
<div className="cat-select-sep" />
|
||||
{options.map(i => (
|
||||
<button key={i.id} type="button" style={rowStyle(String(value) === String(i.id))} onClick={() => select(String(i.id))}>
|
||||
|
||||
@@ -38,6 +38,7 @@ export const MOUV_TYPE_LABELS = {
|
||||
remboursement: 'Remboursement', bonus: 'Bonus', cashback: 'Cashback', souscription: 'Souscription',
|
||||
prelevement_ir: 'Prélèvement IR', prelevement_csg: 'Prélèvement CSG',
|
||||
prelevement_local: "Prélèvement impôts à l'étranger",
|
||||
interets_plateforme: 'Intérêts plateforme',
|
||||
};
|
||||
|
||||
export function mouvToCSV(rows) {
|
||||
@@ -147,7 +148,8 @@ export function DetailPanel({ row, onEdit, onDeleteCorrection, onViewRemb, onVie
|
||||
const isCorrection = row.type === 'correction';
|
||||
const isBonus = row.type === 'bonus';
|
||||
const isCashback = row.type === 'cashback';
|
||||
const isRemb = row.type === 'remboursement' || isBonus || isCashback;
|
||||
const isInteretsPlateforme = row.type === 'interets_plateforme';
|
||||
const isRemb = row.type === 'remboursement' || isBonus || isCashback || isInteretsPlateforme;
|
||||
const isSouscription = row.type === 'souscription';
|
||||
const isPrelevement = row.type === 'prelevement_ir' || row.type === 'prelevement_csg' || row.type === 'prelevement_local';
|
||||
const isAuto = row.source === 'auto_remboursement';
|
||||
@@ -156,6 +158,7 @@ export function DetailPanel({ row, onEdit, onDeleteCorrection, onViewRemb, onVie
|
||||
: isBonus ? 'Bonus'
|
||||
: isCashback ? 'Cashback'
|
||||
: row.type === 'remboursement' ? 'Remboursement (porte-monnaie, brut)'
|
||||
: isInteretsPlateforme ? 'Intérêts plateforme (porte-monnaie, brut)'
|
||||
: row.type === 'prelevement_ir' ? 'Prélèvement impôt sur le revenu'
|
||||
: row.type === 'prelevement_csg' ? 'Prélèvement CSG'
|
||||
: row.type === 'prelevement_local' ? "Prélèvement impôts à l'étranger"
|
||||
@@ -173,7 +176,7 @@ export function DetailPanel({ row, onEdit, onDeleteCorrection, onViewRemb, onVie
|
||||
const montantFmt = `${sign} ${fmtEUR(Math.abs(row.montant))}`;
|
||||
const montantColor = undefined;
|
||||
|
||||
const isRembOnly = row.type === 'remboursement';
|
||||
const isRembOnly = row.type === 'remboursement' || isInteretsPlateforme;
|
||||
|
||||
const fields = [
|
||||
{ label: 'Plateforme', value: row.plateforme_nom },
|
||||
@@ -182,7 +185,7 @@ export function DetailPanel({ row, onEdit, onDeleteCorrection, onViewRemb, onVie
|
||||
{ label: 'Date', value: fmtDate(row.date_operation) },
|
||||
isRembOnly && row.libelle && { label: 'Projet', value: row.libelle },
|
||||
(isBonus || isCashback) && row.libelle && { label: 'Projet', value: row.libelle },
|
||||
isRembOnly && { label: 'Capital', value: fmtEUR(row._capital) },
|
||||
isRembOnly && !isInteretsPlateforme && { label: 'Capital', value: fmtEUR(row._capital) },
|
||||
isRembOnly && row._cashback > 0 && { label: 'Cashback', value: fmtEUR(row._cashback) },
|
||||
isRembOnly && { label: 'Intérêts bruts', value: fmtEUR(row._interets_bruts) },
|
||||
isRembOnly && { label: 'Traitement fiscal', value: row._isFlatTax
|
||||
@@ -243,9 +246,11 @@ export function DetailPanel({ row, onEdit, onDeleteCorrection, onViewRemb, onVie
|
||||
? 'Bonus crédité directement au porte-monnaie de la plateforme.'
|
||||
: isCashback
|
||||
? "Cashback crédité au porte-monnaie de la plateforme, saisi sur un remboursement classique plutôt qu'en tant que bonus. Modifiable depuis la page Remboursements."
|
||||
: row._isAutoSweep
|
||||
? "Remboursement (montant brut) crédité sur le porte-monnaie puis reversé aussitôt sur le compte courant (retrait automatique associé). Les prélèvements éventuels apparaissent en lignes séparées. Modifiable depuis la page Remboursements."
|
||||
: "Montant brut du remboursement versé sur le porte-monnaie de la plateforme (méthode « portefeuille ») — les prélèvements éventuels apparaissent en lignes séparées. Modifiable depuis la page Remboursements."}
|
||||
: isInteretsPlateforme
|
||||
? "Intérêts versés par la plateforme, hors investissement précis — crédités au porte-monnaie (montant brut). Les prélèvements éventuels apparaissent en lignes séparées. Modifiable depuis la page Remboursements."
|
||||
: row._isAutoSweep
|
||||
? "Remboursement (montant brut) crédité sur le porte-monnaie puis reversé aussitôt sur le compte courant (retrait automatique associé). Les prélèvements éventuels apparaissent en lignes séparées. Modifiable depuis la page Remboursements."
|
||||
: "Montant brut du remboursement versé sur le porte-monnaie de la plateforme (méthode « portefeuille ») — les prélèvements éventuels apparaissent en lignes séparées. Modifiable depuis la page Remboursements."}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -20,6 +20,7 @@ const getRatesForYear = (dateStr, pfuRates) => {
|
||||
};
|
||||
|
||||
const BONUS_VALUES = ['BONUS_PARRAINAGE', 'BONUS_PLATEFORME'];
|
||||
const INTERETS_PLATEFORME_VALUE = 'INTERETS_PLATEFORME';
|
||||
|
||||
/**
|
||||
* RembFormModal — formulaire partagé de saisie/édition d'un remboursement.
|
||||
@@ -67,6 +68,9 @@ export default function RembFormModal({
|
||||
confirmingDelete, setConfirmingDelete,
|
||||
}) {
|
||||
const isBonus = !locked && BONUS_VALUES.includes(form.investissement_id);
|
||||
const isInteretsPlateforme = !locked && form.investissement_id === INTERETS_PLATEFORME_VALUE;
|
||||
// Modes "plateforme" (pas d'investissement précis) : bonus classique ou intérêts plateforme
|
||||
const isPlateformeMode = isBonus || isInteretsPlateforme;
|
||||
|
||||
const rates = getRatesForYear(form.date_remb, pfuRates);
|
||||
const labelPS = `Prélèvements sociaux${rates ? ` (${fmtRate(rates.prelev_sociaux)})` : ''} (€)`;
|
||||
@@ -75,8 +79,10 @@ export default function RembFormModal({
|
||||
|
||||
const currentInv = locked
|
||||
? investissements[0]
|
||||
: (!isBonus ? investissements.find(i => i.id === Number(form.investissement_id)) : null);
|
||||
const currentPlat = currentInv ? plateformes.find(p => p.id === currentInv.plateforme_id) : null;
|
||||
: (!isPlateformeMode ? investissements.find(i => i.id === Number(form.investissement_id)) : null);
|
||||
const currentPlat = isInteretsPlateforme
|
||||
? plateformes.find(p => String(p.id) === String(form.bonus_plateforme_id))
|
||||
: (currentInv ? plateformes.find(p => p.id === currentInv.plateforme_id) : null);
|
||||
const hasLocalTax = !isBonus && currentPlat?.fiscalite === 'avec_fiscalite_locale' && currentPlat?.taux_fiscalite_locale;
|
||||
const isExonere = !isBonus && currentInv?.fiscalite_override === 'exonere';
|
||||
const isIndicatif = Boolean(isExonere || (!isBonus && currentPlat && currentPlat.fiscalite !== 'flat_tax'));
|
||||
@@ -127,7 +133,7 @@ export default function RembFormModal({
|
||||
|
||||
{/* ── Sélecteur d'investissement (mode non verrouillé uniquement) ── */}
|
||||
{!locked && (
|
||||
<div style={{ gridColumn: isBonus ? 'span 1' : 'span 3' }}>
|
||||
<div style={{ gridColumn: isPlateformeMode ? 'span 1' : 'span 3' }}>
|
||||
<label>Investissement *</label>
|
||||
<InvestissementSelect
|
||||
value={form.investissement_id}
|
||||
@@ -136,7 +142,7 @@ export default function RembFormModal({
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{!locked && isBonus && (
|
||||
{!locked && isPlateformeMode && (
|
||||
<div>
|
||||
<label>Plateforme associée</label>
|
||||
<select value={form.bonus_plateforme_id} onChange={e => {
|
||||
@@ -156,7 +162,7 @@ export default function RembFormModal({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ── Champs remboursement normal ── */}
|
||||
{/* ── Champs remboursement normal / intérêts plateforme ── */}
|
||||
{!isBonus && (<>
|
||||
<div style={{ gridColumn: 'span 3', borderTop: '1px solid var(--border)', paddingTop: 4, marginTop: 4 }}>
|
||||
<span style={{ fontSize: 11, fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.06em', color: 'var(--text-muted)' }}>Remboursement</span>
|
||||
@@ -175,11 +181,13 @@ export default function RembFormModal({
|
||||
onChange={e => onFieldChange('taxe_locale', e.target.value)} />
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<label>Capital (€)</label>
|
||||
<input type="number" step="0.01" value={form.capital ?? 0}
|
||||
onChange={e => onFieldChange('capital', e.target.value)} />
|
||||
</div>
|
||||
{!isInteretsPlateforme && (
|
||||
<div>
|
||||
<label>Capital (€)</label>
|
||||
<input type="number" step="0.01" value={form.capital ?? 0}
|
||||
onChange={e => onFieldChange('capital', e.target.value)} />
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<label>{hasLocalTax ? 'Intérêts bruts après retenue locale (€)' : 'Intérêts bruts (€)'}</label>
|
||||
<input type="number" step="0.01" value={form.interets_bruts ?? 0}
|
||||
@@ -187,11 +195,13 @@ export default function RembFormModal({
|
||||
style={hasLocalTax ? { background: 'var(--surface-2)', color: 'var(--text-muted)', cursor: 'not-allowed' } : undefined}
|
||||
onChange={e => !hasLocalTax && onFieldChange('interets_bruts', e.target.value)} />
|
||||
</div>
|
||||
<div>
|
||||
<label>Cashback (€)</label>
|
||||
<input type="number" step="0.01" value={form.cashback ?? 0}
|
||||
onChange={e => onFieldChange('cashback', e.target.value)} />
|
||||
</div>
|
||||
{!isInteretsPlateforme && (
|
||||
<div>
|
||||
<label>Cashback (€)</label>
|
||||
<input type="number" step="0.01" value={form.cashback ?? 0}
|
||||
onChange={e => onFieldChange('cashback', e.target.value)} />
|
||||
</div>
|
||||
)}
|
||||
<div style={{ gridColumn: 'span 3', borderTop: '1px solid var(--border)', paddingTop: 4, marginTop: 4 }}>
|
||||
<span style={{ fontSize: 11, fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.06em', color: 'var(--text-muted)' }}>Imposition{isIndicatif ? ' — indicatif' : ''}</span>
|
||||
</div>
|
||||
@@ -228,16 +238,18 @@ export default function RembFormModal({
|
||||
<input type="date" required value={form.date_remb || ''}
|
||||
onChange={e => onFieldChange('date_remb', e.target.value)} />
|
||||
</div>
|
||||
{!isInteretsPlateforme && (
|
||||
<div>
|
||||
<label>Méthode de remboursement</label>
|
||||
<select value={form.methode_remboursement || 'portefeuille'}
|
||||
onChange={e => handleMethodeChange(e.target.value)}>
|
||||
<option value="portefeuille">Porte-monnaie de la plateforme</option>
|
||||
<option value="compte_courant">Compte courant de l'investisseur</option>
|
||||
</select>
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<label>Méthode de remboursement</label>
|
||||
<select value={form.methode_remboursement || 'portefeuille'}
|
||||
onChange={e => handleMethodeChange(e.target.value)}>
|
||||
<option value="portefeuille">Porte-monnaie de la plateforme</option>
|
||||
<option value="compte_courant">Compte courant de l'investisseur</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
{form.methode_remboursement === 'compte_courant' && (
|
||||
{!isInteretsPlateforme && form.methode_remboursement === 'compte_courant' && (
|
||||
<>
|
||||
<label>Compte de réception</label>
|
||||
{comptesInvestisseur?.length > 0 ? (
|
||||
@@ -276,10 +288,12 @@ export default function RembFormModal({
|
||||
onChange={e => onFieldChange('cashback', e.target.value)} />
|
||||
</div>
|
||||
)}
|
||||
{!locked && isBonus && (
|
||||
<div style={{ gridColumn: 'span 2' }}>
|
||||
{!locked && isPlateformeMode && (
|
||||
<div style={{ gridColumn: isBonus ? 'span 2' : 'span 3' }}>
|
||||
<label>Projet / Commentaire</label>
|
||||
<input type="text" placeholder="Nom du projet sollicité par le filleul…" value={form.notes || ''}
|
||||
<input type="text"
|
||||
placeholder={isBonus ? 'Nom du projet sollicité par le filleul…' : 'Commentaire…'}
|
||||
value={form.notes || ''}
|
||||
onChange={e => onFieldChange('notes', e.target.value)} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
Reference in new issue
Block a user