Fix
This commit is contained in:
@@ -54,7 +54,7 @@ export function detectTypeEvenement(changements) {
|
|||||||
// table dédiée, déclenche la régénération automatique de l'échéancier futur.
|
// table dédiée, déclenche la régénération automatique de l'échéancier futur.
|
||||||
const RevisionSchema = z.object({
|
const RevisionSchema = z.object({
|
||||||
date_effet: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
|
date_effet: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
|
||||||
nouveau_taux: z.number().positive().optional(),
|
nouveau_taux: z.number().nonnegative().optional(),
|
||||||
nouvelle_date_cible: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional(),
|
nouvelle_date_cible: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional(),
|
||||||
motif: z.string().trim().min(1, 'Le motif est obligatoire'),
|
motif: z.string().trim().min(1, 'Le motif est obligatoire'),
|
||||||
}).refine(d => d.nouveau_taux !== undefined || d.nouvelle_date_cible !== undefined, {
|
}).refine(d => d.nouveau_taux !== undefined || d.nouvelle_date_cible !== undefined, {
|
||||||
|
|||||||
@@ -102,7 +102,9 @@ router.post('/generate', (req, res, next) => {
|
|||||||
const { investissement_id, replace = true } = req.body;
|
const { investissement_id, replace = true } = req.body;
|
||||||
if (!investissement_id) throw new HttpError(400, 'investissement_id required');
|
if (!investissement_id) throw new HttpError(400, 'investissement_id required');
|
||||||
const inv = assertOwnedInvestissement(Number(investissement_id), req.user.id);
|
const inv = assertOwnedInvestissement(Number(investissement_id), req.user.id);
|
||||||
if (!inv.taux_interet || !inv.duree_mois) {
|
// taux_interet peut légitimement valoir 0 (ex. révision suite à un arrêt de production) —
|
||||||
|
// ne pas confondre avec "non renseigné" (null/undefined).
|
||||||
|
if (inv.taux_interet == null || !inv.duree_mois) {
|
||||||
throw new HttpError(400, 'Investissement requires taux_interet and duree_mois');
|
throw new HttpError(400, 'Investissement requires taux_interet and duree_mois');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -129,7 +129,9 @@ export function adjustSimulForActuals(db, investissementId) {
|
|||||||
FROM investissements WHERE id = ?
|
FROM investissements WHERE id = ?
|
||||||
`).get(investissementId);
|
`).get(investissementId);
|
||||||
|
|
||||||
if (!inv || !inv.taux_interet || !inv.duree_mois) return;
|
// taux_interet peut légitimement valoir 0 (ex. révision suite à un arrêt de production
|
||||||
|
// n'engendrant plus d'intérêts) — ne pas confondre avec "non renseigné" (null/undefined).
|
||||||
|
if (!inv || inv.taux_interet == null || !inv.duree_mois) return;
|
||||||
|
|
||||||
// Réinvestissements triés par date (peuvent être vides)
|
// Réinvestissements triés par date (peuvent être vides)
|
||||||
const reinvests = db.prepare(
|
const reinvests = db.prepare(
|
||||||
@@ -379,7 +381,8 @@ export function generateSimulWithReinvestissements(db, investissementId) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!inv.taux_interet || !inv.duree_mois) return;
|
// taux_interet peut légitimement valoir 0 (cf. commentaire dans adjustSimulForActuals)
|
||||||
|
if (inv.taux_interet == null || !inv.duree_mois) return;
|
||||||
|
|
||||||
const startDate = inv.date_debut_simul || inv.date_premiere_echeance || inv.date_souscription;
|
const startDate = inv.date_debut_simul || inv.date_premiere_echeance || inv.date_souscription;
|
||||||
if (!startDate) return;
|
if (!startDate) return;
|
||||||
@@ -525,7 +528,8 @@ export function generateSimul(db, inv) {
|
|||||||
const { id, montant_investi, taux_interet, duree_mois, type_remb, freq_interets,
|
const { id, montant_investi, taux_interet, duree_mois, type_remb, freq_interets,
|
||||||
date_premiere_echeance, date_debut_simul, date_souscription, echeance_fin_de_mois } = inv;
|
date_premiere_echeance, date_debut_simul, date_souscription, echeance_fin_de_mois } = inv;
|
||||||
|
|
||||||
if (!taux_interet || !duree_mois) return;
|
// taux_interet peut légitimement valoir 0 (cf. commentaire dans adjustSimulForActuals)
|
||||||
|
if (taux_interet == null || !duree_mois) return;
|
||||||
|
|
||||||
// date_debut_simul remplace le point de départ quand le prêt a été restructuré
|
// date_debut_simul remplace le point de départ quand le prêt a été restructuré
|
||||||
const startDate = date_debut_simul || date_premiere_echeance || date_souscription;
|
const startDate = date_debut_simul || date_premiere_echeance || date_souscription;
|
||||||
|
|||||||
@@ -661,7 +661,13 @@ export default function InvestissementDetail() {
|
|||||||
});
|
});
|
||||||
setRevisionModalOpen(false);
|
setRevisionModalOpen(false);
|
||||||
await load();
|
await load();
|
||||||
} catch (e) { setRevisionErr(e.message); } finally { setRevisionSaving(false); }
|
} catch (e) {
|
||||||
|
// Surface le détail des erreurs de validation (Zod) plutôt que le message générique
|
||||||
|
// "Validation error" renvoyé par errorHandler.js.
|
||||||
|
const fieldErrors = e.details?.fieldErrors;
|
||||||
|
const detail = fieldErrors ? Object.values(fieldErrors).flat().filter(Boolean).join(' ') : '';
|
||||||
|
setRevisionErr(detail || e.message);
|
||||||
|
} finally { setRevisionSaving(false); }
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteRevision = async (rid) => {
|
const deleteRevision = async (rid) => {
|
||||||
@@ -2603,7 +2609,7 @@ export default function InvestissementDetail() {
|
|||||||
</svg>
|
</svg>
|
||||||
Traitement en masse des remboursements
|
Traitement en masse des remboursements
|
||||||
</button>
|
</button>
|
||||||
{inv.taux_interet && inv.duree_mois ? (
|
{inv.taux_interet != null && inv.duree_mois ? (
|
||||||
<button
|
<button
|
||||||
style={{ display: 'flex', alignItems: 'center', gap: 8, width: '100%', padding: '8px 14px', background: 'none', border: 'none', cursor: recalculating ? 'not-allowed' : 'pointer', fontSize: 'var(--fs-sm)', color: recalculating ? 'var(--text-muted)' : 'var(--text)', textAlign: 'left', opacity: recalculating ? 0.6 : 1 }}
|
style={{ display: 'flex', alignItems: 'center', gap: 8, width: '100%', padding: '8px 14px', background: 'none', border: 'none', cursor: recalculating ? 'not-allowed' : 'pointer', fontSize: 'var(--fs-sm)', color: recalculating ? 'var(--text-muted)' : 'var(--text)', textAlign: 'left', opacity: recalculating ? 0.6 : 1 }}
|
||||||
onMouseEnter={e => { if (!recalculating) e.currentTarget.style.background = 'var(--surface-2)'; }}
|
onMouseEnter={e => { if (!recalculating) e.currentTarget.style.background = 'var(--surface-2)'; }}
|
||||||
|
|||||||
@@ -1286,7 +1286,7 @@ export default function Remboursements() {
|
|||||||
{simulMsg}
|
{simulMsg}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{simulInv && (!simulInv.taux_interet || !simulInv.duree_mois) && (
|
{simulInv && (simulInv.taux_interet == null || !simulInv.duree_mois) && (
|
||||||
<div className="error" style={{ marginTop: 12 }}>
|
<div className="error" style={{ marginTop: 12 }}>
|
||||||
Cet investissement n'a pas de <strong>taux</strong> et/ou de <strong>durée</strong>. Renseignez-les dans la fiche.
|
Cet investissement n'a pas de <strong>taux</strong> et/ou de <strong>durée</strong>. Renseignez-les dans la fiche.
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ export default function SimulRemboursements() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{msg && <div className={msg.startsWith('✔') ? 'success-msg' : 'error'} style={{ marginTop: 12 }}>{msg}</div>}
|
{msg && <div className={msg.startsWith('✔') ? 'success-msg' : 'error'} style={{ marginTop: 12 }}>{msg}</div>}
|
||||||
{inv && (!inv.taux_interet || !inv.duree_mois) && (
|
{inv && (inv.taux_interet == null || !inv.duree_mois) && (
|
||||||
<div className="error" style={{ marginTop: 12 }}>
|
<div className="error" style={{ marginTop: 12 }}>
|
||||||
Cet investissement n'a pas de <strong>taux</strong> et/ou de <strong>durée</strong>. Renseignez-les dans la fiche.
|
Cet investissement n'a pas de <strong>taux</strong> et/ou de <strong>durée</strong>. Renseignez-les dans la fiche.
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user