Files
formation-manager-itinova/server/attestationScheduler.ts

208 lines
7.1 KiB
TypeScript

import { getDb } from "./db";
import { sequences, inscriptions, apprenants, formations, datesFormation, attestations, logsNotifications } from "../drizzle/schema";
import { eq, and, lte, sql, isNull } from "drizzle-orm";
import { sendAttestationEmail } from "./emailService";
import { logNotification } from "./notificationLogsDb";
/**
* Vérifie si une attestation a déjà été envoyée pour une inscription
*/
async function attestationDejaEnvoyee(apprenantId: number, sequenceId: number): Promise<boolean> {
const db = await getDb();
if (!db) return false;
try {
const [existing] = await db
.select({ id: logsNotifications.id })
.from(logsNotifications)
.where(
and(
eq(logsNotifications.type, "attestation"),
eq(logsNotifications.apprenantId, apprenantId),
eq(logsNotifications.sequenceId, sequenceId),
eq(logsNotifications.statut, "success")
)
)
.limit(1);
return !!existing;
} catch (error) {
console.error("[Attestations Auto] Erreur lors de la vérification du log:", error);
return false;
}
}
/**
* Job automatique qui vérifie quotidiennement les séquences terminées
* et envoie les attestations aux apprenants pour les formations en mode envoi automatique
*/
export async function processAttestationsAutomatiques() {
console.log("[Attestations Auto] Démarrage du traitement des attestations automatiques...");
const db = await getDb();
if (!db) {
console.error("[Attestations Auto] Base de données non disponible");
return;
}
try {
const now = new Date();
const hier = new Date();
hier.setDate(hier.getDate() - 1);
hier.setHours(23, 59, 59, 999);
// Récupérer les séquences dont la dernière date est passée (terminées hier)
// ET dont la formation a le mode d'envoi automatique activé
const sequencesTerminees = await db
.select({
sequence: sequences,
formation: formations,
derniereDate: sql<Date>`MAX(${datesFormation.dateFin})`.as('derniereDate'),
})
.from(sequences)
.innerJoin(formations, eq(sequences.formationId, formations.id))
.innerJoin(datesFormation, eq(sequences.id, datesFormation.sequenceId))
.where(
and(
eq(formations.modeEnvoi, 'auto'), // Seulement les formations en mode envoi automatique
eq(sequences.statut, 'ouverte') // Séquences encore ouvertes
)
)
.groupBy(sequences.id, formations.id)
.having(lte(sql`MAX(${datesFormation.dateFin})`, hier));
console.log(`[Attestations Auto] ${sequencesTerminees.length} séquence(s) terminée(s) avec envoi automatique trouvée(s)`);
for (const { sequence, formation, derniereDate } of sequencesTerminees) {
await processAttestationSequence(sequence, formation, derniereDate);
}
console.log("[Attestations Auto] Traitement terminé");
} catch (error) {
console.error("[Attestations Auto] Erreur lors du traitement des attestations:", error);
}
}
async function processAttestationSequence(
sequence: any,
formation: any,
derniereDate: Date
) {
console.log(`[Attestations Auto] Traitement de la séquence: ${sequence.nom} (terminée le ${derniereDate})`);
const db = await getDb();
if (!db) return;
try {
// Récupérer les inscriptions confirmées avec leurs attestations
const inscriptionsAvecAttestations = await db
.select({
inscription: inscriptions,
apprenant: apprenants,
attestation: attestations,
})
.from(inscriptions)
.innerJoin(apprenants, eq(inscriptions.apprenantId, apprenants.id))
.leftJoin(attestations, eq(inscriptions.id, attestations.inscriptionId))
.where(
and(
eq(inscriptions.sequenceId, sequence.id),
eq(inscriptions.statut, 'confirmee')
)
);
console.log(`[Attestations Auto] ${inscriptionsAvecAttestations.length} inscription(s) confirmée(s) pour ${sequence.nom}`);
let envoyesCount = 0;
let errorsCount = 0;
let sansAttestationCount = 0;
for (const { inscription, apprenant, attestation } of inscriptionsAvecAttestations) {
// Vérifier si l'attestation existe
if (!attestation || !attestation.documentUrl) {
sansAttestationCount++;
console.log(`[Attestations Auto] Pas d'attestation générée pour ${apprenant.email}`);
continue;
}
// Vérifier si l'attestation a déjà été envoyée
const dejaEnvoyee = await attestationDejaEnvoyee(apprenant.id, sequence.id);
if (dejaEnvoyee) {
console.log(`[Attestations Auto] Attestation déjà envoyée pour ${apprenant.email}`);
continue;
}
try {
await sendAttestationEmail({
to: apprenant.email,
apprenantNom: apprenant.nom,
apprenantPrenom: apprenant.prenom,
formationNom: formation.nom,
pdfUrl: attestation.documentUrl,
});
// Logger la notification
await logNotification({
type: "attestation",
sequenceId: sequence.id,
apprenantId: apprenant.id,
emailDestinataire: apprenant.email,
sujet: `Votre attestation de formation - ${formation.nom}`,
statut: "success",
metadata: { attestationId: attestation.id },
});
envoyesCount++;
console.log(`[Attestations Auto] Email envoyé à ${apprenant.email}`);
} catch (error: any) {
// Logger l'échec
await logNotification({
type: "attestation",
sequenceId: sequence.id,
apprenantId: apprenant.id,
emailDestinataire: apprenant.email,
sujet: `Votre attestation de formation - ${formation.nom}`,
statut: "failed",
messageErreur: error.message,
});
errorsCount++;
console.error(`[Attestations Auto] Erreur envoi à ${apprenant.email}:`, error);
}
}
console.log(`[Attestations Auto] Séquence ${sequence.nom}: ${envoyesCount} envoyé(s), ${errorsCount} erreur(s), ${sansAttestationCount} sans attestation`);
} catch (error) {
console.error(`[Attestations Auto] Erreur pour la séquence ${sequence.nom}:`, error);
}
}
/**
* Initialise le scheduler pour l'envoi automatique des attestations
* Exécute le job tous les jours à 10h00
*/
export function initAttestationScheduler() {
console.log("[Attestations Auto] Initialisation du scheduler d'envoi automatique");
// Exécuter immédiatement au démarrage
processAttestationsAutomatiques();
// Puis exécuter tous les jours à 10h00
const HEURE_EXECUTION = 10; // 10h00
const MINUTE_EXECUTION = 0;
setInterval(() => {
const now = new Date();
const heureActuelle = now.getHours();
const minuteActuelle = now.getMinutes();
// Exécuter seulement à l'heure prévue (avec une tolérance de 1 minute)
if (heureActuelle === HEURE_EXECUTION && minuteActuelle === MINUTE_EXECUTION) {
processAttestationsAutomatiques();
}
}, 60 * 1000); // Vérifier toutes les minutes
console.log(`[Attestations Auto] Scheduler initialisé - Exécution quotidienne à ${HEURE_EXECUTION}:${String(MINUTE_EXECUTION).padStart(2, '0')}`);
}