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
@@ -92,6 +92,17 @@ router.get('/', (req, res) => {
|
||||
GROUP BY bonus_plateforme_id
|
||||
`).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(`
|
||||
SELECT i.plateforme_id,
|
||||
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 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 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 correctionsPerPlat) walletMap[r.plateforme_id] = (walletMap[r.plateforme_id] || 0) + r.total_correction;
|
||||
|
||||
@@ -149,6 +161,10 @@ router.get('/', (req, res) => {
|
||||
`).get(...invParams);
|
||||
|
||||
/* ── 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(`
|
||||
SELECT
|
||||
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.net_recu),0) AS net_recu
|
||||
FROM remboursements r
|
||||
JOIN investissements i ON i.id = r.investissement_id
|
||||
WHERE ${invWhere.replace('investisseur_id', 'i.investisseur_id')}
|
||||
`).get(...invParams);
|
||||
LEFT JOIN investissements i ON i.id = r.investissement_id
|
||||
WHERE (
|
||||
(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 ──────────────────────────────────────── */
|
||||
const interetsParAnnee = scopeAll
|
||||
@@ -618,6 +637,23 @@ router.get('/interets-par-plateforme', (req, res) => {
|
||||
ORDER BY p.nom, mois
|
||||
`).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 ──────────────────── */
|
||||
// Exclure les investissements qui ont déjà un remboursement réel dans le même mois
|
||||
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 };
|
||||
}
|
||||
}
|
||||
// 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) {
|
||||
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 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(),
|
||||
bonus_plateforme_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 };
|
||||
}
|
||||
|
||||
function getTaxIndicatif(investissementId) {
|
||||
if (!investissementId) return false;
|
||||
function getTaxIndicatif(body) {
|
||||
// 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(`
|
||||
SELECT p.fiscalite, i.fiscalite_override
|
||||
FROM investissements i JOIN plateformes p ON p.id = i.plateforme_id
|
||||
WHERE i.id = ?
|
||||
`).get(investissementId);
|
||||
`).get(body.investissement_id);
|
||||
if (!row) return false;
|
||||
return row.fiscalite !== 'flat_tax' || row.fiscalite_override === 'exonere';
|
||||
}
|
||||
@@ -167,6 +174,7 @@ router.get('/', (req, res) => {
|
||||
WHEN 'normal' THEN b.inv_nom_projet
|
||||
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' || 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
|
||||
END AS nom_projet,
|
||||
p.nom AS plateforme_nom,
|
||||
@@ -188,7 +196,7 @@ router.get('/', (req, res) => {
|
||||
router.post('/', (req, res, next) => {
|
||||
try {
|
||||
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') {
|
||||
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 });
|
||||
}
|
||||
|
||||
// Bonus (parrainage ou plateforme)
|
||||
// Bonus (parrainage / plateforme) ou Intérêts plateforme
|
||||
const bonusInvestisseurId = body.bonus_investisseur_id ?? req.investisseur.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(`
|
||||
INSERT INTO remboursements
|
||||
(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');
|
||||
|
||||
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') {
|
||||
db.prepare(`
|
||||
@@ -369,6 +394,17 @@ router.put('/:id', (req, res, next) => {
|
||||
syncInvestissementStatut(body.investissement_id);
|
||||
adjustSimulForActuals(db, body.investissement_id);
|
||||
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 {
|
||||
const bonusInvestisseurId = body.bonus_investisseur_id ?? existing.bonus_investisseur_id;
|
||||
db.prepare(`
|
||||
|
||||
Reference in new issue
Block a user