Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1bab97b5b3 | ||
|
|
37f9f8b8a7 | ||
|
|
014adaf22e | ||
|
|
330ccaa9d2 |
No files matched your search
@@ -2301,4 +2301,43 @@ db.exec('CREATE INDEX IF NOT EXISTS idx_api_keys_hash ON api_keys(key_hash)');
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Migration : v_interets_annuels — inclure les "Intérêts plateforme" ──
|
||||||
|
// Nouveau type de remboursement (intérêts versés par une plateforme, hors
|
||||||
|
// investissement précis, rattachés via bonus_plateforme_id/bonus_investisseur_id
|
||||||
|
// comme les bonus). Contrairement aux bonus (cashback non imposable), ce sont
|
||||||
|
// de vrais intérêts fiscalisés : ils doivent compter dans le récap annuel au
|
||||||
|
// même titre que les intérêts d'investissement classiques.
|
||||||
|
{
|
||||||
|
const viewRow = db.prepare(
|
||||||
|
"SELECT sql FROM sqlite_master WHERE type='view' AND name='v_interets_annuels'"
|
||||||
|
).get();
|
||||||
|
if (viewRow && !viewRow.sql.includes('interets_plateforme')) {
|
||||||
|
db.exec('DROP VIEW IF EXISTS v_interets_annuels');
|
||||||
|
db.exec(`
|
||||||
|
CREATE VIEW v_interets_annuels AS
|
||||||
|
SELECT investisseur_id, annee,
|
||||||
|
SUM(interets_bruts) AS interets_bruts,
|
||||||
|
SUM(prelev_sociaux) AS prelev_sociaux,
|
||||||
|
SUM(prelev_forfaitaire) AS prelev_forfaitaire,
|
||||||
|
SUM(net_recu) AS net_recu
|
||||||
|
FROM (
|
||||||
|
SELECT i.investisseur_id AS investisseur_id,
|
||||||
|
substr(r.date_remb,1,4) AS annee,
|
||||||
|
r.interets_bruts, r.prelev_sociaux, r.prelev_forfaitaire, r.net_recu
|
||||||
|
FROM remboursements r
|
||||||
|
JOIN investissements i ON i.id = r.investissement_id
|
||||||
|
WHERE r.statut IN ('paye','partiel') AND r.type = 'normal'
|
||||||
|
UNION ALL
|
||||||
|
SELECT r.bonus_investisseur_id AS investisseur_id,
|
||||||
|
substr(r.date_remb,1,4) AS annee,
|
||||||
|
r.interets_bruts, r.prelev_sociaux, r.prelev_forfaitaire, r.net_recu
|
||||||
|
FROM remboursements r
|
||||||
|
WHERE r.statut IN ('paye','partiel') AND r.type = 'interets_plateforme'
|
||||||
|
)
|
||||||
|
GROUP BY investisseur_id, annee
|
||||||
|
`);
|
||||||
|
console.log('[DB] v_interets_annuels: vue recréée (inclut interets_plateforme)');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default db;
|
export default db;
|
||||||
@@ -92,6 +92,17 @@ router.get('/', (req, res) => {
|
|||||||
GROUP BY bonus_plateforme_id
|
GROUP BY bonus_plateforme_id
|
||||||
`).all(...invParams);
|
`).all(...invParams);
|
||||||
|
|
||||||
|
// Intérêts plateforme : comme les bonus, ils créditent directement le
|
||||||
|
// porte-monnaie de la plateforme (net_recu tient déjà compte des prélèvements).
|
||||||
|
const interetsPlateformeWalletPerPlat = db.prepare(`
|
||||||
|
SELECT bonus_plateforme_id AS plateforme_id,
|
||||||
|
COALESCE(SUM(net_recu), 0) AS interets_plateforme_wallet
|
||||||
|
FROM remboursements
|
||||||
|
WHERE ${invWhere.replace('investisseur_id', 'bonus_investisseur_id')}
|
||||||
|
AND type = 'interets_plateforme'
|
||||||
|
GROUP BY bonus_plateforme_id
|
||||||
|
`).all(...invParams);
|
||||||
|
|
||||||
const capitalInvestiPerPlat = db.prepare(`
|
const capitalInvestiPerPlat = db.prepare(`
|
||||||
SELECT i.plateforme_id,
|
SELECT i.plateforme_id,
|
||||||
COALESCE(SUM(
|
COALESCE(SUM(
|
||||||
@@ -117,6 +128,7 @@ router.get('/', (req, res) => {
|
|||||||
for (const r of drManuelPerPlat) walletMap[r.plateforme_id] = (walletMap[r.plateforme_id] || 0) + r.depots - r.retraits_manuels;
|
for (const r of drManuelPerPlat) walletMap[r.plateforme_id] = (walletMap[r.plateforme_id] || 0) + r.depots - r.retraits_manuels;
|
||||||
for (const r of rembWalletPerPlat) walletMap[r.plateforme_id] = (walletMap[r.plateforme_id] || 0) + r.remb_wallet;
|
for (const r of rembWalletPerPlat) walletMap[r.plateforme_id] = (walletMap[r.plateforme_id] || 0) + r.remb_wallet;
|
||||||
for (const r of bonusWalletPerPlat) walletMap[r.plateforme_id] = (walletMap[r.plateforme_id] || 0) + r.bonus_wallet;
|
for (const r of bonusWalletPerPlat) walletMap[r.plateforme_id] = (walletMap[r.plateforme_id] || 0) + r.bonus_wallet;
|
||||||
|
for (const r of interetsPlateformeWalletPerPlat) walletMap[r.plateforme_id] = (walletMap[r.plateforme_id] || 0) + r.interets_plateforme_wallet;
|
||||||
for (const r of capitalInvestiPerPlat) walletMap[r.plateforme_id] = (walletMap[r.plateforme_id] || 0) - r.capital_investi;
|
for (const r of capitalInvestiPerPlat) walletMap[r.plateforme_id] = (walletMap[r.plateforme_id] || 0) - r.capital_investi;
|
||||||
for (const r of correctionsPerPlat) walletMap[r.plateforme_id] = (walletMap[r.plateforme_id] || 0) + r.total_correction;
|
for (const r of correctionsPerPlat) walletMap[r.plateforme_id] = (walletMap[r.plateforme_id] || 0) + r.total_correction;
|
||||||
|
|
||||||
@@ -149,6 +161,10 @@ router.get('/', (req, res) => {
|
|||||||
`).get(...invParams);
|
`).get(...invParams);
|
||||||
|
|
||||||
/* ── Interets ────────────────────────────────────────────────── */
|
/* ── Interets ────────────────────────────────────────────────── */
|
||||||
|
// Inclut les remboursements normaux (liés à un investissement) et les
|
||||||
|
// "Intérêts plateforme" (type=interets_plateforme, rattachés via
|
||||||
|
// bonus_plateforme_id/bonus_investisseur_id comme les bonus, mais ce sont
|
||||||
|
// de vrais intérêts fiscalisés — contrairement aux bonus qui restent exclus).
|
||||||
const interets = db.prepare(`
|
const interets = db.prepare(`
|
||||||
SELECT
|
SELECT
|
||||||
COALESCE(SUM(r.interets_bruts),0) AS interets_bruts,
|
COALESCE(SUM(r.interets_bruts),0) AS interets_bruts,
|
||||||
@@ -156,9 +172,12 @@ router.get('/', (req, res) => {
|
|||||||
COALESCE(SUM(r.prelev_forfaitaire),0) AS prelev_forfaitaire,
|
COALESCE(SUM(r.prelev_forfaitaire),0) AS prelev_forfaitaire,
|
||||||
COALESCE(SUM(r.net_recu),0) AS net_recu
|
COALESCE(SUM(r.net_recu),0) AS net_recu
|
||||||
FROM remboursements r
|
FROM remboursements r
|
||||||
JOIN investissements i ON i.id = r.investissement_id
|
LEFT JOIN investissements i ON i.id = r.investissement_id
|
||||||
WHERE ${invWhere.replace('investisseur_id', 'i.investisseur_id')}
|
WHERE (
|
||||||
`).get(...invParams);
|
(r.investissement_id IS NOT NULL AND ${invWhere.replace('investisseur_id', 'i.investisseur_id')})
|
||||||
|
OR (r.investissement_id IS NULL AND r.type = 'interets_plateforme' AND ${invWhere.replace('investisseur_id', 'r.bonus_investisseur_id')})
|
||||||
|
)
|
||||||
|
`).get(...invParams, ...invParams);
|
||||||
|
|
||||||
/* ── Interets par annee ──────────────────────────────────────── */
|
/* ── Interets par annee ──────────────────────────────────────── */
|
||||||
const interetsParAnnee = scopeAll
|
const interetsParAnnee = scopeAll
|
||||||
@@ -618,6 +637,23 @@ router.get('/interets-par-plateforme', (req, res) => {
|
|||||||
ORDER BY p.nom, mois
|
ORDER BY p.nom, mois
|
||||||
`).all(...invParams, anneeStr);
|
`).all(...invParams, anneeStr);
|
||||||
|
|
||||||
|
/* ── Intérêts plateforme (hors investissement) par plateforme et par mois ── */
|
||||||
|
const interetsPlateformeByPlat = db.prepare(`
|
||||||
|
SELECT
|
||||||
|
r.bonus_plateforme_id AS plateforme_id,
|
||||||
|
p.nom AS plateforme_nom,
|
||||||
|
strftime('%Y-%m', r.date_remb) AS mois,
|
||||||
|
COALESCE(SUM(r.interets_bruts), 0) AS interets_bruts,
|
||||||
|
COALESCE(SUM(r.interets_bruts - r.prelev_sociaux - r.prelev_forfaitaire), 0) AS interets_nets
|
||||||
|
FROM remboursements r
|
||||||
|
JOIN plateformes p ON p.id = r.bonus_plateforme_id
|
||||||
|
WHERE ${invWhere.replace('investisseur_id', 'bonus_investisseur_id')}
|
||||||
|
AND r.type = 'interets_plateforme'
|
||||||
|
AND strftime('%Y', r.date_remb) = ?
|
||||||
|
GROUP BY r.bonus_plateforme_id, p.nom, mois
|
||||||
|
ORDER BY p.nom, mois
|
||||||
|
`).all(...invParams, anneeStr);
|
||||||
|
|
||||||
/* ── Projections par plateforme et par mois ──────────────────── */
|
/* ── Projections par plateforme et par mois ──────────────────── */
|
||||||
// Exclure les investissements qui ont déjà un remboursement réel dans le même mois
|
// Exclure les investissements qui ont déjà un remboursement réel dans le même mois
|
||||||
const projectionsByPlat = db.prepare(`
|
const projectionsByPlat = db.prepare(`
|
||||||
@@ -782,6 +818,17 @@ router.get('/interets-par-plateforme', (req, res) => {
|
|||||||
rembMap[b.plateforme_id].rembourses[b.mois] = { interets_bruts: 0, interets_nets: 0, cashback: b.cashback, capital: 0 };
|
rembMap[b.plateforme_id].rembourses[b.mois] = { interets_bruts: 0, interets_nets: 0, cashback: b.cashback, capital: 0 };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Ajouter les intérêts plateforme (hors investissement) aux remboursements par mois
|
||||||
|
for (const ip of interetsPlateformeByPlat) {
|
||||||
|
if (!rembMap[ip.plateforme_id]) rembMap[ip.plateforme_id] = { id: ip.plateforme_id, nom: ip.plateforme_nom, detenteur_nom: detenteurMap[ip.plateforme_id] ?? null, rembourses: {}, projections: {} };
|
||||||
|
const entry = rembMap[ip.plateforme_id].rembourses[ip.mois];
|
||||||
|
if (entry) {
|
||||||
|
entry.interets_bruts = (entry.interets_bruts ?? 0) + ip.interets_bruts;
|
||||||
|
entry.interets_nets = (entry.interets_nets ?? 0) + ip.interets_nets;
|
||||||
|
} else {
|
||||||
|
rembMap[ip.plateforme_id].rembourses[ip.mois] = { interets_bruts: ip.interets_bruts, interets_nets: ip.interets_nets, cashback: 0, capital: 0 };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (const p of projectionsByPlat) {
|
for (const p of projectionsByPlat) {
|
||||||
if (!rembMap[p.plateforme_id]) rembMap[p.plateforme_id] = { id: p.plateforme_id, nom: p.plateforme_nom, detenteur_nom: detenteurMap[p.plateforme_id] ?? null, rembourses: {}, projections: {} };
|
if (!rembMap[p.plateforme_id]) rembMap[p.plateforme_id] = { id: p.plateforme_id, nom: p.plateforme_nom, detenteur_nom: detenteurMap[p.plateforme_id] ?? null, rembourses: {}, projections: {} };
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { adjustSimulForActuals, generateSimulWithReinvestissements } from '../ut
|
|||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
const Schema = z.object({
|
const Schema = z.object({
|
||||||
type: z.enum(['normal', 'bonus_parrainage', 'bonus_plateforme']).default('normal'),
|
type: z.enum(['normal', 'bonus_parrainage', 'bonus_plateforme', 'interets_plateforme']).default('normal'),
|
||||||
investissement_id: z.number().int().positive().nullable().optional(),
|
investissement_id: z.number().int().positive().nullable().optional(),
|
||||||
bonus_plateforme_id: z.number().int().positive().nullable().optional(),
|
bonus_plateforme_id: z.number().int().positive().nullable().optional(),
|
||||||
bonus_investisseur_id: z.number().int().positive().nullable().optional(),
|
bonus_investisseur_id: z.number().int().positive().nullable().optional(),
|
||||||
@@ -44,13 +44,20 @@ function computeChamps(body, isTaxIndicatif = false) {
|
|||||||
return { interets_nets, net_recu };
|
return { interets_nets, net_recu };
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTaxIndicatif(investissementId) {
|
function getTaxIndicatif(body) {
|
||||||
if (!investissementId) return false;
|
// Intérêts plateforme : pas d'investissement, la fiscalité applicable est celle
|
||||||
|
// de la plateforme associée (bonus_plateforme_id) — pas de fiscalite_override possible.
|
||||||
|
if (body.type === 'interets_plateforme') {
|
||||||
|
if (!body.bonus_plateforme_id) return false;
|
||||||
|
const p = db.prepare('SELECT fiscalite FROM plateformes WHERE id = ?').get(body.bonus_plateforme_id);
|
||||||
|
return p ? p.fiscalite !== 'flat_tax' : false;
|
||||||
|
}
|
||||||
|
if (!body.investissement_id) return false;
|
||||||
const row = db.prepare(`
|
const row = db.prepare(`
|
||||||
SELECT p.fiscalite, i.fiscalite_override
|
SELECT p.fiscalite, i.fiscalite_override
|
||||||
FROM investissements i JOIN plateformes p ON p.id = i.plateforme_id
|
FROM investissements i JOIN plateformes p ON p.id = i.plateforme_id
|
||||||
WHERE i.id = ?
|
WHERE i.id = ?
|
||||||
`).get(investissementId);
|
`).get(body.investissement_id);
|
||||||
if (!row) return false;
|
if (!row) return false;
|
||||||
return row.fiscalite !== 'flat_tax' || row.fiscalite_override === 'exonere';
|
return row.fiscalite !== 'flat_tax' || row.fiscalite_override === 'exonere';
|
||||||
}
|
}
|
||||||
@@ -165,8 +172,9 @@ router.get('/', (req, res) => {
|
|||||||
b.*,
|
b.*,
|
||||||
CASE b.type
|
CASE b.type
|
||||||
WHEN 'normal' THEN b.inv_nom_projet
|
WHEN 'normal' THEN b.inv_nom_projet
|
||||||
WHEN 'bonus_parrainage' THEN '— Bonus Parrainage'
|
WHEN 'bonus_parrainage' THEN '— Bonus Parrainage' || CASE WHEN NULLIF(b.notes, '') IS NOT NULL THEN ' — ' || b.notes ELSE '' END
|
||||||
WHEN 'bonus_plateforme' THEN '— Bonus Plateforme'
|
WHEN 'bonus_plateforme' THEN '— Bonus Plateforme' || CASE WHEN NULLIF(b.notes, '') IS NOT NULL THEN ' — ' || b.notes ELSE '' END
|
||||||
|
WHEN 'interets_plateforme' THEN '— Intérêts plateforme' || CASE WHEN NULLIF(b.notes, '') IS NOT NULL THEN ' — ' || b.notes ELSE '' END
|
||||||
ELSE b.inv_nom_projet
|
ELSE b.inv_nom_projet
|
||||||
END AS nom_projet,
|
END AS nom_projet,
|
||||||
p.nom AS plateforme_nom,
|
p.nom AS plateforme_nom,
|
||||||
@@ -188,7 +196,7 @@ router.get('/', (req, res) => {
|
|||||||
router.post('/', (req, res, next) => {
|
router.post('/', (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const body = Schema.parse(req.body);
|
const body = Schema.parse(req.body);
|
||||||
const { interets_nets, net_recu } = computeChamps(body, getTaxIndicatif(body.investissement_id));
|
const { interets_nets, net_recu } = computeChamps(body, getTaxIndicatif(body));
|
||||||
|
|
||||||
if (body.type === 'normal') {
|
if (body.type === 'normal') {
|
||||||
assertOwnedInvestissement(body.investissement_id, req.user.id);
|
assertOwnedInvestissement(body.investissement_id, req.user.id);
|
||||||
@@ -229,9 +237,26 @@ router.post('/', (req, res, next) => {
|
|||||||
return res.status(201).json({ id: r.lastInsertRowid, ...body, interets_nets, net_recu });
|
return res.status(201).json({ id: r.lastInsertRowid, ...body, interets_nets, net_recu });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bonus (parrainage ou plateforme)
|
// Bonus (parrainage / plateforme) ou Intérêts plateforme
|
||||||
const bonusInvestisseurId = body.bonus_investisseur_id ?? req.investisseur.id;
|
const bonusInvestisseurId = body.bonus_investisseur_id ?? req.investisseur.id;
|
||||||
assertOwnedInvestisseur(bonusInvestisseurId, req.user.id);
|
assertOwnedInvestisseur(bonusInvestisseurId, req.user.id);
|
||||||
|
|
||||||
|
if (body.type === 'interets_plateforme') {
|
||||||
|
const r = db.prepare(`
|
||||||
|
INSERT INTO remboursements
|
||||||
|
(type, bonus_plateforme_id, bonus_investisseur_id, date_remb,
|
||||||
|
capital, cashback, interets_bruts_avant_local, taxe_locale, interets_bruts,
|
||||||
|
prelev_sociaux, prelev_forfaitaire, interets_nets, net_recu, statut, source, notes)
|
||||||
|
VALUES ('interets_plateforme',?,?,?, 0,0,?,?,?,?,?, ?,?, ?, 'manuel', ?)
|
||||||
|
`).run(
|
||||||
|
body.bonus_plateforme_id, bonusInvestisseurId, body.date_remb,
|
||||||
|
body.interets_bruts_avant_local, body.taxe_locale, body.interets_bruts,
|
||||||
|
body.prelev_sociaux, body.prelev_forfaitaire,
|
||||||
|
interets_nets, net_recu, body.statut, body.notes || null,
|
||||||
|
);
|
||||||
|
return res.status(201).json({ id: r.lastInsertRowid, ...body, interets_nets, net_recu });
|
||||||
|
}
|
||||||
|
|
||||||
const r = db.prepare(`
|
const r = db.prepare(`
|
||||||
INSERT INTO remboursements
|
INSERT INTO remboursements
|
||||||
(type, bonus_plateforme_id, bonus_investisseur_id, date_remb,
|
(type, bonus_plateforme_id, bonus_investisseur_id, date_remb,
|
||||||
@@ -348,7 +373,7 @@ router.put('/:id', (req, res, next) => {
|
|||||||
if (!existing) throw new HttpError(404, 'Not found');
|
if (!existing) throw new HttpError(404, 'Not found');
|
||||||
|
|
||||||
const body = Schema.parse(req.body);
|
const body = Schema.parse(req.body);
|
||||||
const { interets_nets, net_recu } = computeChamps(body, getTaxIndicatif(body.investissement_id));
|
const { interets_nets, net_recu } = computeChamps(body, getTaxIndicatif(body));
|
||||||
|
|
||||||
if (body.type === 'normal') {
|
if (body.type === 'normal') {
|
||||||
db.prepare(`
|
db.prepare(`
|
||||||
@@ -369,6 +394,17 @@ router.put('/:id', (req, res, next) => {
|
|||||||
syncInvestissementStatut(body.investissement_id);
|
syncInvestissementStatut(body.investissement_id);
|
||||||
adjustSimulForActuals(db, body.investissement_id);
|
adjustSimulForActuals(db, body.investissement_id);
|
||||||
syncAutoRetrait(id, body.methode_remboursement, net_recu, body.investissement_id, body.date_remb);
|
syncAutoRetrait(id, body.methode_remboursement, net_recu, body.investissement_id, body.date_remb);
|
||||||
|
} else if (body.type === 'interets_plateforme') {
|
||||||
|
db.prepare(`
|
||||||
|
UPDATE remboursements
|
||||||
|
SET date_remb=?, interets_bruts_avant_local=?, taxe_locale=?, interets_bruts=?,
|
||||||
|
prelev_sociaux=?, prelev_forfaitaire=?, interets_nets=?, net_recu=?, statut=?, notes=?
|
||||||
|
WHERE id=?
|
||||||
|
`).run(
|
||||||
|
body.date_remb, body.interets_bruts_avant_local, body.taxe_locale, body.interets_bruts,
|
||||||
|
body.prelev_sociaux, body.prelev_forfaitaire,
|
||||||
|
interets_nets, net_recu, body.statut, body.notes || null, id,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
const bonusInvestisseurId = body.bonus_investisseur_id ?? existing.bonus_investisseur_id;
|
const bonusInvestisseurId = body.bonus_investisseur_id ?? existing.bonus_investisseur_id;
|
||||||
db.prepare(`
|
db.prepare(`
|
||||||
|
|||||||
@@ -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).
|
* en dessous, et se ferme au scroll/resize (repositionnement non géré en direct, comme InvSelect).
|
||||||
*
|
*
|
||||||
* Props :
|
* Props :
|
||||||
* value : '' | 'BONUS_PARRAINAGE' | 'BONUS_PLATEFORME' | id (string/number)
|
* value : '' | 'BONUS_PARRAINAGE' | 'BONUS_PLATEFORME' | 'INTERETS_PLATEFORME' | id (string/number)
|
||||||
* onChange : (value) => void
|
* onChange : (value) => void
|
||||||
* options : investissementOptions — { id, plateforme_nom, nom_projet, montant_investi }[]
|
* 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 selectedOption = options.find(i => String(i.id) === String(value));
|
||||||
const label = value === 'BONUS_PARRAINAGE' ? '🎁 Bonus Parrainage'
|
const label = value === 'BONUS_PARRAINAGE' ? '🎁 Bonus Parrainage'
|
||||||
: value === 'BONUS_PLATEFORME' ? '🏆 Bonus Plateforme'
|
: value === 'BONUS_PLATEFORME' ? '🏆 Bonus Plateforme'
|
||||||
|
: value === 'INTERETS_PLATEFORME' ? '🏦 Intérêts plateforme'
|
||||||
: selectedOption ? `${selectedOption.plateforme_nom} — ${selectedOption.nom_projet} (${fmtEUR(selectedOption.montant_investi)})`
|
: selectedOption ? `${selectedOption.plateforme_nom} — ${selectedOption.nom_projet} (${fmtEUR(selectedOption.montant_investi)})`
|
||||||
: null;
|
: 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 === '')} 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_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 === '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" />
|
<div className="cat-select-sep" />
|
||||||
{options.map(i => (
|
{options.map(i => (
|
||||||
<button key={i.id} type="button" style={rowStyle(String(value) === String(i.id))} onClick={() => select(String(i.id))}>
|
<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',
|
remboursement: 'Remboursement', bonus: 'Bonus', cashback: 'Cashback', souscription: 'Souscription',
|
||||||
prelevement_ir: 'Prélèvement IR', prelevement_csg: 'Prélèvement CSG',
|
prelevement_ir: 'Prélèvement IR', prelevement_csg: 'Prélèvement CSG',
|
||||||
prelevement_local: "Prélèvement impôts à l'étranger",
|
prelevement_local: "Prélèvement impôts à l'étranger",
|
||||||
|
interets_plateforme: 'Intérêts plateforme',
|
||||||
};
|
};
|
||||||
|
|
||||||
export function mouvToCSV(rows) {
|
export function mouvToCSV(rows) {
|
||||||
@@ -147,7 +148,8 @@ export function DetailPanel({ row, onEdit, onDeleteCorrection, onViewRemb, onVie
|
|||||||
const isCorrection = row.type === 'correction';
|
const isCorrection = row.type === 'correction';
|
||||||
const isBonus = row.type === 'bonus';
|
const isBonus = row.type === 'bonus';
|
||||||
const isCashback = row.type === 'cashback';
|
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 isSouscription = row.type === 'souscription';
|
||||||
const isPrelevement = row.type === 'prelevement_ir' || row.type === 'prelevement_csg' || row.type === 'prelevement_local';
|
const isPrelevement = row.type === 'prelevement_ir' || row.type === 'prelevement_csg' || row.type === 'prelevement_local';
|
||||||
const isAuto = row.source === 'auto_remboursement';
|
const isAuto = row.source === 'auto_remboursement';
|
||||||
@@ -156,6 +158,7 @@ export function DetailPanel({ row, onEdit, onDeleteCorrection, onViewRemb, onVie
|
|||||||
: isBonus ? 'Bonus'
|
: isBonus ? 'Bonus'
|
||||||
: isCashback ? 'Cashback'
|
: isCashback ? 'Cashback'
|
||||||
: row.type === 'remboursement' ? 'Remboursement (porte-monnaie, brut)'
|
: 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_ir' ? 'Prélèvement impôt sur le revenu'
|
||||||
: row.type === 'prelevement_csg' ? 'Prélèvement CSG'
|
: row.type === 'prelevement_csg' ? 'Prélèvement CSG'
|
||||||
: row.type === 'prelevement_local' ? "Prélèvement impôts à l'étranger"
|
: 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 montantFmt = `${sign} ${fmtEUR(Math.abs(row.montant))}`;
|
||||||
const montantColor = undefined;
|
const montantColor = undefined;
|
||||||
|
|
||||||
const isRembOnly = row.type === 'remboursement';
|
const isRembOnly = row.type === 'remboursement' || isInteretsPlateforme;
|
||||||
|
|
||||||
const fields = [
|
const fields = [
|
||||||
{ label: 'Plateforme', value: row.plateforme_nom },
|
{ 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) },
|
{ label: 'Date', value: fmtDate(row.date_operation) },
|
||||||
isRembOnly && row.libelle && { label: 'Projet', value: row.libelle },
|
isRembOnly && row.libelle && { label: 'Projet', value: row.libelle },
|
||||||
(isBonus || isCashback) && 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 && row._cashback > 0 && { label: 'Cashback', value: fmtEUR(row._cashback) },
|
||||||
isRembOnly && { label: 'Intérêts bruts', value: fmtEUR(row._interets_bruts) },
|
isRembOnly && { label: 'Intérêts bruts', value: fmtEUR(row._interets_bruts) },
|
||||||
isRembOnly && { label: 'Traitement fiscal', value: row._isFlatTax
|
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.'
|
? 'Bonus crédité directement au porte-monnaie de la plateforme.'
|
||||||
: isCashback
|
: 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."
|
? "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
|
: isInteretsPlateforme
|
||||||
? "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."
|
? "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."
|
||||||
: "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."}
|
: 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>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ const getRatesForYear = (dateStr, pfuRates) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const BONUS_VALUES = ['BONUS_PARRAINAGE', 'BONUS_PLATEFORME'];
|
const BONUS_VALUES = ['BONUS_PARRAINAGE', 'BONUS_PLATEFORME'];
|
||||||
|
const INTERETS_PLATEFORME_VALUE = 'INTERETS_PLATEFORME';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* RembFormModal — formulaire partagé de saisie/édition d'un remboursement.
|
* RembFormModal — formulaire partagé de saisie/édition d'un remboursement.
|
||||||
@@ -67,6 +68,9 @@ export default function RembFormModal({
|
|||||||
confirmingDelete, setConfirmingDelete,
|
confirmingDelete, setConfirmingDelete,
|
||||||
}) {
|
}) {
|
||||||
const isBonus = !locked && BONUS_VALUES.includes(form.investissement_id);
|
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 rates = getRatesForYear(form.date_remb, pfuRates);
|
||||||
const labelPS = `Prélèvements sociaux${rates ? ` (${fmtRate(rates.prelev_sociaux)})` : ''} (€)`;
|
const labelPS = `Prélèvements sociaux${rates ? ` (${fmtRate(rates.prelev_sociaux)})` : ''} (€)`;
|
||||||
@@ -75,8 +79,10 @@ export default function RembFormModal({
|
|||||||
|
|
||||||
const currentInv = locked
|
const currentInv = locked
|
||||||
? investissements[0]
|
? investissements[0]
|
||||||
: (!isBonus ? investissements.find(i => i.id === Number(form.investissement_id)) : null);
|
: (!isPlateformeMode ? investissements.find(i => i.id === Number(form.investissement_id)) : null);
|
||||||
const currentPlat = currentInv ? plateformes.find(p => p.id === currentInv.plateforme_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 hasLocalTax = !isBonus && currentPlat?.fiscalite === 'avec_fiscalite_locale' && currentPlat?.taux_fiscalite_locale;
|
||||||
const isExonere = !isBonus && currentInv?.fiscalite_override === 'exonere';
|
const isExonere = !isBonus && currentInv?.fiscalite_override === 'exonere';
|
||||||
const isIndicatif = Boolean(isExonere || (!isBonus && currentPlat && currentPlat.fiscalite !== 'flat_tax'));
|
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) ── */}
|
{/* ── Sélecteur d'investissement (mode non verrouillé uniquement) ── */}
|
||||||
{!locked && (
|
{!locked && (
|
||||||
<div style={{ gridColumn: isBonus ? 'span 1' : 'span 3' }}>
|
<div style={{ gridColumn: isPlateformeMode ? 'span 1' : 'span 3' }}>
|
||||||
<label>Investissement *</label>
|
<label>Investissement *</label>
|
||||||
<InvestissementSelect
|
<InvestissementSelect
|
||||||
value={form.investissement_id}
|
value={form.investissement_id}
|
||||||
@@ -136,9 +142,9 @@ export default function RembFormModal({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!locked && isBonus && (
|
{!locked && isPlateformeMode && (
|
||||||
<div style={{ gridColumn: 'span 2' }}>
|
<div>
|
||||||
<label>Plateforme associée (bonus)</label>
|
<label>Plateforme associée</label>
|
||||||
<select value={form.bonus_plateforme_id} onChange={e => {
|
<select value={form.bonus_plateforme_id} onChange={e => {
|
||||||
const plat = plateformes.find(p => String(p.id) === e.target.value);
|
const plat = plateformes.find(p => String(p.id) === e.target.value);
|
||||||
onFormPatch({ bonus_plateforme_id: e.target.value, bonus_investisseur_id: plat?.investisseur_id ? String(plat.investisseur_id) : '' });
|
onFormPatch({ bonus_plateforme_id: e.target.value, bonus_investisseur_id: plat?.investisseur_id ? String(plat.investisseur_id) : '' });
|
||||||
@@ -148,8 +154,15 @@ export default function RembFormModal({
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{!locked && isBonus && (
|
||||||
|
<div>
|
||||||
|
<label>Date *</label>
|
||||||
|
<input type="date" required value={form.date_remb || ''}
|
||||||
|
onChange={e => onFieldChange('date_remb', e.target.value)} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* ── Champs remboursement normal ── */}
|
{/* ── Champs remboursement normal / intérêts plateforme ── */}
|
||||||
{!isBonus && (<>
|
{!isBonus && (<>
|
||||||
<div style={{ gridColumn: 'span 3', borderTop: '1px solid var(--border)', paddingTop: 4, marginTop: 4 }}>
|
<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>
|
<span style={{ fontSize: 11, fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.06em', color: 'var(--text-muted)' }}>Remboursement</span>
|
||||||
@@ -168,11 +181,13 @@ export default function RembFormModal({
|
|||||||
onChange={e => onFieldChange('taxe_locale', e.target.value)} />
|
onChange={e => onFieldChange('taxe_locale', e.target.value)} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div>
|
{!isInteretsPlateforme && (
|
||||||
<label>Capital (€)</label>
|
<div>
|
||||||
<input type="number" step="0.01" value={form.capital ?? 0}
|
<label>Capital (€)</label>
|
||||||
onChange={e => onFieldChange('capital', e.target.value)} />
|
<input type="number" step="0.01" value={form.capital ?? 0}
|
||||||
</div>
|
onChange={e => onFieldChange('capital', e.target.value)} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div>
|
<div>
|
||||||
<label>{hasLocalTax ? 'Intérêts bruts après retenue locale (€)' : 'Intérêts bruts (€)'}</label>
|
<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}
|
<input type="number" step="0.01" value={form.interets_bruts ?? 0}
|
||||||
@@ -180,11 +195,13 @@ export default function RembFormModal({
|
|||||||
style={hasLocalTax ? { background: 'var(--surface-2)', color: 'var(--text-muted)', cursor: 'not-allowed' } : undefined}
|
style={hasLocalTax ? { background: 'var(--surface-2)', color: 'var(--text-muted)', cursor: 'not-allowed' } : undefined}
|
||||||
onChange={e => !hasLocalTax && onFieldChange('interets_bruts', e.target.value)} />
|
onChange={e => !hasLocalTax && onFieldChange('interets_bruts', e.target.value)} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
{!isInteretsPlateforme && (
|
||||||
<label>Cashback (€)</label>
|
<div>
|
||||||
<input type="number" step="0.01" value={form.cashback ?? 0}
|
<label>Cashback (€)</label>
|
||||||
onChange={e => onFieldChange('cashback', e.target.value)} />
|
<input type="number" step="0.01" value={form.cashback ?? 0}
|
||||||
</div>
|
onChange={e => onFieldChange('cashback', e.target.value)} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div style={{ gridColumn: 'span 3', borderTop: '1px solid var(--border)', paddingTop: 4, marginTop: 4 }}>
|
<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>
|
<span style={{ fontSize: 11, fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.06em', color: 'var(--text-muted)' }}>Imposition{isIndicatif ? ' — indicatif' : ''}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -221,16 +238,18 @@ export default function RembFormModal({
|
|||||||
<input type="date" required value={form.date_remb || ''}
|
<input type="date" required value={form.date_remb || ''}
|
||||||
onChange={e => onFieldChange('date_remb', e.target.value)} />
|
onChange={e => onFieldChange('date_remb', e.target.value)} />
|
||||||
</div>
|
</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>
|
<div>
|
||||||
<label>Méthode de remboursement</label>
|
{!isInteretsPlateforme && form.methode_remboursement === 'compte_courant' && (
|
||||||
<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' && (
|
|
||||||
<>
|
<>
|
||||||
<label>Compte de réception</label>
|
<label>Compte de réception</label>
|
||||||
{comptesInvestisseur?.length > 0 ? (
|
{comptesInvestisseur?.length > 0 ? (
|
||||||
@@ -264,18 +283,20 @@ export default function RembFormModal({
|
|||||||
{/* ── Bonus (mode non verrouillé uniquement) ── */}
|
{/* ── Bonus (mode non verrouillé uniquement) ── */}
|
||||||
{!locked && isBonus && (
|
{!locked && isBonus && (
|
||||||
<div>
|
<div>
|
||||||
<label>Date *</label>
|
|
||||||
<input type="date" required value={form.date_remb || ''}
|
|
||||||
onChange={e => onFieldChange('date_remb', e.target.value)} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{!locked && isBonus && (
|
|
||||||
<div style={{ gridColumn: 'span 2' }}>
|
|
||||||
<label>Montant (€)</label>
|
<label>Montant (€)</label>
|
||||||
<input type="number" step="0.01" value={form.cashback ?? 0}
|
<input type="number" step="0.01" value={form.cashback ?? 0}
|
||||||
onChange={e => onFieldChange('cashback', e.target.value)} />
|
onChange={e => onFieldChange('cashback', e.target.value)} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{!locked && isPlateformeMode && (
|
||||||
|
<div style={{ gridColumn: isBonus ? 'span 2' : 'span 3' }}>
|
||||||
|
<label>Projet / Commentaire</label>
|
||||||
|
<input type="text"
|
||||||
|
placeholder={isBonus ? 'Nom du projet sollicité par le filleul…' : 'Commentaire…'}
|
||||||
|
value={form.notes || ''}
|
||||||
|
onChange={e => onFieldChange('notes', e.target.value)} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -268,6 +268,7 @@ export default function DepotsRetraits() {
|
|||||||
const out = [];
|
const out = [];
|
||||||
for (const r of allRemb) {
|
for (const r of allRemb) {
|
||||||
const isBonus = r.type === 'bonus_parrainage' || r.type === 'bonus_plateforme';
|
const isBonus = r.type === 'bonus_parrainage' || r.type === 'bonus_plateforme';
|
||||||
|
const isInteretsPlateforme = r.type === 'interets_plateforme';
|
||||||
const plat = plats.find(p => p.id === r.plateforme_id);
|
const plat = plats.find(p => p.id === r.plateforme_id);
|
||||||
const base = {
|
const base = {
|
||||||
_rembId: r.id,
|
_rembId: r.id,
|
||||||
@@ -305,7 +306,7 @@ export default function DepotsRetraits() {
|
|||||||
out.push({
|
out.push({
|
||||||
...base,
|
...base,
|
||||||
id: `remb_${r.id}`,
|
id: `remb_${r.id}`,
|
||||||
type: 'remboursement',
|
type: isInteretsPlateforme ? 'interets_plateforme' : 'remboursement',
|
||||||
montant: grossMontant,
|
montant: grossMontant,
|
||||||
_isFlatTax: isFlatTax,
|
_isFlatTax: isFlatTax,
|
||||||
_isAutoSweep: r.methode_remboursement === 'compte_courant',
|
_isAutoSweep: r.methode_remboursement === 'compte_courant',
|
||||||
@@ -1140,6 +1141,7 @@ export default function DepotsRetraits() {
|
|||||||
<option value="remboursement">Remboursements</option>
|
<option value="remboursement">Remboursements</option>
|
||||||
<option value="bonus">Bonus</option>
|
<option value="bonus">Bonus</option>
|
||||||
<option value="cashback">Cashback</option>
|
<option value="cashback">Cashback</option>
|
||||||
|
<option value="interets_plateforme">Intérêts plateforme</option>
|
||||||
<option value="souscription">Souscriptions</option>
|
<option value="souscription">Souscriptions</option>
|
||||||
<option value="prelevement_ir">Prélèvements IR</option>
|
<option value="prelevement_ir">Prélèvements IR</option>
|
||||||
<option value="prelevement_csg">Prélèvements CSG</option>
|
<option value="prelevement_csg">Prélèvements CSG</option>
|
||||||
@@ -1251,9 +1253,10 @@ export default function DepotsRetraits() {
|
|||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (r.type === 'remboursement' || r.type === 'bonus' || r.type === 'cashback') {
|
if (r.type === 'remboursement' || r.type === 'bonus' || r.type === 'cashback' || r.type === 'interets_plateforme') {
|
||||||
const isBonus = r.type === 'bonus';
|
const isBonus = r.type === 'bonus';
|
||||||
const isCashback = r.type === 'cashback';
|
const isCashback = r.type === 'cashback';
|
||||||
|
const isInteretsPlateforme = r.type === 'interets_plateforme';
|
||||||
return (
|
return (
|
||||||
<tr key={r.id}
|
<tr key={r.id}
|
||||||
className={`dr-row${selectedRow?.id === r.id ? ' dr-row-selected' : ''}`}
|
className={`dr-row${selectedRow?.id === r.id ? ' dr-row-selected' : ''}`}
|
||||||
@@ -1269,9 +1272,11 @@ export default function DepotsRetraits() {
|
|||||||
padding: '2px 7px', borderRadius: 4,
|
padding: '2px 7px', borderRadius: 4,
|
||||||
background: (isBonus || isCashback)
|
background: (isBonus || isCashback)
|
||||||
? 'color-mix(in srgb, var(--warning) 18%, transparent)'
|
? 'color-mix(in srgb, var(--warning) 18%, transparent)'
|
||||||
: 'color-mix(in srgb, var(--success) 15%, transparent)',
|
: isInteretsPlateforme
|
||||||
color: (isBonus || isCashback) ? 'var(--warning)' : 'var(--success)',
|
? 'color-mix(in srgb, var(--primary) 18%, transparent)'
|
||||||
}}>{isBonus ? 'bonus' : isCashback ? 'cashback' : 'remboursement'}</span>
|
: 'color-mix(in srgb, var(--success) 15%, transparent)',
|
||||||
|
color: (isBonus || isCashback) ? 'var(--warning)' : isInteretsPlateforme ? 'var(--primary)' : 'var(--success)',
|
||||||
|
}}>{isBonus ? 'bonus' : isCashback ? 'cashback' : isInteretsPlateforme ? 'intérêts plateforme' : 'remboursement'}</span>
|
||||||
</td>
|
</td>
|
||||||
<td className="text-muted">{r._projet || '—'}</td>
|
<td className="text-muted">{r._projet || '—'}</td>
|
||||||
<td className="num success">
|
<td className="num success">
|
||||||
|
|||||||
@@ -850,6 +850,8 @@ export default function Plateformes() {
|
|||||||
: (r.capital || 0) + (r.cashback || 0) + (r.interets_bruts || 0);
|
: (r.capital || 0) + (r.cashback || 0) + (r.interets_bruts || 0);
|
||||||
} else if (r.type === 'bonus_parrainage' || r.type === 'bonus_plateforme') {
|
} else if (r.type === 'bonus_parrainage' || r.type === 'bonus_plateforme') {
|
||||||
balance += r.cashback || 0;
|
balance += r.cashback || 0;
|
||||||
|
} else if (r.type === 'interets_plateforme') {
|
||||||
|
balance += r.net_recu || 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const inv of allRows) {
|
for (const inv of allRows) {
|
||||||
@@ -964,6 +966,7 @@ export default function Plateformes() {
|
|||||||
const out = [];
|
const out = [];
|
||||||
for (const r of allRembs) {
|
for (const r of allRembs) {
|
||||||
const isBonus = r.type === 'bonus_parrainage' || r.type === 'bonus_plateforme';
|
const isBonus = r.type === 'bonus_parrainage' || r.type === 'bonus_plateforme';
|
||||||
|
const isInteretsPlateforme = r.type === 'interets_plateforme';
|
||||||
const plat = plats.find(p => p.id === r.plateforme_id);
|
const plat = plats.find(p => p.id === r.plateforme_id);
|
||||||
const base = {
|
const base = {
|
||||||
_rembId: r.id,
|
_rembId: r.id,
|
||||||
@@ -999,7 +1002,7 @@ export default function Plateformes() {
|
|||||||
out.push({
|
out.push({
|
||||||
...base,
|
...base,
|
||||||
id: `remb_${r.id}`,
|
id: `remb_${r.id}`,
|
||||||
type: 'remboursement',
|
type: isInteretsPlateforme ? 'interets_plateforme' : 'remboursement',
|
||||||
montant: grossMontant,
|
montant: grossMontant,
|
||||||
_isFlatTax: isFlatTax,
|
_isFlatTax: isFlatTax,
|
||||||
_isAutoSweep: r.methode_remboursement === 'compte_courant',
|
_isAutoSweep: r.methode_remboursement === 'compte_courant',
|
||||||
@@ -2406,6 +2409,7 @@ export default function Plateformes() {
|
|||||||
<option value="remboursement">Remboursements</option>
|
<option value="remboursement">Remboursements</option>
|
||||||
<option value="bonus">Bonus</option>
|
<option value="bonus">Bonus</option>
|
||||||
<option value="cashback">Cashback</option>
|
<option value="cashback">Cashback</option>
|
||||||
|
<option value="interets_plateforme">Intérêts plateforme</option>
|
||||||
<option value="souscription">Souscriptions</option>
|
<option value="souscription">Souscriptions</option>
|
||||||
<option value="prelevement_ir">Prélèvements IR</option>
|
<option value="prelevement_ir">Prélèvements IR</option>
|
||||||
<option value="prelevement_csg">Prélèvements CSG</option>
|
<option value="prelevement_csg">Prélèvements CSG</option>
|
||||||
@@ -2493,9 +2497,10 @@ export default function Plateformes() {
|
|||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (r.type === 'remboursement' || r.type === 'bonus' || r.type === 'cashback') {
|
if (r.type === 'remboursement' || r.type === 'bonus' || r.type === 'cashback' || r.type === 'interets_plateforme') {
|
||||||
const isBonus = r.type === 'bonus';
|
const isBonus = r.type === 'bonus';
|
||||||
const isCashback = r.type === 'cashback';
|
const isCashback = r.type === 'cashback';
|
||||||
|
const isInteretsPlateforme = r.type === 'interets_plateforme';
|
||||||
return (
|
return (
|
||||||
<tr key={r.id}
|
<tr key={r.id}
|
||||||
className={`dr-row${mvSelectedRow?.id === r.id ? ' dr-row-selected' : ''}`}
|
className={`dr-row${mvSelectedRow?.id === r.id ? ' dr-row-selected' : ''}`}
|
||||||
@@ -2511,9 +2516,11 @@ export default function Plateformes() {
|
|||||||
padding: '2px 7px', borderRadius: 4,
|
padding: '2px 7px', borderRadius: 4,
|
||||||
background: (isBonus || isCashback)
|
background: (isBonus || isCashback)
|
||||||
? 'color-mix(in srgb, var(--warning) 18%, transparent)'
|
? 'color-mix(in srgb, var(--warning) 18%, transparent)'
|
||||||
: 'color-mix(in srgb, var(--success) 15%, transparent)',
|
: isInteretsPlateforme
|
||||||
color: (isBonus || isCashback) ? 'var(--warning)' : 'var(--success)',
|
? 'color-mix(in srgb, var(--primary) 18%, transparent)'
|
||||||
}}>{isBonus ? 'bonus' : isCashback ? 'cashback' : 'remboursement'}</span>
|
: 'color-mix(in srgb, var(--success) 15%, transparent)',
|
||||||
|
color: (isBonus || isCashback) ? 'var(--warning)' : isInteretsPlateforme ? 'var(--primary)' : 'var(--success)',
|
||||||
|
}}>{isBonus ? 'bonus' : isCashback ? 'cashback' : isInteretsPlateforme ? 'intérêts plateforme' : 'remboursement'}</span>
|
||||||
</td>
|
</td>
|
||||||
<td className="text-muted">{r._projet || '—'}</td>
|
<td className="text-muted">{r._projet || '—'}</td>
|
||||||
<td className="num success">
|
<td className="num success">
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ function TrendBadge({ current, prev, invert = false }) {
|
|||||||
|
|
||||||
|
|
||||||
const BONUS_VALUES = ['BONUS_PARRAINAGE', 'BONUS_PLATEFORME'];
|
const BONUS_VALUES = ['BONUS_PARRAINAGE', 'BONUS_PLATEFORME'];
|
||||||
|
const INTERETS_PLATEFORME_VALUE = 'INTERETS_PLATEFORME';
|
||||||
const ICONS_BASE = '/api/icons-files/';
|
const ICONS_BASE = '/api/icons-files/';
|
||||||
const BONUS_TYPE_MAP = { BONUS_PARRAINAGE: 'bonus_parrainage', BONUS_PLATEFORME: 'bonus_plateforme' };
|
const BONUS_TYPE_MAP = { BONUS_PARRAINAGE: 'bonus_parrainage', BONUS_PLATEFORME: 'bonus_plateforme' };
|
||||||
|
|
||||||
@@ -424,9 +425,9 @@ export default function Remboursements() {
|
|||||||
if (tableFilter.type && tableFilter.type !== 'correction_solde' && r.type !== tableFilter.type) return false;
|
if (tableFilter.type && tableFilter.type !== 'correction_solde' && r.type !== tableFilter.type) return false;
|
||||||
if (tableFilter.type === 'correction_solde') return false; // géré séparément
|
if (tableFilter.type === 'correction_solde') return false; // géré séparément
|
||||||
// Filtre projet
|
// Filtre projet
|
||||||
const isBonusTypeFilter = tableFilter.type === 'bonus_parrainage' || tableFilter.type === 'bonus_plateforme';
|
const isBonusTypeFilter = tableFilter.type === 'bonus_parrainage' || tableFilter.type === 'bonus_plateforme' || tableFilter.type === 'interets_plateforme';
|
||||||
if (!isBonusTypeFilter && tableFilter.investissement_id) {
|
if (!isBonusTypeFilter && tableFilter.investissement_id) {
|
||||||
const isBonus = r.type === 'bonus_parrainage' || r.type === 'bonus_plateforme';
|
const isBonus = r.type === 'bonus_parrainage' || r.type === 'bonus_plateforme' || r.type === 'interets_plateforme';
|
||||||
if (isBonus) return false;
|
if (isBonus) return false;
|
||||||
if (String(r.investissement_id) !== String(tableFilter.investissement_id)) return false;
|
if (String(r.investissement_id) !== String(tableFilter.investissement_id)) return false;
|
||||||
}
|
}
|
||||||
@@ -535,9 +536,13 @@ export default function Remboursements() {
|
|||||||
const openEdit = (r) => {
|
const openEdit = (r) => {
|
||||||
setEditingId(r.id);
|
setEditingId(r.id);
|
||||||
const isBonus = r.type === 'bonus_parrainage' || r.type === 'bonus_plateforme';
|
const isBonus = r.type === 'bonus_parrainage' || r.type === 'bonus_plateforme';
|
||||||
const sentinelle = r.type === 'bonus_parrainage' ? 'BONUS_PARRAINAGE' : r.type === 'bonus_plateforme' ? 'BONUS_PLATEFORME' : '';
|
const isInteretsPlateforme = r.type === 'interets_plateforme';
|
||||||
|
const sentinelle = r.type === 'bonus_parrainage' ? 'BONUS_PARRAINAGE'
|
||||||
|
: r.type === 'bonus_plateforme' ? 'BONUS_PLATEFORME'
|
||||||
|
: isInteretsPlateforme ? INTERETS_PLATEFORME_VALUE
|
||||||
|
: '';
|
||||||
setForm({
|
setForm({
|
||||||
investissement_id: isBonus ? sentinelle : r.investissement_id,
|
investissement_id: (isBonus || isInteretsPlateforme) ? sentinelle : r.investissement_id,
|
||||||
date_remb: r.date_remb,
|
date_remb: r.date_remb,
|
||||||
capital: r.capital, cashback: r.cashback || 0,
|
capital: r.capital, cashback: r.cashback || 0,
|
||||||
interets_bruts_avant_local: r.interets_bruts_avant_local || 0,
|
interets_bruts_avant_local: r.interets_bruts_avant_local || 0,
|
||||||
@@ -552,7 +557,7 @@ export default function Remboursements() {
|
|||||||
});
|
});
|
||||||
setErr(null); setModalOpen(true);
|
setErr(null); setModalOpen(true);
|
||||||
// Charge les comptes du détenteur pour le dropdown Compte de réception
|
// Charge les comptes du détenteur pour le dropdown Compte de réception
|
||||||
if (!isBonus) {
|
if (!isBonus && !isInteretsPlateforme) {
|
||||||
const invForEdit = investissements.find(i => i.id === r.investissement_id);
|
const invForEdit = investissements.find(i => i.id === r.investissement_id);
|
||||||
if (invForEdit?.investisseur_id) {
|
if (invForEdit?.investisseur_id) {
|
||||||
api.get(`/investissements/comptes-par-investisseur/${invForEdit.investisseur_id}`)
|
api.get(`/investissements/comptes-par-investisseur/${invForEdit.investisseur_id}`)
|
||||||
@@ -654,8 +659,12 @@ export default function Remboursements() {
|
|||||||
|
|
||||||
// Fiscalité locale : calcule taxe_locale et interets_bruts à partir du brut avant retenue
|
// Fiscalité locale : calcule taxe_locale et interets_bruts à partir du brut avant retenue
|
||||||
if (k === 'interets_bruts_avant_local') {
|
if (k === 'interets_bruts_avant_local') {
|
||||||
const inv = investissements.find(i => i.id === Number(next.investissement_id));
|
const plat = next.investissement_id === INTERETS_PLATEFORME_VALUE
|
||||||
const plat = plateformes.find(p => p.id === inv?.plateforme_id);
|
? plateformes.find(p => String(p.id) === String(next.bonus_plateforme_id))
|
||||||
|
: (() => {
|
||||||
|
const inv = investissements.find(i => i.id === Number(next.investissement_id));
|
||||||
|
return plateformes.find(p => p.id === inv?.plateforme_id);
|
||||||
|
})();
|
||||||
const taux = plat?.fiscalite === 'avec_fiscalite_locale' ? (plat?.taux_fiscalite_locale || 0) : 0;
|
const taux = plat?.fiscalite === 'avec_fiscalite_locale' ? (plat?.taux_fiscalite_locale || 0) : 0;
|
||||||
next.taxe_locale = round2(Number(v) * taux / 100);
|
next.taxe_locale = round2(Number(v) * taux / 100);
|
||||||
next.interets_bruts = round2(Number(v) - next.taxe_locale);
|
next.interets_bruts = round2(Number(v) - next.taxe_locale);
|
||||||
@@ -676,7 +685,7 @@ export default function Remboursements() {
|
|||||||
}
|
}
|
||||||
// Quand l'investissement change, on hérite silencieusement de la méthode de la plateforme
|
// Quand l'investissement change, on hérite silencieusement de la méthode de la plateforme
|
||||||
// (si fermée) ou on remet le défaut (si ouverte, l'utilisateur choisira dans le formulaire)
|
// (si fermée) ou on remet le défaut (si ouverte, l'utilisateur choisira dans le formulaire)
|
||||||
if (k === 'investissement_id' && !BONUS_VALUES.includes(v)) {
|
if (k === 'investissement_id' && !BONUS_VALUES.includes(v) && v !== INTERETS_PLATEFORME_VALUE) {
|
||||||
const inv = investissements.find(i => i.id === Number(v));
|
const inv = investissements.find(i => i.id === Number(v));
|
||||||
const plat = plateformes.find(p => p.id === inv?.plateforme_id);
|
const plat = plateformes.find(p => p.id === inv?.plateforme_id);
|
||||||
if (plat && plat.methode_remboursement !== 'choix_investisseur') {
|
if (plat && plat.methode_remboursement !== 'choix_investisseur') {
|
||||||
@@ -708,6 +717,7 @@ export default function Remboursements() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const isBonus = BONUS_VALUES.includes(form.investissement_id);
|
const isBonus = BONUS_VALUES.includes(form.investissement_id);
|
||||||
|
const isInteretsPlateforme = form.investissement_id === INTERETS_PLATEFORME_VALUE;
|
||||||
|
|
||||||
const submit = async (e) => {
|
const submit = async (e) => {
|
||||||
e?.preventDefault?.();
|
e?.preventDefault?.();
|
||||||
@@ -724,6 +734,20 @@ export default function Remboursements() {
|
|||||||
statut: form.statut,
|
statut: form.statut,
|
||||||
notes: form.notes || undefined,
|
notes: form.notes || undefined,
|
||||||
};
|
};
|
||||||
|
} else if (isInteretsPlateforme) {
|
||||||
|
payload = {
|
||||||
|
type: 'interets_plateforme',
|
||||||
|
bonus_plateforme_id: Number(form.bonus_plateforme_id),
|
||||||
|
bonus_investisseur_id: form.bonus_investisseur_id ? Number(form.bonus_investisseur_id) : undefined,
|
||||||
|
date_remb: form.date_remb,
|
||||||
|
interets_bruts_avant_local: Number(form.interets_bruts_avant_local || 0),
|
||||||
|
taxe_locale: Number(form.taxe_locale || 0),
|
||||||
|
interets_bruts: Number(form.interets_bruts || 0),
|
||||||
|
prelev_sociaux: Number(form.prelev_sociaux || 0),
|
||||||
|
prelev_forfaitaire: Number(form.prelev_forfaitaire || 0),
|
||||||
|
statut: form.statut,
|
||||||
|
notes: form.notes || undefined,
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
payload = {
|
payload = {
|
||||||
type: 'normal',
|
type: 'normal',
|
||||||
@@ -1136,6 +1160,7 @@ export default function Remboursements() {
|
|||||||
<option value="normal">Remboursements</option>
|
<option value="normal">Remboursements</option>
|
||||||
<option value="bonus_parrainage">Bonus Parrainage</option>
|
<option value="bonus_parrainage">Bonus Parrainage</option>
|
||||||
<option value="bonus_plateforme">Bonus Plateforme</option>
|
<option value="bonus_plateforme">Bonus Plateforme</option>
|
||||||
|
<option value="interets_plateforme">Intérêts plateforme</option>
|
||||||
<option value="correction_solde">Corrections de solde</option>
|
<option value="correction_solde">Corrections de solde</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
@@ -1186,6 +1211,8 @@ export default function Remboursements() {
|
|||||||
)}
|
)}
|
||||||
{pagedTableRows.map(r => {
|
{pagedTableRows.map(r => {
|
||||||
const isBonus = r.type === 'bonus_parrainage' || r.type === 'bonus_plateforme';
|
const isBonus = r.type === 'bonus_parrainage' || r.type === 'bonus_plateforme';
|
||||||
|
const isInteretsPlateforme = r.type === 'interets_plateforme';
|
||||||
|
const isBonusLike = isBonus || isInteretsPlateforme;
|
||||||
const isCorrection = r._is_correction === true;
|
const isCorrection = r._is_correction === true;
|
||||||
const rowKey = isCorrection ? `corr-${r.id}` : `remb-${r.id}`;
|
const rowKey = isCorrection ? `corr-${r.id}` : `remb-${r.id}`;
|
||||||
return (
|
return (
|
||||||
@@ -1214,7 +1241,7 @@ export default function Remboursements() {
|
|||||||
<td>
|
<td>
|
||||||
{isCorrection
|
{isCorrection
|
||||||
? <span style={{ color: 'var(--text-muted)', fontStyle: 'italic' }}>{r.nom_projet}</span>
|
? <span style={{ color: 'var(--text-muted)', fontStyle: 'italic' }}>{r.nom_projet}</span>
|
||||||
: isBonus
|
: isBonusLike
|
||||||
? <span style={{ color: 'var(--text-muted)', fontStyle: 'italic' }}>{r.nom_projet}</span>
|
? <span style={{ color: 'var(--text-muted)', fontStyle: 'italic' }}>{r.nom_projet}</span>
|
||||||
: r.nom_projet}
|
: r.nom_projet}
|
||||||
</td>
|
</td>
|
||||||
@@ -1233,9 +1260,9 @@ export default function Remboursements() {
|
|||||||
<span className="text-muted">Porte-monnaie</span>
|
<span className="text-muted">Porte-monnaie</span>
|
||||||
)}
|
)}
|
||||||
</td>
|
</td>
|
||||||
<td className="num">{(isBonus || isCorrection) ? '—' : fmtEUR(r.capital)}</td>
|
<td className="num">{(isBonusLike || isCorrection) ? '—' : fmtEUR(r.capital)}</td>
|
||||||
<td className="num" style={{ color: !isBonus && !isCorrection && r.capital_restant_du <= 0 ? 'var(--success)' : undefined }}>
|
<td className="num" style={{ color: !isBonusLike && !isCorrection && r.capital_restant_du <= 0 ? 'var(--success)' : undefined }}>
|
||||||
{(isBonus || isCorrection) ? '—' : fmtEUR(Math.max(0, r.capital_restant_du ?? 0))}
|
{(isBonusLike || isCorrection) ? '—' : fmtEUR(Math.max(0, r.capital_restant_du ?? 0))}
|
||||||
</td>
|
</td>
|
||||||
<td className="num">{r.cashback ? fmtEUR(r.cashback) : '—'}</td>
|
<td className="num">{r.cashback ? fmtEUR(r.cashback) : '—'}</td>
|
||||||
<td className="num">{(isBonus || isCorrection) ? '—' : fmtEUR(r.interets_bruts)}</td>
|
<td className="num">{(isBonus || isCorrection) ? '—' : fmtEUR(r.interets_bruts)}</td>
|
||||||
|
|||||||
+2
-1
@@ -4,7 +4,8 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Lance backend, frontend et mcp-server en parallèle pour le développement local",
|
"description": "Lance backend, frontend et mcp-server en parallèle pour le développement local",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "concurrently -n BACKEND,FRONTEND,MCP -c blue,green,magenta \"npm run dev --prefix backend\" \"npm run dev --prefix frontend\" \"npm run dev --prefix mcp-server\""
|
"dev": "concurrently -n BACKEND,FRONTEND,MCP -c blue,green,magenta \"npm run dev --prefix backend\" \"npm run dev --prefix frontend\" \"npm run dev --prefix mcp-server\"",
|
||||||
|
"stop": "powershell -ExecutionPolicy Bypass -File scripts/stop-dev.ps1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"concurrently": "^9.1.2"
|
"concurrently": "^9.1.2"
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
# Stoppe proprement les processus laissés par "npm run dev"
|
||||||
|
# (backend:4000, frontend:5173, mcp-server:4100) même si le terminal
|
||||||
|
# d'origine a été fermé sans Ctrl+C.
|
||||||
|
|
||||||
|
$ports = @(4000, 5173, 4100)
|
||||||
|
$killed = 0
|
||||||
|
|
||||||
|
foreach ($port in $ports) {
|
||||||
|
$lines = netstat -ano | Select-String ":$port\s" | Select-String "LISTENING"
|
||||||
|
if (-not $lines) {
|
||||||
|
Write-Host "Port $port : rien à l'écoute." -ForegroundColor DarkGray
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
$pids = $lines | ForEach-Object { ($_ -split '\s+')[-1] } | Sort-Object -Unique
|
||||||
|
foreach ($processId in $pids) {
|
||||||
|
try {
|
||||||
|
$proc = Get-Process -Id $processId -ErrorAction Stop
|
||||||
|
Write-Host "Port $port : arrêt de $($proc.ProcessName) (PID $processId)" -ForegroundColor Yellow
|
||||||
|
Stop-Process -Id $processId -Force
|
||||||
|
$killed++
|
||||||
|
} catch {
|
||||||
|
Write-Host "Port $port : PID $processId déjà terminé." -ForegroundColor DarkGray
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($killed -eq 0) {
|
||||||
|
Write-Host "Aucun processus de dev actif à arrêter." -ForegroundColor Green
|
||||||
|
} else {
|
||||||
|
Write-Host "$killed processus arrêté(s)." -ForegroundColor Green
|
||||||
|
}
|
||||||
Reference in new issue
Block a user