Initial commit
This commit is contained in:
@@ -0,0 +1,421 @@
|
||||
import { Router } from 'express';
|
||||
import db from '../db/index.js';
|
||||
import { requireInvestisseur } from '../middleware/investisseurScope.js';
|
||||
|
||||
const router = Router();
|
||||
router.use(requireInvestisseur);
|
||||
|
||||
const round2 = v => Math.round((v ?? 0) * 100) / 100;
|
||||
|
||||
router.get('/', (req, res) => {
|
||||
const annee = req.query.annee || String(new Date().getFullYear() - 1);
|
||||
const scopeAll = req.query.scope === 'all';
|
||||
const invCond = scopeAll
|
||||
? 'i.investisseur_id IN (SELECT id FROM investisseurs WHERE user_id = ?)'
|
||||
: 'i.investisseur_id = ?';
|
||||
const invArg = scopeAll ? req.user.id : req.investisseur.id;
|
||||
|
||||
const recap = 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
|
||||
JOIN investissements i ON i.id = r.investissement_id
|
||||
WHERE ${invCond}
|
||||
AND substr(r.date_remb,1,4) = ?
|
||||
AND r.statut IN ('paye','partiel')
|
||||
`).get(invArg, annee);
|
||||
|
||||
const pertes = db.prepare(`
|
||||
SELECT i.id, i.nom_projet, p.nom AS plateforme_nom,
|
||||
i.montant_investi,
|
||||
COALESCE((SELECT SUM(r.capital) FROM remboursements r WHERE r.investissement_id = i.id),0) AS capital_rembourse,
|
||||
(i.montant_investi -
|
||||
COALESCE((SELECT SUM(r.capital) FROM remboursements r WHERE r.investissement_id = i.id),0)
|
||||
) AS perte_capital,
|
||||
i.statut, i.updated_at
|
||||
FROM investissements i
|
||||
JOIN plateformes p ON p.id = i.plateforme_id
|
||||
WHERE ${invCond}
|
||||
AND i.statut IN ('en_retard','cloture')
|
||||
AND substr(i.updated_at,1,4) = ?
|
||||
`).all(invArg, annee);
|
||||
|
||||
const pertesTotales = pertes.reduce((s, p) => s + Math.max(0, p.perte_capital), 0);
|
||||
|
||||
const detail = db.prepare(`
|
||||
SELECT i.id AS investissement_id, i.nom_projet, p.nom AS plateforme_nom,
|
||||
inv.nom AS investisseur_nom,
|
||||
SUM(r.interets_bruts) AS interets_bruts,
|
||||
SUM(r.prelev_sociaux) AS prelev_sociaux,
|
||||
SUM(r.prelev_forfaitaire) AS prelev_forfaitaire,
|
||||
SUM(r.net_recu) AS net_recu,
|
||||
COUNT(r.id) AS nb_echeances
|
||||
FROM remboursements r
|
||||
JOIN investissements i ON i.id = r.investissement_id
|
||||
JOIN investisseurs inv ON inv.id = i.investisseur_id
|
||||
JOIN plateformes p ON p.id = i.plateforme_id
|
||||
WHERE ${invCond}
|
||||
AND substr(r.date_remb,1,4) = ?
|
||||
AND r.statut IN ('paye','partiel')
|
||||
GROUP BY i.id
|
||||
ORDER BY interets_bruts DESC
|
||||
`).all(invArg, annee);
|
||||
|
||||
const corrCond = scopeAll
|
||||
? 'c.investisseur_id IN (SELECT id FROM investisseurs WHERE user_id = ?)'
|
||||
: 'c.investisseur_id = ?';
|
||||
|
||||
const corrections = db.prepare(`
|
||||
SELECT c.id, c.date, c.montant, c.notes,
|
||||
p.nom AS plateforme_nom,
|
||||
inv.nom AS investisseur_nom
|
||||
FROM corrections_solde c
|
||||
JOIN plateformes p ON p.id = c.plateforme_id
|
||||
JOIN investisseurs inv ON inv.id = c.investisseur_id
|
||||
WHERE ${corrCond}
|
||||
AND substr(c.date,1,4) = ?
|
||||
ORDER BY c.date DESC
|
||||
`).all(invArg, annee);
|
||||
|
||||
const totalCorrections = corrections.reduce((s, c) => s + c.montant, 0);
|
||||
|
||||
const interetsNets = recap.interets_bruts - recap.prelev_sociaux - recap.prelev_forfaitaire;
|
||||
|
||||
const recapTotal = {
|
||||
interets_bruts: round2(recap.interets_bruts),
|
||||
prelev_sociaux: round2(recap.prelev_sociaux),
|
||||
prelev_forfaitaire: round2(recap.prelev_forfaitaire),
|
||||
interets_nets: round2(interetsNets),
|
||||
interets_nets_corriges: round2(interetsNets + totalCorrections),
|
||||
nb_remboursements: recap.nb_remboursements,
|
||||
total_corrections: round2(totalCorrections),
|
||||
};
|
||||
|
||||
const cases = {
|
||||
case_2TR: round2(recap.interets_bruts),
|
||||
case_2CK: round2(recap.prelev_forfaitaire),
|
||||
case_2BH: round2(recap.interets_bruts),
|
||||
case_2BH_perte_capital: round2(pertesTotales),
|
||||
note: "Les cases sont indicatives. Verifiez avec votre situation fiscale reelle.",
|
||||
};
|
||||
|
||||
res.json({ annee, recap: recapTotal, pertes, pertesTotales: round2(pertesTotales), detail, corrections, cases });
|
||||
});
|
||||
|
||||
router.get('/export', (req, res) => {
|
||||
const annee = req.query.annee || String(new Date().getFullYear() - 1);
|
||||
const scopeAll = req.query.scope === 'all';
|
||||
const invCond = scopeAll
|
||||
? 'i.investisseur_id IN (SELECT id FROM investisseurs WHERE user_id = ?)'
|
||||
: 'i.investisseur_id = ?';
|
||||
const invArg = scopeAll ? req.user.id : req.investisseur.id;
|
||||
|
||||
const rows = db.prepare(`
|
||||
SELECT i.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 investissements i ON i.id = r.investissement_id
|
||||
JOIN plateformes p ON p.id = i.plateforme_id
|
||||
WHERE ${invCond} AND substr(r.date_remb,1,4) = ?
|
||||
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 = ?';
|
||||
|
||||
const corrRows = db.prepare(`
|
||||
SELECT c.notes AS nom_projet, p.nom AS plateforme, c.date AS date_remb,
|
||||
0 AS capital, 0 AS interets_bruts,
|
||||
0 AS prelev_sociaux, 0 AS prelev_forfaitaire,
|
||||
c.montant AS net_recu, 'correction_solde' AS statut
|
||||
FROM corrections_solde c
|
||||
JOIN plateformes p ON p.id = c.plateforme_id
|
||||
WHERE ${corrCond2} AND substr(c.date,1,4) = ?
|
||||
ORDER BY c.date
|
||||
`).all(invArg, annee);
|
||||
|
||||
const header = ['Projet','Plateforme','Date','Capital','Interets bruts','Prelev sociaux','Impot revenu','Net recu','Statut'];
|
||||
const allRows = [...rows, ...corrRows];
|
||||
const csv = [
|
||||
header.join(';'),
|
||||
...allRows.map(r => [
|
||||
esc(r.nom_projet), esc(r.plateforme), r.date_remb,
|
||||
r.capital, r.interets_bruts, r.prelev_sociaux, r.prelev_forfaitaire,
|
||||
r.net_recu, r.statut,
|
||||
].join(';')),
|
||||
].join('\n');
|
||||
|
||||
res.setHeader('Content-Type', 'text/csv; charset=utf-8');
|
||||
res.setHeader('Content-Disposition', `attachment; filename="2778-SD-${annee}.csv"`);
|
||||
res.send('\uFEFF' + csv);
|
||||
});
|
||||
|
||||
/* ── CERFA 2561 — synthèse par plateforme × investisseur ── */
|
||||
router.get('/cerfa2561', (req, res) => {
|
||||
const annee = req.query.annee || String(new Date().getFullYear() - 1);
|
||||
const scopeAll = req.query.scope === 'all';
|
||||
const invCond = scopeAll
|
||||
? 'i.investisseur_id IN (SELECT id FROM investisseurs WHERE user_id = ?)'
|
||||
: 'i.investisseur_id = ?';
|
||||
const invArg = scopeAll ? req.user.id : req.investisseur.id;
|
||||
|
||||
const rows = 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 investissements i ON i.id = r.investissement_id
|
||||
JOIN plateformes p ON p.id = i.plateforme_id
|
||||
JOIN investisseurs inv ON inv.id = i.investisseur_id
|
||||
WHERE ${invCond}
|
||||
AND substr(r.date_remb,1,4) = ?
|
||||
AND r.statut IN ('paye','partiel')
|
||||
AND r.type = 'normal'
|
||||
GROUP BY p.id, inv.id
|
||||
ORDER BY p.nom, inv.nom
|
||||
`).all(invArg, annee);
|
||||
|
||||
// Pertes en capital par plateforme × investisseur
|
||||
const pertesRows = db.prepare(`
|
||||
SELECT
|
||||
p.id AS plateforme_id,
|
||||
i.investisseur_id,
|
||||
COALESCE(SUM(
|
||||
i.montant_investi -
|
||||
COALESCE((SELECT SUM(r2.capital) FROM remboursements r2 WHERE r2.investissement_id = i.id),0)
|
||||
), 0) AS perte_capital
|
||||
FROM investissements i
|
||||
JOIN plateformes p ON p.id = i.plateforme_id
|
||||
WHERE ${invCond}
|
||||
AND i.statut IN ('en_retard','cloture')
|
||||
AND substr(i.updated_at,1,4) = ?
|
||||
GROUP BY p.id, i.investisseur_id
|
||||
`).all(invArg, annee);
|
||||
|
||||
const pertesMap = {};
|
||||
for (const p of pertesRows) {
|
||||
pertesMap[`${p.plateforme_id}_${p.investisseur_id}`] = Math.max(0, p.perte_capital);
|
||||
}
|
||||
|
||||
const lignes = rows.map(r => {
|
||||
const key = `${r.plateforme_id}_${r.investisseur_id}`;
|
||||
const perteCapital = pertesMap[key] ?? 0;
|
||||
const isFlatTax = r.domiciliation === 'FR' && r.fiscalite === 'flat_tax';
|
||||
const use2TR = r.type_produit_fiscal === '2TR';
|
||||
const interetsNets = round2(r.interets_bruts - r.prelev_sociaux - r.prelev_forfaitaire);
|
||||
return {
|
||||
annee,
|
||||
plateforme_id: r.plateforme_id,
|
||||
plateforme_nom: r.plateforme_nom,
|
||||
domiciliation: r.domiciliation,
|
||||
fiscalite: r.fiscalite,
|
||||
type_produit_fiscal: r.type_produit_fiscal,
|
||||
investisseur_id: r.investisseur_id,
|
||||
investisseur_nom: r.investisseur_nom,
|
||||
investisseur_prenom: r.investisseur_prenom,
|
||||
interets_bruts: round2(r.interets_bruts),
|
||||
prelev_sociaux: round2(r.prelev_sociaux),
|
||||
prelev_forfaitaire: round2(r.prelev_forfaitaire),
|
||||
interets_nets: interetsNets,
|
||||
// Cases fiscales — d'après IFU réels :
|
||||
// 2TT ou 2TR = intérêts bruts (toutes plateformes)
|
||||
// 2BH = intérêts bruts si PS déjà prélevés (prelev_sociaux > 0)
|
||||
// 2CK = PFNL déjà versé (flat-tax FR uniquement)
|
||||
// 2TY = pertes en capital
|
||||
case_2TT: !use2TR ? Math.round(r.interets_bruts) : 0,
|
||||
case_2TR: use2TR ? Math.round(r.interets_bruts) : 0,
|
||||
case_2BH: r.prelev_sociaux > 0 ? Math.round(r.interets_bruts) : 0,
|
||||
case_2CK: isFlatTax ? Math.round(r.prelev_forfaitaire) : 0,
|
||||
case_2TY: perteCapital > 0 ? Math.round(perteCapital) : 0,
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
// Breakdown mensuel par plateforme × investisseur
|
||||
const moisRows = db.prepare(`
|
||||
SELECT
|
||||
p.id AS plateforme_id,
|
||||
i.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 investissements i ON i.id = r.investissement_id
|
||||
JOIN plateformes p ON p.id = i.plateforme_id
|
||||
WHERE ${invCond}
|
||||
AND substr(r.date_remb,1,4) = ?
|
||||
AND r.statut IN ('paye','partiel')
|
||||
AND r.type = 'normal'
|
||||
GROUP BY p.id, i.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] = {
|
||||
interets_bruts: round2(m.interets_bruts),
|
||||
prelev_sociaux: round2(m.prelev_sociaux),
|
||||
prelev_forfaitaire: round2(m.prelev_forfaitaire),
|
||||
};
|
||||
}
|
||||
|
||||
const lignesWithMois = lignes.map(l => ({
|
||||
...l,
|
||||
mois: moisMap[`${l.plateforme_id}_${l.investisseur_id}`] ?? {},
|
||||
}));
|
||||
|
||||
res.json({ annee, lignes: lignesWithMois });
|
||||
});
|
||||
|
||||
/* ── CERFA 2561 — détail des remboursements par plateforme × investisseur ── */
|
||||
router.get('/cerfa2561/remboursements', (req, res) => {
|
||||
const { annee, plateforme_id, investisseur_id } = req.query;
|
||||
if (!annee || !plateforme_id) return res.status(400).json({ error: 'annee et plateforme_id requis' });
|
||||
|
||||
const scopeAll = req.query.scope === 'all';
|
||||
const invCond = scopeAll
|
||||
? 'i.investisseur_id IN (SELECT id FROM investisseurs WHERE user_id = ?)'
|
||||
: 'i.investisseur_id = ?';
|
||||
const invArg = scopeAll ? req.user.id : req.investisseur.id;
|
||||
|
||||
const platCond = investisseur_id
|
||||
? 'AND p.id = ? AND i.investisseur_id = ?'
|
||||
: 'AND p.id = ?';
|
||||
const platArgs = investisseur_id
|
||||
? [Number(plateforme_id), Number(investisseur_id)]
|
||||
: [Number(plateforme_id)];
|
||||
|
||||
const rows = 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,
|
||||
i.nom_projet
|
||||
FROM remboursements r
|
||||
JOIN investissements i ON i.id = r.investissement_id
|
||||
JOIN plateformes p ON p.id = i.plateforme_id
|
||||
WHERE ${invCond} ${platCond}
|
||||
AND substr(r.date_remb,1,4) = ?
|
||||
AND r.statut IN ('paye','partiel')
|
||||
AND r.type = 'normal'
|
||||
ORDER BY r.date_remb
|
||||
`).all(invArg, ...platArgs, annee);
|
||||
|
||||
res.json(rows);
|
||||
});
|
||||
|
||||
|
||||
/* ── Années disponibles ── */
|
||||
router.get('/years', (req, res) => {
|
||||
const scopeAll = req.query.scope === 'all';
|
||||
let invWhere, invParams;
|
||||
|
||||
if (scopeAll) {
|
||||
invWhere = 'i.investisseur_id IN (SELECT id FROM investisseurs WHERE user_id = ?)';
|
||||
invParams = [req.user.id];
|
||||
} else {
|
||||
const invId = Number(req.header('X-Investisseur-Id'));
|
||||
if (!invId) return res.status(400).json({ error: 'Missing investisseur id' });
|
||||
invWhere = 'i.investisseur_id = ?';
|
||||
invParams = [invId];
|
||||
}
|
||||
|
||||
const rows = db.prepare(`
|
||||
SELECT DISTINCT strftime('%Y', r.date_remb) AS annee
|
||||
FROM remboursements r
|
||||
JOIN investissements i ON i.id = r.investissement_id
|
||||
WHERE ${invWhere}
|
||||
AND r.type = 'normal'
|
||||
ORDER BY annee DESC
|
||||
`).all(...invParams);
|
||||
|
||||
res.json(rows.map(r => r.annee));
|
||||
});
|
||||
|
||||
|
||||
|
||||
/* ── 2778-SD — matrice mensuelle par plateforme étrangère ── */
|
||||
router.get('/2778', (req, res) => {
|
||||
const annee = req.query.annee || String(new Date().getFullYear() - 1);
|
||||
const scopeAll = req.query.scope === 'all';
|
||||
const invCond = scopeAll
|
||||
? 'i.investisseur_id IN (SELECT id FROM investisseurs WHERE user_id = ?)'
|
||||
: 'i.investisseur_id = ?';
|
||||
const invArg = scopeAll ? req.user.id : req.investisseur.id;
|
||||
|
||||
// Plateformes étrangères × investisseur avec au moins un remboursement sur l'année
|
||||
const platRows = 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 investissements i ON i.id = r.investissement_id
|
||||
JOIN plateformes p ON p.id = i.plateforme_id
|
||||
JOIN investisseurs inv ON inv.id = i.investisseur_id
|
||||
WHERE ${invCond}
|
||||
AND substr(r.date_remb,1,4) = ?
|
||||
AND r.statut IN ('paye','partiel')
|
||||
AND r.type = 'normal'
|
||||
AND p.domiciliation != 'FR'
|
||||
ORDER BY p.nom, inv.nom
|
||||
`).all(invArg, annee);
|
||||
|
||||
// Montants mensuels par plateforme × investisseur
|
||||
const moisRows = db.prepare(`
|
||||
SELECT
|
||||
p.id AS plateforme_id,
|
||||
i.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 investissements i ON i.id = r.investissement_id
|
||||
JOIN plateformes p ON p.id = i.plateforme_id
|
||||
WHERE ${invCond}
|
||||
AND substr(r.date_remb,1,4) = ?
|
||||
AND r.statut IN ('paye','partiel')
|
||||
AND r.type = 'normal'
|
||||
AND p.domiciliation != 'FR'
|
||||
GROUP BY p.id, i.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);
|
||||
}
|
||||
|
||||
const plateformes = platRows.map(p => ({
|
||||
id: `${p.id}_${p.investisseur_id}`,
|
||||
plateforme_id: p.id,
|
||||
nom: p.nom,
|
||||
investisseur_id: p.investisseur_id,
|
||||
investisseur_nom: p.investisseur_nom,
|
||||
investisseur_prenom: p.investisseur_prenom,
|
||||
mois: moisMap[`${p.id}_${p.investisseur_id}`] ?? {},
|
||||
}));
|
||||
|
||||
res.json({ annee, plateformes });
|
||||
});
|
||||
|
||||
export default router;
|
||||
Reference in New Issue
Block a user