MAJ
This commit is contained in:
1 parent
14cf4e5a19
commit
26197fc411
15 files changed
+1776
-29
No files matched your search
@@ -48,6 +48,17 @@ const Schema = z.object({
|
||||
mode_detention: z.enum(['direct', 'pea_pme', 'assurance_vie']).default('direct'),
|
||||
});
|
||||
|
||||
// Déclaration d'une perte définitive de capital sur un deal PE (20/09/26, chantier
|
||||
// "Fiscalité PE" — cf. migration investissement_pe_pertes dans db/index.js). Même schéma que
|
||||
// PerteSchema côté crowdlending (investissements.js) : montant_perte éditable (perte partielle
|
||||
// possible si une partie du capital a déjà été recouvrée), préfilli côté frontend mais non
|
||||
// recalculé ici.
|
||||
const PerteSchema = z.object({
|
||||
date_effet: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
|
||||
montant_perte: z.number().nonnegative(),
|
||||
motif: z.string().trim().min(1, 'Le motif est obligatoire'),
|
||||
});
|
||||
|
||||
const ValorisationSchema = z.object({
|
||||
date_valorisation: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
|
||||
valorisation_nette: z.number().nonnegative(),
|
||||
@@ -402,7 +413,18 @@ router.get('/:id', (req, res, next) => {
|
||||
-- dans le TVPI (valeur réellement perçue), juste isolé ici pour l'affichage détaillé.
|
||||
COALESCE((SELECT SUM(r.net_recu) FROM remboursements r
|
||||
WHERE r.investissement_pe_id = ipe.id AND r.distribution_nature = 'interet_non_deploye'), 0)
|
||||
AS obligation_cumulee
|
||||
AS obligation_cumulee,
|
||||
-- Idem, pour la part "plus-value réalisée" des distributions (20/09/26, chantier
|
||||
-- "Fiscalité PE" — cf. distribution_nature dans RembFormModal.jsx). Inclus dans
|
||||
-- distributions_cumulees ET dans le TVPI, isolé ici pour l'affichage/le calcul fiscal.
|
||||
COALESCE((SELECT SUM(r.net_recu) FROM remboursements r
|
||||
WHERE r.investissement_pe_id = ipe.id AND r.distribution_nature = 'plus_value'), 0)
|
||||
AS plus_value_cumulee,
|
||||
-- Idem, pour la part "restitution de capital déployé" — sert à préfiller le montant par
|
||||
-- défaut de la modale "Déclarer une perte définitive" (capital pas encore restitué).
|
||||
COALESCE((SELECT SUM(r.net_recu) FROM remboursements r
|
||||
WHERE r.investissement_pe_id = ipe.id AND r.distribution_nature = 'capital'), 0)
|
||||
AS capital_restitue_cumule
|
||||
FROM investissements_pe ipe
|
||||
JOIN plateformes p ON p.id = ipe.plateforme_id
|
||||
JOIN investisseurs inv ON inv.id = ipe.investisseur_id
|
||||
@@ -412,6 +434,13 @@ router.get('/:id', (req, res, next) => {
|
||||
row.tvpi = row.derniere_valorisation != null
|
||||
? Math.round(((row.derniere_valorisation + row.distributions_cumulees) / row.montant_investi) * 100) / 100
|
||||
: null;
|
||||
// Pertes définitives (chantier "Fiscalité PE", 20/09/26) — au plus une ligne active à la
|
||||
// fois en pratique (statut repasse à perte_definitive), mais l'historique complet
|
||||
// (déclarations annulées comprises) est conservé pour traçabilité, comme
|
||||
// investissement_pertes côté crowdlending.
|
||||
row.pertes = db.prepare(
|
||||
'SELECT * FROM investissement_pe_pertes WHERE investissement_pe_id = ? ORDER BY id ASC'
|
||||
).all(id);
|
||||
res.json(row);
|
||||
} catch (e) { next(e); }
|
||||
});
|
||||
@@ -547,6 +576,67 @@ router.delete('/:id', requireInvestisseur, (req, res, next) => {
|
||||
} catch (e) { next(e); }
|
||||
});
|
||||
|
||||
/* ── Perte définitive (chantier "Fiscalité PE", 20/09/26) ───────
|
||||
* POST /:id/perte-definitive — déclare un deal en perte définitive (capital réellement
|
||||
* irrécouvrable : fonds liquidé sans distribution, société sous-jacente radiée…).
|
||||
* Contrairement au crowdlending, un deal PE n'a pas de statut intermédiaire de
|
||||
* recouvrement (en_retard/procedure) : n'importe quel statut autre que
|
||||
* 'perte_definitive' peut y transiter directement.
|
||||
* DELETE /:id/perte-definitive — annule la dernière déclaration, restaure le statut précédent.
|
||||
*/
|
||||
router.post('/:id/perte-definitive', requireInvestisseur, (req, res, next) => {
|
||||
try {
|
||||
const body = PerteSchema.parse(req.body);
|
||||
const id = Number(req.params.id);
|
||||
const deal = ownedDeal(req, id);
|
||||
if (deal.statut === 'perte_definitive') {
|
||||
throw new HttpError(400, 'Ce deal est déjà déclaré en perte définitive.');
|
||||
}
|
||||
|
||||
let perteId;
|
||||
const tx = db.transaction(() => {
|
||||
const r = db.prepare(`
|
||||
INSERT INTO investissement_pe_pertes (investissement_pe_id, date_effet, montant_perte, ancien_statut, motif)
|
||||
VALUES (?,?,?,?,?)
|
||||
`).run(id, body.date_effet, body.montant_perte, deal.statut, body.motif);
|
||||
perteId = r.lastInsertRowid;
|
||||
|
||||
db.prepare(`
|
||||
UPDATE investissements_pe SET statut = 'perte_definitive', updated_at = datetime('now') WHERE id = ?
|
||||
`).run(id);
|
||||
});
|
||||
tx();
|
||||
|
||||
const perte = db.prepare('SELECT * FROM investissement_pe_pertes WHERE id = ?').get(perteId);
|
||||
res.status(201).json(perte);
|
||||
} catch (e) { next(e); }
|
||||
});
|
||||
|
||||
router.delete('/:id/perte-definitive', requireInvestisseur, (req, res, next) => {
|
||||
try {
|
||||
const id = Number(req.params.id);
|
||||
const deal = ownedDeal(req, id);
|
||||
if (deal.statut !== 'perte_definitive') {
|
||||
throw new HttpError(400, "Ce deal n'est pas déclaré en perte définitive.");
|
||||
}
|
||||
|
||||
const derniere = db.prepare(
|
||||
'SELECT * FROM investissement_pe_pertes WHERE investissement_pe_id = ? ORDER BY id DESC LIMIT 1'
|
||||
).get(id);
|
||||
const statutRestaure = derniere?.ancien_statut || 'valide';
|
||||
|
||||
const tx = db.transaction(() => {
|
||||
if (derniere) db.prepare('DELETE FROM investissement_pe_pertes WHERE id = ?').run(derniere.id);
|
||||
db.prepare(`
|
||||
UPDATE investissements_pe SET statut = ?, updated_at = datetime('now') WHERE id = ?
|
||||
`).run(statutRestaure, id);
|
||||
});
|
||||
tx();
|
||||
|
||||
res.status(204).end();
|
||||
} catch (e) { next(e); }
|
||||
});
|
||||
|
||||
/* ── Historique des valorisations ────────────────────────────── */
|
||||
|
||||
router.get('/:id/valorisations', requireInvestisseur, (req, res, next) => {
|
||||
|
||||
Reference in new issue
Block a user