import { getDb } from "./db"; import { sequences, inscriptions, apprenants, formations, datesFormation, attestations, historiqueAttestations } from "../drizzle/schema"; import { eq, and, lte, sql, isNull } from "drizzle-orm"; import { sendAttestationEmail } from "./emailService"; import { enregistrerEnvoiAttestation } from "./attestationsDb"; /** * Vérifie si une attestation a déjà été envoyée pour une inscription */ async function attestationDejaEnvoyee(apprenantId: number, sequenceId: number): Promise { const db = await getDb(); if (!db) return false; try { const [existing] = await db .select({ id: historiqueAttestations.id }) .from(historiqueAttestations) .where( and( eq(historiqueAttestations.apprenantId, apprenantId), eq(historiqueAttestations.sequenceId, sequenceId), eq(historiqueAttestations.statut, "envoye") ) ) .limit(1); return !!existing; } catch (error) { console.error("[Attestations Auto] Erreur lors de la vérification de l'historique:", 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`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, }); // Enregistrer dans l'historique await enregistrerEnvoiAttestation({ sequenceId: sequence.id, apprenantId: apprenant.id, statut: "envoye", urlAttestation: attestation.documentUrl, modeEnvoi: "auto", }); envoyesCount++; console.log(`[Attestations Auto] Email envoyé à ${apprenant.email}`); } catch (error: any) { // Enregistrer l'échec dans l'historique await enregistrerEnvoiAttestation({ sequenceId: sequence.id, apprenantId: apprenant.id, statut: "erreur", messageErreur: error.message, urlAttestation: attestation.documentUrl, modeEnvoi: "auto", }); 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')}`); }