import db from '../db/index.js'; const JOB_NAME = 'auto_statut_retard'; /** Persiste une entrée dans job_logs */ function writeLog({ status, nbChanges, details, errorMsg }) { try { db.prepare(` INSERT INTO job_logs (job_name, status, nb_changes, details, error_msg) VALUES (?, ?, ?, ?, ?) `).run(JOB_NAME, status, nbChanges ?? 0, details ?? null, errorMsg ?? null); } catch (e) { console.error('[autoStatut] Impossible d\'écrire dans job_logs :', 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é. * * @returns {number} nombre d'investissements mis à jour */ export function checkStatutsRetard() { 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') `).all(); if (candidats.length === 0) { writeLog({ status: 'ok', nbChanges: 0, details: 'Aucun investissement en retard détecté' }); return 0; } const updateStmt = db.prepare(` UPDATE investissements SET statut = 'en_retard', updated_at = datetime('now') WHERE id = ? `); const histStmt = db.prepare(` INSERT INTO investissement_historique (investissement_id, type_evenement, changements, notes) VALUES (?, 'passage_auto_retard', ?, ?) `); 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_cours', nouvelle_valeur: 'en_retard', }]), `Passage automatique : date cible (${inv.date_cible}) dépassée` ); } }); tx(); const details = candidats .map(i => `"${i.nom_projet}" (id=${i.id}, date_cible=${i.date_cible})`) .join('; '); writeLog({ status: 'ok', nbChanges: candidats.length, details: `Passé en retard : ${details}`, }); console.log(`[autoStatut] ${candidats.length} investissement(s) passé(s) en retard : ${details}`); return candidats.length; } /** * Démarre le job de vérification automatique des statuts. * * - Exécution immédiate au démarrage (rattrape les retards accumulés * pendant que le serveur était éteint). * - Puis répétition quotidienne, calée sur la prochaine minuit locale * afin de ne pas dériver au fil des redémarrages. */ export function startAutoStatutJob() { // Exécution initiale try { checkStatutsRetard(); } catch (err) { console.error('[autoStatut] Erreur lors de la vérification initiale :', err); writeLog({ status: 'error', nbChanges: 0, errorMsg: err.message }); } // Calcule le délai jusqu'à la prochaine minuit locale function msUntilMidnight() { const now = new Date(); const next = new Date(now); next.setHours(24, 0, 0, 0); return next.getTime() - now.getTime(); } // Planifie la première échéance à minuit, puis toutes les 24h (sans dérive) function scheduleDailyRun() { setTimeout(() => { try { checkStatutsRetard(); } catch (err) { console.error('[autoStatut] Erreur lors de la vérification quotidienne :', err); writeLog({ status: 'error', nbChanges: 0, errorMsg: err.message }); } scheduleDailyRun(); }, msUntilMidnight()); } scheduleDailyRun(); console.log(`[autoStatut] Job démarré — prochaine vérification dans ${Math.round(msUntilMidnight() / 60000)} min (minuit)`); }