Amélioration de la tax report
This commit is contained in:
1 parent
849e16de33
commit
b43754fe19
2 files changed
+211
-6
No files matched your search
@@ -6,6 +6,10 @@ const router = Router();
|
||||
router.use(requireInvestisseur);
|
||||
|
||||
const round2 = v => Math.round((v ?? 0) * 100) / 100;
|
||||
// Échappement CSV (séparateur ';') — remplace les caractères qui casseraient les colonnes.
|
||||
// Bug préexistant : cette fonction était utilisée plus bas dans /export sans être définie
|
||||
// (ReferenceError à l'exécution), corrigé au passage.
|
||||
const esc = v => String(v ?? '').replace(/;/g, ',').replace(/[\r\n]+/g, ' ');
|
||||
|
||||
router.get('/', (req, res) => {
|
||||
const annee = req.query.annee || String(new Date().getFullYear() - 1);
|
||||
@@ -29,6 +33,32 @@ router.get('/', (req, res) => {
|
||||
AND r.statut IN ('paye','partiel')
|
||||
`).get(invArg, annee);
|
||||
|
||||
// Intérêts plateforme (type=interets_plateforme, pas d'investissement — rattachés via
|
||||
// bonus_plateforme_id/bonus_investisseur_id) : mêmes règles fiscales que la plateforme
|
||||
// associée, donc à additionner aux totaux comme des intérêts d'investissement classiques.
|
||||
const invCondBonus = scopeAll
|
||||
? 'r.bonus_investisseur_id IN (SELECT id FROM investisseurs WHERE user_id = ?)'
|
||||
: 'r.bonus_investisseur_id = ?';
|
||||
const recapInteretsPlateforme = db.prepare(`
|
||||
SELECT
|
||||
COALESCE(SUM(r.interets_bruts),0) AS interets_bruts,
|
||||
COALESCE(SUM(r.prelev_sociaux),0) AS prelev_sociaux,
|
||||
COALESCE(SUM(r.prelev_forfaitaire),0) AS prelev_forfaitaire,
|
||||
COALESCE(SUM(r.net_recu),0) AS net_recu,
|
||||
COUNT(*) AS nb_remboursements
|
||||
FROM remboursements r
|
||||
WHERE ${invCondBonus}
|
||||
AND substr(r.date_remb,1,4) = ?
|
||||
AND r.statut IN ('paye','partiel')
|
||||
AND r.type = 'interets_plateforme'
|
||||
`).get(invArg, annee);
|
||||
|
||||
recap.interets_bruts += recapInteretsPlateforme.interets_bruts;
|
||||
recap.prelev_sociaux += recapInteretsPlateforme.prelev_sociaux;
|
||||
recap.prelev_forfaitaire += recapInteretsPlateforme.prelev_forfaitaire;
|
||||
recap.net_recu += recapInteretsPlateforme.net_recu;
|
||||
recap.nb_remboursements += recapInteretsPlateforme.nb_remboursements;
|
||||
|
||||
const pertes = db.prepare(`
|
||||
SELECT i.id, i.nom_projet, p.nom AS plateforme_nom,
|
||||
i.montant_investi,
|
||||
@@ -125,6 +155,21 @@ router.get('/export', (req, res) => {
|
||||
ORDER BY r.date_remb
|
||||
`).all(invArg, annee);
|
||||
|
||||
const invCondBonusExp = scopeAll
|
||||
? 'r.bonus_investisseur_id IN (SELECT id FROM investisseurs WHERE user_id = ?)'
|
||||
: 'r.bonus_investisseur_id = ?';
|
||||
const interetsPlateformeRows = db.prepare(`
|
||||
SELECT
|
||||
'— Intérêts plateforme' || CASE WHEN NULLIF(r.notes, '') IS NOT NULL THEN ' — ' || r.notes ELSE '' END AS nom_projet,
|
||||
p.nom AS plateforme,
|
||||
r.date_remb, r.capital, r.interets_bruts,
|
||||
r.prelev_sociaux, r.prelev_forfaitaire, r.net_recu, r.statut
|
||||
FROM remboursements r
|
||||
JOIN plateformes p ON p.id = r.bonus_plateforme_id
|
||||
WHERE ${invCondBonusExp} AND substr(r.date_remb,1,4) = ? AND r.type = 'interets_plateforme'
|
||||
ORDER BY r.date_remb
|
||||
`).all(invArg, annee);
|
||||
|
||||
const corrCond2 = scopeAll
|
||||
? 'c.investisseur_id IN (SELECT id FROM investisseurs WHERE user_id = ?)'
|
||||
: 'c.investisseur_id = ?';
|
||||
@@ -141,7 +186,7 @@ router.get('/export', (req, res) => {
|
||||
`).all(invArg, annee);
|
||||
|
||||
const header = ['Projet','Plateforme','Date','Capital','Interets bruts','Prelev sociaux','Impot revenu','Net recu','Statut'];
|
||||
const allRows = [...rows, ...corrRows];
|
||||
const allRows = [...rows, ...interetsPlateformeRows, ...corrRows].sort((a, b) => (a.date_remb || '').localeCompare(b.date_remb || ''));
|
||||
const csv = [
|
||||
header.join(';'),
|
||||
...allRows.map(r => [
|
||||
@@ -190,6 +235,52 @@ router.get('/cerfa2561', (req, res) => {
|
||||
ORDER BY p.nom, inv.nom
|
||||
`).all(invArg, annee);
|
||||
|
||||
// Intérêts plateforme (pas d'investissement — rattachés via bonus_plateforme_id/
|
||||
// bonus_investisseur_id) : fusionnés sur la même ligne plateforme × investisseur que
|
||||
// les intérêts d'investissement classiques, car la fiscalité (domiciliation/fiscalite/
|
||||
// type_produit_fiscal) dépend de la plateforme, pas de l'investissement précis.
|
||||
const invCondBonus2561 = scopeAll
|
||||
? 'r.bonus_investisseur_id IN (SELECT id FROM investisseurs WHERE user_id = ?)'
|
||||
: 'r.bonus_investisseur_id = ?';
|
||||
const rowsInteretsPlateforme = db.prepare(`
|
||||
SELECT
|
||||
p.id AS plateforme_id,
|
||||
p.nom AS plateforme_nom,
|
||||
p.domiciliation,
|
||||
p.fiscalite,
|
||||
p.type_produit_fiscal,
|
||||
inv.id AS investisseur_id,
|
||||
inv.nom AS investisseur_nom,
|
||||
inv.prenom AS investisseur_prenom,
|
||||
COALESCE(SUM(r.interets_bruts), 0) AS interets_bruts,
|
||||
COALESCE(SUM(r.prelev_sociaux), 0) AS prelev_sociaux,
|
||||
COALESCE(SUM(r.prelev_forfaitaire), 0) AS prelev_forfaitaire
|
||||
FROM remboursements r
|
||||
JOIN plateformes p ON p.id = r.bonus_plateforme_id
|
||||
JOIN investisseurs inv ON inv.id = r.bonus_investisseur_id
|
||||
WHERE ${invCondBonus2561}
|
||||
AND substr(r.date_remb,1,4) = ?
|
||||
AND r.statut IN ('paye','partiel')
|
||||
AND r.type = 'interets_plateforme'
|
||||
GROUP BY p.id, inv.id
|
||||
`).all(invArg, annee);
|
||||
|
||||
// Fusion des deux sources sur la clé plateforme × investisseur
|
||||
const rowsMap = {};
|
||||
for (const r of rows) rowsMap[`${r.plateforme_id}_${r.investisseur_id}`] = { ...r };
|
||||
for (const r of rowsInteretsPlateforme) {
|
||||
const key = `${r.plateforme_id}_${r.investisseur_id}`;
|
||||
if (rowsMap[key]) {
|
||||
rowsMap[key].interets_bruts += r.interets_bruts;
|
||||
rowsMap[key].prelev_sociaux += r.prelev_sociaux;
|
||||
rowsMap[key].prelev_forfaitaire += r.prelev_forfaitaire;
|
||||
} else {
|
||||
rowsMap[key] = { ...r };
|
||||
}
|
||||
}
|
||||
const rowsMerged = Object.values(rowsMap).sort((a, b) =>
|
||||
a.plateforme_nom.localeCompare(b.plateforme_nom) || (a.investisseur_nom ?? '').localeCompare(b.investisseur_nom ?? ''));
|
||||
|
||||
// Pertes en capital par plateforme × investisseur
|
||||
const pertesRows = db.prepare(`
|
||||
SELECT
|
||||
@@ -212,7 +303,7 @@ router.get('/cerfa2561', (req, res) => {
|
||||
pertesMap[`${p.plateforme_id}_${p.investisseur_id}`] = Math.max(0, p.perte_capital);
|
||||
}
|
||||
|
||||
const lignes = rows.map(r => {
|
||||
const lignes = rowsMerged.map(r => {
|
||||
const key = `${r.plateforme_id}_${r.investisseur_id}`;
|
||||
const perteCapital = pertesMap[key] ?? 0;
|
||||
const isFlatTax = r.domiciliation === 'FR' && r.fiscalite === 'flat_tax';
|
||||
@@ -265,6 +356,23 @@ router.get('/cerfa2561', (req, res) => {
|
||||
GROUP BY p.id, i.investisseur_id, mois
|
||||
`).all(invArg, annee);
|
||||
|
||||
const moisRowsInteretsPlateforme = db.prepare(`
|
||||
SELECT
|
||||
p.id AS plateforme_id,
|
||||
r.bonus_investisseur_id AS investisseur_id,
|
||||
substr(r.date_remb,6,2) AS mois,
|
||||
COALESCE(SUM(r.interets_bruts), 0) AS interets_bruts,
|
||||
COALESCE(SUM(r.prelev_sociaux), 0) AS prelev_sociaux,
|
||||
COALESCE(SUM(r.prelev_forfaitaire), 0) AS prelev_forfaitaire
|
||||
FROM remboursements r
|
||||
JOIN plateformes p ON p.id = r.bonus_plateforme_id
|
||||
WHERE ${invCondBonus2561}
|
||||
AND substr(r.date_remb,1,4) = ?
|
||||
AND r.statut IN ('paye','partiel')
|
||||
AND r.type = 'interets_plateforme'
|
||||
GROUP BY p.id, r.bonus_investisseur_id, mois
|
||||
`).all(invArg, annee);
|
||||
|
||||
const moisMap = {};
|
||||
for (const m of moisRows) {
|
||||
const key = `${m.plateforme_id}_${m.investisseur_id}`;
|
||||
@@ -275,6 +383,16 @@ router.get('/cerfa2561', (req, res) => {
|
||||
prelev_forfaitaire: round2(m.prelev_forfaitaire),
|
||||
};
|
||||
}
|
||||
for (const m of moisRowsInteretsPlateforme) {
|
||||
const key = `${m.plateforme_id}_${m.investisseur_id}`;
|
||||
if (!moisMap[key]) moisMap[key] = {};
|
||||
const existing = moisMap[key][m.mois];
|
||||
moisMap[key][m.mois] = {
|
||||
interets_bruts: round2((existing?.interets_bruts ?? 0) + m.interets_bruts),
|
||||
prelev_sociaux: round2((existing?.prelev_sociaux ?? 0) + m.prelev_sociaux),
|
||||
prelev_forfaitaire: round2((existing?.prelev_forfaitaire ?? 0) + m.prelev_forfaitaire),
|
||||
};
|
||||
}
|
||||
|
||||
const lignesWithMois = lignes.map(l => ({
|
||||
...l,
|
||||
@@ -318,7 +436,31 @@ router.get('/cerfa2561/remboursements', (req, res) => {
|
||||
ORDER BY r.date_remb
|
||||
`).all(invArg, ...platArgs, annee);
|
||||
|
||||
res.json(rows);
|
||||
// Intérêts plateforme correspondant à la même plateforme (et investisseur si précisé)
|
||||
const invCondBonusDetail = scopeAll
|
||||
? 'r.bonus_investisseur_id IN (SELECT id FROM investisseurs WHERE user_id = ?)'
|
||||
: 'r.bonus_investisseur_id = ?';
|
||||
const platCondBonus = investisseur_id
|
||||
? 'AND p.id = ? AND r.bonus_investisseur_id = ?'
|
||||
: 'AND p.id = ?';
|
||||
const rowsInteretsPlateforme = db.prepare(`
|
||||
SELECT
|
||||
r.id, r.date_remb, r.capital, r.interets_bruts,
|
||||
r.prelev_sociaux, r.prelev_forfaitaire, r.interets_nets, r.net_recu,
|
||||
r.statut, r.notes,
|
||||
'— Intérêts plateforme' || CASE WHEN NULLIF(r.notes, '') IS NOT NULL THEN ' — ' || r.notes ELSE '' END AS nom_projet
|
||||
FROM remboursements r
|
||||
JOIN plateformes p ON p.id = r.bonus_plateforme_id
|
||||
WHERE ${invCondBonusDetail} ${platCondBonus}
|
||||
AND substr(r.date_remb,1,4) = ?
|
||||
AND r.statut IN ('paye','partiel')
|
||||
AND r.type = 'interets_plateforme'
|
||||
ORDER BY r.date_remb
|
||||
`).all(invArg, ...platArgs, annee);
|
||||
|
||||
const allRows = [...rows, ...rowsInteretsPlateforme].sort((a, b) => (a.date_remb || '').localeCompare(b.date_remb || ''));
|
||||
|
||||
res.json(allRows);
|
||||
});
|
||||
|
||||
|
||||
@@ -346,7 +488,20 @@ router.get('/years', (req, res) => {
|
||||
ORDER BY annee DESC
|
||||
`).all(...invParams);
|
||||
|
||||
res.json(rows.map(r => r.annee));
|
||||
const invWhereBonus = scopeAll
|
||||
? 'r.bonus_investisseur_id IN (SELECT id FROM investisseurs WHERE user_id = ?)'
|
||||
: 'r.bonus_investisseur_id = ?';
|
||||
const rowsInteretsPlateforme = db.prepare(`
|
||||
SELECT DISTINCT strftime('%Y', r.date_remb) AS annee
|
||||
FROM remboursements r
|
||||
WHERE ${invWhereBonus}
|
||||
AND r.type = 'interets_plateforme'
|
||||
`).all(...invParams);
|
||||
|
||||
const years = [...new Set([...rows.map(r => r.annee), ...rowsInteretsPlateforme.map(r => r.annee)])]
|
||||
.sort((a, b) => b.localeCompare(a));
|
||||
|
||||
res.json(years);
|
||||
});
|
||||
|
||||
|
||||
@@ -375,6 +530,30 @@ router.get('/2778', (req, res) => {
|
||||
ORDER BY p.nom, inv.nom
|
||||
`).all(invArg, annee);
|
||||
|
||||
// Intérêts plateforme (pas d'investissement) sur des plateformes étrangères — même
|
||||
// logique de fusion que pour le 2561 : rattachés à la ligne plateforme × investisseur.
|
||||
const invCondBonus2778 = scopeAll
|
||||
? 'r.bonus_investisseur_id IN (SELECT id FROM investisseurs WHERE user_id = ?)'
|
||||
: 'r.bonus_investisseur_id = ?';
|
||||
const platRowsInteretsPlateforme = db.prepare(`
|
||||
SELECT DISTINCT p.id, p.nom, inv.id AS investisseur_id, inv.nom AS investisseur_nom, inv.prenom AS investisseur_prenom
|
||||
FROM remboursements r
|
||||
JOIN plateformes p ON p.id = r.bonus_plateforme_id
|
||||
JOIN investisseurs inv ON inv.id = r.bonus_investisseur_id
|
||||
WHERE ${invCondBonus2778}
|
||||
AND substr(r.date_remb,1,4) = ?
|
||||
AND r.statut IN ('paye','partiel')
|
||||
AND r.type = 'interets_plateforme'
|
||||
AND p.domiciliation != 'FR'
|
||||
`).all(invArg, annee);
|
||||
|
||||
const platRowsMap = {};
|
||||
for (const p of [...platRows, ...platRowsInteretsPlateforme]) {
|
||||
platRowsMap[`${p.id}_${p.investisseur_id}`] = p;
|
||||
}
|
||||
const platRowsMerged = Object.values(platRowsMap).sort((a, b) =>
|
||||
a.nom.localeCompare(b.nom) || (a.investisseur_nom ?? '').localeCompare(b.investisseur_nom ?? ''));
|
||||
|
||||
// Montants mensuels par plateforme × investisseur
|
||||
const moisRows = db.prepare(`
|
||||
SELECT
|
||||
@@ -398,14 +577,40 @@ router.get('/2778', (req, res) => {
|
||||
GROUP BY p.id, i.investisseur_id, mois
|
||||
`).all(invArg, annee);
|
||||
|
||||
const moisRowsInteretsPlateforme = db.prepare(`
|
||||
SELECT
|
||||
p.id AS plateforme_id,
|
||||
r.bonus_investisseur_id AS investisseur_id,
|
||||
substr(r.date_remb,6,2) AS mois,
|
||||
COALESCE(SUM(
|
||||
CASE WHEN r.interets_bruts_avant_local IS NOT NULL AND r.interets_bruts_avant_local > 0
|
||||
THEN r.interets_bruts_avant_local
|
||||
ELSE r.interets_bruts
|
||||
END
|
||||
), 0) AS montant
|
||||
FROM remboursements r
|
||||
JOIN plateformes p ON p.id = r.bonus_plateforme_id
|
||||
WHERE ${invCondBonus2778}
|
||||
AND substr(r.date_remb,1,4) = ?
|
||||
AND r.statut IN ('paye','partiel')
|
||||
AND r.type = 'interets_plateforme'
|
||||
AND p.domiciliation != 'FR'
|
||||
GROUP BY p.id, r.bonus_investisseur_id, mois
|
||||
`).all(invArg, annee);
|
||||
|
||||
const moisMap = {};
|
||||
for (const m of moisRows) {
|
||||
const key = `${m.plateforme_id}_${m.investisseur_id}`;
|
||||
if (!moisMap[key]) moisMap[key] = {};
|
||||
moisMap[key][m.mois] = round2(m.montant);
|
||||
}
|
||||
for (const m of moisRowsInteretsPlateforme) {
|
||||
const key = `${m.plateforme_id}_${m.investisseur_id}`;
|
||||
if (!moisMap[key]) moisMap[key] = {};
|
||||
moisMap[key][m.mois] = round2((moisMap[key][m.mois] ?? 0) + m.montant);
|
||||
}
|
||||
|
||||
const plateformes = platRows.map(p => ({
|
||||
const plateformes = platRowsMerged.map(p => ({
|
||||
id: `${p.id}_${p.investisseur_id}`,
|
||||
plateforme_id: p.id,
|
||||
nom: p.nom,
|
||||
|
||||
Reference in new issue
Block a user