Corrections sur les jobs En retard
This commit is contained in:
+134
-20
@@ -14,29 +14,40 @@ function writeLog({ status, nbChanges, details, errorMsg }) {
|
||||
}
|
||||
}
|
||||
|
||||
/** Insère une notification utilisateur (best-effort, ne bloque jamais le job). */
|
||||
function notifyUser(userId, { type, title, body, link }) {
|
||||
try {
|
||||
db.prepare(`
|
||||
INSERT INTO notifications (user_id, type, title, body, link)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
`).run(userId, type, title, body ?? null, link ?? null);
|
||||
} catch (e) {
|
||||
console.error('[autoStatut] Impossible de créer la notification :', e.message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Passe automatiquement au statut "en_retard" les investissements dont :
|
||||
* - le statut est actuellement "en_cours"
|
||||
* - la date_cible est renseignée et strictement antérieure à aujourd'hui
|
||||
*
|
||||
* Chaque passage est tracé dans investissement_historique avec le
|
||||
* type_evenement 'passage_auto_retard' pour conserver l'auditabilité.
|
||||
* type_evenement 'passage_auto_retard' pour conserver l'auditabilité, et
|
||||
* génère une notification utilisateur.
|
||||
*
|
||||
* @returns {number} nombre d'investissements mis à jour
|
||||
*/
|
||||
export function checkStatutsRetard() {
|
||||
function checkPassagesEnRetard() {
|
||||
const candidats = db.prepare(`
|
||||
SELECT id, nom_projet, date_cible
|
||||
FROM investissements
|
||||
WHERE statut = 'en_cours'
|
||||
AND date_cible IS NOT NULL
|
||||
AND date_cible < date('now')
|
||||
SELECT i.id, i.nom_projet, i.date_cible, inv.user_id
|
||||
FROM investissements i
|
||||
JOIN investisseurs inv ON inv.id = i.investisseur_id
|
||||
WHERE i.statut = 'en_cours'
|
||||
AND i.date_cible IS NOT NULL
|
||||
AND i.date_cible < date('now')
|
||||
`).all();
|
||||
|
||||
if (candidats.length === 0) {
|
||||
writeLog({ status: 'ok', nbChanges: 0, details: 'Aucun investissement en retard détecté' });
|
||||
return 0;
|
||||
}
|
||||
if (candidats.length === 0) return { nb: 0, details: [] };
|
||||
|
||||
const updateStmt = db.prepare(`
|
||||
UPDATE investissements
|
||||
@@ -63,22 +74,125 @@ export function checkStatutsRetard() {
|
||||
}]),
|
||||
`Passage automatique : date cible (${inv.date_cible}) dépassée`
|
||||
);
|
||||
notifyUser(inv.user_id, {
|
||||
type: 'warning',
|
||||
title: `Prêt en retard : "${inv.nom_projet}"`,
|
||||
body: `La date cible (${inv.date_cible}) est dépassée sans remboursement enregistré. Le prêt est passé automatiquement au statut "En retard".`,
|
||||
link: `/investissements/${inv.id}`,
|
||||
});
|
||||
}
|
||||
});
|
||||
tx();
|
||||
|
||||
const details = candidats
|
||||
.map(i => `"${i.nom_projet}" (id=${i.id}, date_cible=${i.date_cible})`)
|
||||
.join('; ');
|
||||
return {
|
||||
nb: candidats.length,
|
||||
details: candidats.map(i => `"${i.nom_projet}" (id=${i.id}, date_cible=${i.date_cible})`),
|
||||
};
|
||||
}
|
||||
|
||||
writeLog({
|
||||
status: 'ok',
|
||||
nbChanges: candidats.length,
|
||||
details: `Passé en retard : ${details}`,
|
||||
/**
|
||||
* Repasse automatiquement au statut "en_cours" les investissements dont :
|
||||
* - le statut est actuellement "en_retard"
|
||||
* - la date_cible est désormais renseignée à une échéance future plausible
|
||||
* (entre aujourd'hui et +30 ans — cette borne haute évite qu'une date encore
|
||||
* aberrante, ex. année 2400 non corrigée, ne soit prise à tort pour une date
|
||||
* valide simplement parce qu'elle est "dans le futur")
|
||||
* - le dernier événement ayant modifié le statut est bien le passage automatique
|
||||
* en retard ('passage_auto_retard') — un passage en retard décidé manuellement
|
||||
* par l'utilisateur n'est jamais annulé automatiquement.
|
||||
*
|
||||
* Typiquement déclenché après une correction de date_cible (ex : bouton
|
||||
* "Corriger les dates des prêts différés" dans Nettoyage) qui repousse
|
||||
* l'échéance dans le futur.
|
||||
*
|
||||
* @returns {number} nombre d'investissements mis à jour
|
||||
*/
|
||||
function checkRetoursEnCours() {
|
||||
const candidats = db.prepare(`
|
||||
SELECT i.id, i.nom_projet, i.date_cible, inv.user_id
|
||||
FROM investissements i
|
||||
JOIN investisseurs inv ON inv.id = i.investisseur_id
|
||||
WHERE i.statut = 'en_retard'
|
||||
AND i.date_cible IS NOT NULL
|
||||
AND i.date_cible >= date('now')
|
||||
AND i.date_cible <= date('now', '+30 years')
|
||||
AND (
|
||||
SELECT h.type_evenement FROM investissement_historique h
|
||||
WHERE h.investissement_id = i.id
|
||||
AND h.changements LIKE '%"champ":"statut"%'
|
||||
ORDER BY h.created_at DESC, h.id DESC
|
||||
LIMIT 1
|
||||
) = 'passage_auto_retard'
|
||||
`).all();
|
||||
|
||||
if (candidats.length === 0) return { nb: 0, details: [] };
|
||||
|
||||
const updateStmt = db.prepare(`
|
||||
UPDATE investissements
|
||||
SET statut = 'en_cours', updated_at = datetime('now')
|
||||
WHERE id = ?
|
||||
`);
|
||||
|
||||
const histStmt = db.prepare(`
|
||||
INSERT INTO investissement_historique
|
||||
(investissement_id, type_evenement, changements, notes)
|
||||
VALUES (?, 'retour_auto_en_cours', ?, ?)
|
||||
`);
|
||||
|
||||
const tx = db.transaction(() => {
|
||||
for (const inv of candidats) {
|
||||
updateStmt.run(inv.id);
|
||||
histStmt.run(
|
||||
inv.id,
|
||||
JSON.stringify([{
|
||||
champ: 'statut',
|
||||
label: 'Statut',
|
||||
ancienne_valeur: 'en_retard',
|
||||
nouvelle_valeur: 'en_cours',
|
||||
}]),
|
||||
`Retour automatique : date cible (${inv.date_cible}) désormais future`
|
||||
);
|
||||
notifyUser(inv.user_id, {
|
||||
type: 'success',
|
||||
title: `Prêt de nouveau en cours : "${inv.nom_projet}"`,
|
||||
body: `La date cible a été corrigée (${inv.date_cible}, désormais future). Le prêt repasse automatiquement au statut "En cours".`,
|
||||
link: `/investissements/${inv.id}`,
|
||||
});
|
||||
}
|
||||
});
|
||||
tx();
|
||||
|
||||
console.log(`[autoStatut] ${candidats.length} investissement(s) passé(s) en retard : ${details}`);
|
||||
return candidats.length;
|
||||
return {
|
||||
nb: candidats.length,
|
||||
details: candidats.map(i => `"${i.nom_projet}" (id=${i.id}, date_cible=${i.date_cible})`),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie et applique les transitions automatiques de statut liées à la date_cible :
|
||||
* en_cours → en_retard (échéance dépassée) et en_retard → en_cours (échéance corrigée
|
||||
* dans le futur, uniquement si le passage en retard était lui-même automatique).
|
||||
*
|
||||
* @returns {number} nombre total d'investissements mis à jour
|
||||
*/
|
||||
export function checkStatutsRetard() {
|
||||
const retards = checkPassagesEnRetard();
|
||||
const retours = checkRetoursEnCours();
|
||||
|
||||
const nbChanges = retards.nb + retours.nb;
|
||||
if (nbChanges === 0) {
|
||||
writeLog({ status: 'ok', nbChanges: 0, details: 'Aucun changement de statut détecté' });
|
||||
return 0;
|
||||
}
|
||||
|
||||
const parts = [];
|
||||
if (retards.nb > 0) parts.push(`Passé en retard : ${retards.details.join('; ')}`);
|
||||
if (retours.nb > 0) parts.push(`Repassé en cours : ${retours.details.join('; ')}`);
|
||||
const details = parts.join(' | ');
|
||||
|
||||
writeLog({ status: 'ok', nbChanges, details });
|
||||
console.log(`[autoStatut] ${nbChanges} investissement(s) mis à jour : ${details}`);
|
||||
return nbChanges;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user