227 lines
7.5 KiB
TypeScript
227 lines
7.5 KiB
TypeScript
import { getDb } from "./db";
|
|
import { logsRappels, sequences, inscriptions, apprenants, formations, datesFormation, rappels, formateurs } from "../drizzle/schema";
|
|
import { eq, and } from "drizzle-orm";
|
|
import { sendRappelJ7Email, sendRappelJ1Email } from "./emailService";
|
|
import { getLogsAReessayer } from "./rappelDb";
|
|
import { notifyOwner } from "./_core/notification";
|
|
|
|
/**
|
|
* Calcule le délai avant le prochain essai selon le nombre de tentatives
|
|
* Délai exponentiel : 1h, 4h, 24h
|
|
*/
|
|
function calculerProchainEssai(nbTentatives: number): Date {
|
|
const maintenant = new Date();
|
|
let delaiHeures = 1; // 1 heure par défaut
|
|
|
|
if (nbTentatives === 1) {
|
|
delaiHeures = 1; // 1ère tentative échouée → réessayer dans 1h
|
|
} else if (nbTentatives === 2) {
|
|
delaiHeures = 4; // 2ème tentative échouée → réessayer dans 4h
|
|
} else {
|
|
delaiHeures = 24; // 3ème tentative échouée → réessayer dans 24h (mais on arrête à 3)
|
|
}
|
|
|
|
maintenant.setHours(maintenant.getHours() + delaiHeures);
|
|
return maintenant;
|
|
}
|
|
|
|
/**
|
|
* Réessaye d'envoyer les rappels échoués
|
|
*/
|
|
export async function processRappelRetry() {
|
|
console.log("[Rappels Retry] Démarrage du processus de réessai");
|
|
|
|
const db = await getDb();
|
|
if (!db) {
|
|
console.error("[Rappels Retry] Base de données non disponible");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// Récupérer les logs à réessayer
|
|
const logsAReessayer = await getLogsAReessayer();
|
|
|
|
if (logsAReessayer.length === 0) {
|
|
console.log("[Rappels Retry] Aucun rappel à réessayer");
|
|
return;
|
|
}
|
|
|
|
console.log(`[Rappels Retry] ${logsAReessayer.length} rappel(s) à réessayer`);
|
|
|
|
let nbSucces = 0;
|
|
let nbEchecs = 0;
|
|
|
|
for (const log of logsAReessayer) {
|
|
try {
|
|
// Récupérer les informations de la séquence
|
|
const sequence = await db
|
|
.select()
|
|
.from(sequences)
|
|
.where(eq(sequences.id, log.sequenceId))
|
|
.limit(1);
|
|
|
|
if (sequence.length === 0) {
|
|
console.warn(`[Rappels Retry] Séquence ${log.sequenceId} introuvable`);
|
|
continue;
|
|
}
|
|
|
|
const seq = sequence[0];
|
|
|
|
// Récupérer la formation
|
|
const formation = await db
|
|
.select()
|
|
.from(formations)
|
|
.where(eq(formations.id, seq.formationId))
|
|
.limit(1);
|
|
|
|
if (formation.length === 0) {
|
|
console.warn(`[Rappels Retry] Formation ${seq.formationId} introuvable`);
|
|
continue;
|
|
}
|
|
|
|
// Récupérer l'apprenant
|
|
const apprenant = await db
|
|
.select()
|
|
.from(apprenants)
|
|
.where(eq(apprenants.id, log.apprenantId))
|
|
.limit(1);
|
|
|
|
if (apprenant.length === 0 || !apprenant[0].email) {
|
|
console.warn(`[Rappels Retry] Apprenant ${log.apprenantId} introuvable ou sans email`);
|
|
continue;
|
|
}
|
|
|
|
// Récupérer les dates de la séquence
|
|
const dates = await db
|
|
.select()
|
|
.from(datesFormation)
|
|
.where(eq(datesFormation.sequenceId, seq.id))
|
|
.orderBy(datesFormation.ordre);
|
|
|
|
// Récupérer le formateur si disponible
|
|
let formateurNom = "";
|
|
if (seq.formateurId) {
|
|
const formateur = await db
|
|
.select()
|
|
.from(formateurs)
|
|
.where(eq(formateurs.id, seq.formateurId))
|
|
.limit(1);
|
|
if (formateur.length > 0) {
|
|
formateurNom = formateur[0].nom;
|
|
}
|
|
}
|
|
|
|
// Réessayer l'envoi
|
|
try {
|
|
if (log.typeRappel === "rappel") {
|
|
await sendRappelJ7Email({
|
|
apprenantEmail: apprenant[0].email,
|
|
apprenantPrenom: apprenant[0].prenom,
|
|
apprenantNom: apprenant[0].nom,
|
|
apprenantFonction: apprenant[0].fonction,
|
|
formationNom: formation[0].nom,
|
|
sequenceNom: seq.nom,
|
|
dates: dates.map(d => ({
|
|
dateDebut: d.dateDebut,
|
|
dateFin: d.dateFin,
|
|
ordre: d.ordre
|
|
})),
|
|
lieu: seq.lieu || "",
|
|
});
|
|
} else if (log.typeRappel === "rappelJ1") {
|
|
await sendRappelJ1Email({
|
|
apprenantEmail: apprenant[0].email,
|
|
apprenantPrenom: apprenant[0].prenom,
|
|
apprenantNom: apprenant[0].nom,
|
|
apprenantFonction: apprenant[0].fonction,
|
|
formationNom: formation[0].nom,
|
|
sequenceNom: seq.nom,
|
|
dates: dates.map(d => ({
|
|
dateDebut: d.dateDebut,
|
|
dateFin: d.dateFin,
|
|
ordre: d.ordre
|
|
})),
|
|
lieu: seq.lieu || "",
|
|
});
|
|
}
|
|
|
|
// Succès : mettre à jour le log
|
|
await db
|
|
.update(logsRappels)
|
|
.set({
|
|
statut: "success",
|
|
nbTentatives: log.nbTentatives + 1,
|
|
prochainEssai: null,
|
|
})
|
|
.where(eq(logsRappels.id, log.id));
|
|
|
|
console.log(`[Rappels Retry] Succès pour ${apprenant[0].email} après ${log.nbTentatives + 1} tentative(s)`);
|
|
nbSucces++;
|
|
} catch (emailError: any) {
|
|
const messageErreur = emailError?.message || String(emailError);
|
|
const nouvelleTentative = log.nbTentatives + 1;
|
|
|
|
if (nouvelleTentative >= 3) {
|
|
// Abandon après 3 tentatives
|
|
await db
|
|
.update(logsRappels)
|
|
.set({
|
|
statut: "failed",
|
|
nbTentatives: nouvelleTentative,
|
|
messageErreur: `Abandon après 3 tentatives: ${messageErreur}`,
|
|
prochainEssai: null,
|
|
})
|
|
.where(eq(logsRappels.id, log.id));
|
|
|
|
console.error(`[Rappels Retry] Abandon pour ${apprenant[0].email} après 3 tentatives`);
|
|
|
|
// Notifier l'admin
|
|
await notifyOwner({
|
|
title: `❌ Abandon d'envoi de rappel`,
|
|
content: `Le rappel pour ${apprenant[0].email} (${seq.nom}) a échoué 3 fois et a été abandonné.\\n\\nDernière erreur: ${messageErreur}`,
|
|
});
|
|
} else {
|
|
// Planifier un nouvel essai
|
|
const prochainEssai = calculerProchainEssai(nouvelleTentative);
|
|
await db
|
|
.update(logsRappels)
|
|
.set({
|
|
nbTentatives: nouvelleTentative,
|
|
messageErreur: messageErreur,
|
|
prochainEssai: prochainEssai,
|
|
})
|
|
.where(eq(logsRappels.id, log.id));
|
|
|
|
console.log(`[Rappels Retry] Échec pour ${apprenant[0].email}, tentative ${nouvelleTentative}/3. Prochain essai: ${prochainEssai.toLocaleString()}`);
|
|
}
|
|
nbEchecs++;
|
|
}
|
|
} catch (error) {
|
|
console.error(`[Rappels Retry] Erreur lors du traitement du log ${log.id}:`, error);
|
|
}
|
|
}
|
|
|
|
console.log(`[Rappels Retry] Traitement terminé: ${nbSucces} succès, ${nbEchecs} échecs`);
|
|
} catch (error) {
|
|
console.error("[Rappels Retry] Erreur lors du processus de retry:", error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Initialise le scheduler de retry
|
|
* Vérifie toutes les heures s'il y a des rappels à réessayer
|
|
*/
|
|
export function initRappelRetryScheduler() {
|
|
console.log("[Rappels Retry] Initialisation du scheduler de réessai automatique");
|
|
|
|
// Exécuter immédiatement au démarrage
|
|
processRappelRetry();
|
|
|
|
// Puis exécuter toutes les heures
|
|
setInterval(() => {
|
|
processRappelRetry();
|
|
}, 60 * 60 * 1000); // 1 heure en millisecondes
|
|
|
|
console.log("[Rappels Retry] Scheduler de réessai initialisé - vérification toutes les heures");
|
|
}
|