Bug Prêts différés
This commit is contained in:
1 parent
26197fc411
commit
e8e3ca34cc
1 file changed
+46
-26
@@ -264,6 +264,10 @@ router.post('/fix-differe-dates', (req, res, next) => {
|
|||||||
WHERE i.type_remb = 'differe'
|
WHERE i.type_remb = 'differe'
|
||||||
AND i.date_souscription IS NOT NULL
|
AND i.date_souscription IS NOT NULL
|
||||||
AND i.duree_mois IS NOT NULL
|
AND i.duree_mois IS NOT NULL
|
||||||
|
-- Bug corrigé le 26/09/26 (dossier "Le Mitra") : un prêt déjà remboursé/clôturé n'a
|
||||||
|
-- plus lieu d'avoir sa date d'échéance "corrigée" — cf. commentaire plus bas sur
|
||||||
|
-- regenererEcheancier() pour le détail du bug que cette exclusion referme.
|
||||||
|
AND i.statut NOT IN ('rembourse', 'cloture')
|
||||||
`).all(req.user.id);
|
`).all(req.user.id);
|
||||||
|
|
||||||
// Approximation jours/mois (moyenne julienne) — cohérent avec l'ancien seuil fixe
|
// Approximation jours/mois (moyenne julienne) — cohérent avec l'ancien seuil fixe
|
||||||
@@ -327,20 +331,20 @@ router.post('/fix-differe-dates', (req, res, next) => {
|
|||||||
`écart > ${seuilMois} mois avec date_souscription + duree_mois`,
|
`écart > ${seuilMois} mois avec date_souscription + duree_mois`,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Régénère l'échéancier de projection avec la date corrigée — sans ça,
|
// Régénère l'échéancier de projection avec la date corrigée, PUIS réconcilie contre
|
||||||
// simul_remboursements reste calé sur l'ancienne date aberrante.
|
// les remboursements réels déjà enregistrés. Bug corrigé le 26/09/26 (dossier "Le
|
||||||
generateSimul(db, {
|
// Mitra", statut déjà "Remboursé" depuis le 24/07/2025) : un generateSimul() direct
|
||||||
id: inv.id,
|
// ignore tout remboursement déjà perçu et recrée toujours une échéance naïve à la
|
||||||
montant_investi: inv.montant_investi,
|
// date corrigée — y compris pour un prêt intégralement soldé, dont la date corrigée
|
||||||
taux_interet: inv.taux_interet,
|
// (date_souscription + duree_mois) tombait dans le futur. L'échéance orpheline
|
||||||
duree_mois: inv.duree_mois,
|
// réapparaissait alors comme "à recevoir" dans le Dashboard et sur la fiche du prêt.
|
||||||
type_remb: inv.type_remb,
|
// regenererEcheancier() applique le même correctif que POST/PUT /revisions (cf.
|
||||||
freq_interets: inv.freq_interets,
|
// project_revision_correction) : pour un prêt différé (jamais de restructuration,
|
||||||
date_premiere_echeance: dateCalculee,
|
// donc jamais de date_debut_simul), il rejoue l'historique des remboursements de
|
||||||
date_debut_simul: inv.date_debut_simul,
|
// capital via replayAdjustSimulForActuals() après régénération, qui supprime toute
|
||||||
date_souscription: inv.date_souscription,
|
// échéance future désormais caduque. Filet de sécurité en plus de l'exclusion des
|
||||||
echeance_fin_de_mois: inv.echeance_fin_de_mois ?? 0,
|
// prêts clôturés/remboursés ajoutée ci-dessus (WHERE i.statut NOT IN (...)).
|
||||||
});
|
regenererEcheancier(inv.id);
|
||||||
|
|
||||||
corriges.push({
|
corriges.push({
|
||||||
id: inv.id,
|
id: inv.id,
|
||||||
@@ -376,7 +380,7 @@ router.post('/fix-differe-dates', (req, res, next) => {
|
|||||||
router.post('/check-echeancier-differe', (req, res, next) => {
|
router.post('/check-echeancier-differe', (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const rows = db.prepare(`
|
const rows = db.prepare(`
|
||||||
SELECT i.id, i.nom_projet, i.date_premiere_echeance, i.date_cible,
|
SELECT i.id, i.nom_projet, i.statut, i.date_premiere_echeance, i.date_cible,
|
||||||
i.montant_investi, i.taux_interet, i.duree_mois, i.type_remb, i.freq_interets,
|
i.montant_investi, i.taux_interet, i.duree_mois, i.type_remb, i.freq_interets,
|
||||||
i.date_debut_simul, i.date_souscription, i.echeance_fin_de_mois
|
i.date_debut_simul, i.date_souscription, i.echeance_fin_de_mois
|
||||||
FROM investissements i
|
FROM investissements i
|
||||||
@@ -393,15 +397,23 @@ router.post('/check-echeancier-differe', (req, res, next) => {
|
|||||||
WHERE investissement_id = ?
|
WHERE investissement_id = ?
|
||||||
ORDER BY numero_echeance
|
ORDER BY numero_echeance
|
||||||
`);
|
`);
|
||||||
const hasReinvest = db.prepare('SELECT 1 FROM reinvestissements WHERE investissement_id = ? LIMIT 1');
|
|
||||||
|
|
||||||
const corriges = [];
|
const corriges = [];
|
||||||
|
|
||||||
for (const inv of rows) {
|
for (const inv of rows) {
|
||||||
const simulRows = getSimul.all(inv.id);
|
const simulRows = getSimul.all(inv.id);
|
||||||
const incoherent =
|
|
||||||
simulRows.length !== 1 ||
|
// Bug corrigé le 26/09/26 (dossier "Le Mitra") : un prêt déjà remboursé ou clôturé ne
|
||||||
simulRows[0].date_prevue !== inv.date_premiere_echeance;
|
// doit conserver AUCUNE échéance de projection — même une échéance unique dont la date
|
||||||
|
// correspond bien à date_premiere_echeance est une échéance ORPHELINE dans ce cas (le
|
||||||
|
// prêt est soldé), pas un état cohérent. Sans ce garde-fou, cette route n'aurait jamais
|
||||||
|
// détecté l'échéance laissée par le bug de "Corriger les dates des prêts différés"
|
||||||
|
// (ci-dessus) sur un prêt déjà soldé, puisque cette échéance orpheline correspondait
|
||||||
|
// justement (par construction du bug) à date_premiere_echeance.
|
||||||
|
const estClos = inv.statut === 'rembourse' || inv.statut === 'cloture';
|
||||||
|
const incoherent = estClos
|
||||||
|
? simulRows.length > 0
|
||||||
|
: (simulRows.length !== 1 || simulRows[0].date_prevue !== inv.date_premiere_echeance);
|
||||||
|
|
||||||
if (!incoherent) continue;
|
if (!incoherent) continue;
|
||||||
|
|
||||||
@@ -409,11 +421,19 @@ router.post('/check-echeancier-differe', (req, res, next) => {
|
|||||||
? 'aucune échéance'
|
? 'aucune échéance'
|
||||||
: simulRows.map(s => `${s.date_prevue} (n°${s.numero_echeance})`).join(', ');
|
: simulRows.map(s => `${s.date_prevue} (n°${s.numero_echeance})`).join(', ');
|
||||||
|
|
||||||
if (hasReinvest.get(inv.id)) {
|
// regenererEcheancier() régénère PUIS réconcilie contre les remboursements réels déjà
|
||||||
generateSimulWithReinvestissements(db, inv.id);
|
// enregistrés (replayAdjustSimulForActuals(), cf. project_revision_correction.md) — pour
|
||||||
} else {
|
// un prêt soldé, cela supprime l'échéance orpheline ; pour un prêt actif dont
|
||||||
generateSimul(db, inv);
|
// l'échéancier était simplement désynchronisé, cela le reconstruit SANS écraser un
|
||||||
}
|
// éventuel remboursement anticipé partiel déjà pris en compte, contrairement à l'ancien
|
||||||
|
// appel direct à generateSimul()/generateSimulWithReinvestissements() ci-dessous, qui ne
|
||||||
|
// réconciliait jamais rien.
|
||||||
|
regenererEcheancier(inv.id);
|
||||||
|
|
||||||
|
const nouvelEcheancier = getSimul.all(inv.id);
|
||||||
|
const nouvelleValeur = nouvelEcheancier.length === 0
|
||||||
|
? 'aucune échéance (prêt soldé)'
|
||||||
|
: nouvelEcheancier.map(s => `${s.date_prevue} (n°${s.numero_echeance})`).join(', ');
|
||||||
|
|
||||||
recordHistory(inv.id, {
|
recordHistory(inv.id, {
|
||||||
type_evenement: 'correction_auto_echeancier',
|
type_evenement: 'correction_auto_echeancier',
|
||||||
@@ -421,7 +441,7 @@ router.post('/check-echeancier-differe', (req, res, next) => {
|
|||||||
champ: 'echeancier',
|
champ: 'echeancier',
|
||||||
label: 'Échéancier de projection',
|
label: 'Échéancier de projection',
|
||||||
ancienne_valeur: ancienEcheancier,
|
ancienne_valeur: ancienEcheancier,
|
||||||
nouvelle_valeur: inv.date_premiere_echeance,
|
nouvelle_valeur: nouvelleValeur,
|
||||||
}],
|
}],
|
||||||
notes: "Correction automatique (Nettoyage > Vérifier la cohérence de l'échéancier des prêts différés)",
|
notes: "Correction automatique (Nettoyage > Vérifier la cohérence de l'échéancier des prêts différés)",
|
||||||
});
|
});
|
||||||
@@ -430,7 +450,7 @@ router.post('/check-echeancier-differe', (req, res, next) => {
|
|||||||
id: inv.id,
|
id: inv.id,
|
||||||
nom_projet: inv.nom_projet,
|
nom_projet: inv.nom_projet,
|
||||||
ancien_echeancier: ancienEcheancier,
|
ancien_echeancier: ancienEcheancier,
|
||||||
nouvelle_date: inv.date_premiere_echeance,
|
nouvelle_date: nouvelleValeur,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user