Checkpoint: Implémentation de l'envoi automatique des attestations : création d'un scheduler qui vérifie quotidiennement (à 10h00) les séquences terminées la veille et envoie automatiquement les attestations par email pour les formations ayant le mode d'envoi automatique activé (modeEnvoi='auto'). Le système utilise le template email personnalisable créé précédemment et enregistre les envois dans les logs de notifications pour éviter les doublons. Déployé et testé sur le VPS.
This commit is contained in:
207
server/attestationScheduler.ts
Normal file
207
server/attestationScheduler.ts
Normal file
@@ -0,0 +1,207 @@
|
||||
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')}`);
|
||||
}
|
||||
Reference in New Issue
Block a user