Checkpoint: Ajout du système de notifications enrichi comprenant : email de remerciement post-formation automatique, notifications aux formateurs (nouvelle inscription et annulation), alertes de capacité atteinte pour les admins, notifications aux apprenants en liste d'attente quand une place se libère, ajout du champ email pour les formateurs, et scheduler automatique pour les remerciements.
This commit is contained in:
209
server/remerciementScheduler.ts
Normal file
209
server/remerciementScheduler.ts
Normal file
@@ -0,0 +1,209 @@
|
||||
import { getDb } from "./db";
|
||||
import { sequences, inscriptions, apprenants, formations, datesFormation, formateurs } from "../drizzle/schema";
|
||||
import { eq, and, lte, sql } from "drizzle-orm";
|
||||
import { sendRemerciementPostFormation } from "./emailService";
|
||||
|
||||
/**
|
||||
* Table pour suivre les remerciements déjà envoyés
|
||||
*/
|
||||
const remerciementsEnvoyes = new Map<string, Date>();
|
||||
|
||||
/**
|
||||
* Vérifie si un remerciement a déjà été envoyé pour une inscription
|
||||
*/
|
||||
function remerciementDejaEnvoye(inscriptionId: number, sequenceId: number): boolean {
|
||||
const key = `${inscriptionId}-${sequenceId}`;
|
||||
return remerciementsEnvoyes.has(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Marque un remerciement comme envoyé
|
||||
*/
|
||||
function marquerRemerciementEnvoye(inscriptionId: number, sequenceId: number): void {
|
||||
const key = `${inscriptionId}-${sequenceId}`;
|
||||
remerciementsEnvoyes.set(key, new Date());
|
||||
}
|
||||
|
||||
/**
|
||||
* Job automatique qui vérifie quotidiennement les séquences terminées
|
||||
* et envoie les emails de remerciement aux apprenants
|
||||
*/
|
||||
export async function processRemerciementsAutomatiques() {
|
||||
console.log("[Remerciements] Démarrage du traitement des remerciements post-formation...");
|
||||
|
||||
const db = await getDb();
|
||||
if (!db) {
|
||||
console.error("[Remerciements] 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 ou avant)
|
||||
const sequencesTerminees = await db
|
||||
.select({
|
||||
sequence: sequences,
|
||||
formation: formations,
|
||||
formateur: formateurs,
|
||||
derniereDate: sql<Date>`MAX(${datesFormation.dateFin})`.as('derniereDate'),
|
||||
})
|
||||
.from(sequences)
|
||||
.innerJoin(formations, eq(sequences.formationId, formations.id))
|
||||
.leftJoin(formateurs, eq(sequences.formateurId, formateurs.id))
|
||||
.innerJoin(datesFormation, eq(sequences.id, datesFormation.sequenceId))
|
||||
.where(eq(sequences.statut, 'ouverte')) // Séquences encore ouvertes (pas encore marquées terminées)
|
||||
.groupBy(sequences.id, formations.id, formateurs.id)
|
||||
.having(lte(sql`MAX(${datesFormation.dateFin})`, hier));
|
||||
|
||||
console.log(`[Remerciements] ${sequencesTerminees.length} séquence(s) terminée(s) trouvée(s)`);
|
||||
|
||||
for (const { sequence, formation, formateur, derniereDate } of sequencesTerminees) {
|
||||
await processRemerciementSequence(sequence, formation, formateur, derniereDate);
|
||||
}
|
||||
|
||||
console.log("[Remerciements] Traitement terminé");
|
||||
} catch (error) {
|
||||
console.error("[Remerciements] Erreur lors du traitement des remerciements:", error);
|
||||
}
|
||||
}
|
||||
|
||||
async function processRemerciementSequence(
|
||||
sequence: any,
|
||||
formation: any,
|
||||
formateur: any | null,
|
||||
derniereDate: Date
|
||||
) {
|
||||
console.log(`[Remerciements] 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 pour cette séquence
|
||||
const inscriptionsConfirmees = await db
|
||||
.select({
|
||||
inscription: inscriptions,
|
||||
apprenant: apprenants,
|
||||
})
|
||||
.from(inscriptions)
|
||||
.innerJoin(apprenants, eq(inscriptions.apprenantId, apprenants.id))
|
||||
.where(
|
||||
and(
|
||||
eq(inscriptions.sequenceId, sequence.id),
|
||||
eq(inscriptions.statut, 'confirmee')
|
||||
)
|
||||
);
|
||||
|
||||
console.log(`[Remerciements] ${inscriptionsConfirmees.length} inscription(s) confirmée(s) pour ${sequence.nom}`);
|
||||
|
||||
let envoyesCount = 0;
|
||||
let errorsCount = 0;
|
||||
|
||||
for (const { inscription, apprenant } of inscriptionsConfirmees) {
|
||||
// Vérifier si le remerciement a déjà été envoyé
|
||||
if (remerciementDejaEnvoye(inscription.id, sequence.id)) {
|
||||
console.log(`[Remerciements] Remerciement déjà envoyé pour ${apprenant.email}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
await sendRemerciementPostFormation({
|
||||
apprenantEmail: apprenant.email,
|
||||
apprenantNom: apprenant.nom,
|
||||
apprenantPrenom: apprenant.prenom,
|
||||
apprenantFonction: apprenant.fonction,
|
||||
formationNom: formation.nom,
|
||||
sequenceNom: sequence.nom,
|
||||
formateurNom: formateur?.nom,
|
||||
});
|
||||
|
||||
marquerRemerciementEnvoye(inscription.id, sequence.id);
|
||||
envoyesCount++;
|
||||
console.log(`[Remerciements] Email envoyé à ${apprenant.email}`);
|
||||
} catch (error) {
|
||||
errorsCount++;
|
||||
console.error(`[Remerciements] Erreur envoi à ${apprenant.email}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`[Remerciements] Séquence ${sequence.nom}: ${envoyesCount} envoyé(s), ${errorsCount} erreur(s)`);
|
||||
|
||||
// Optionnel: Marquer la séquence comme terminée
|
||||
// await db.update(sequences).set({ statut: 'terminee' }).where(eq(sequences.id, sequence.id));
|
||||
|
||||
} catch (error) {
|
||||
console.error(`[Remerciements] Erreur pour la séquence ${sequence.nom}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Envoie manuellement un email de remerciement pour une séquence spécifique
|
||||
* Utilisé par l'interface admin pour envoyer les remerciements à la demande
|
||||
*/
|
||||
export async function envoyerRemerciementsSequence(sequenceId: number): Promise<{ sent: number; failed: number }> {
|
||||
const db = await getDb();
|
||||
if (!db) {
|
||||
throw new Error("Base de données non disponible");
|
||||
}
|
||||
|
||||
// Récupérer la séquence avec formation et formateur
|
||||
const sequenceData = await db
|
||||
.select({
|
||||
sequence: sequences,
|
||||
formation: formations,
|
||||
formateur: formateurs,
|
||||
})
|
||||
.from(sequences)
|
||||
.innerJoin(formations, eq(sequences.formationId, formations.id))
|
||||
.leftJoin(formateurs, eq(sequences.formateurId, formateurs.id))
|
||||
.where(eq(sequences.id, sequenceId))
|
||||
.limit(1);
|
||||
|
||||
if (sequenceData.length === 0) {
|
||||
throw new Error("Séquence introuvable");
|
||||
}
|
||||
|
||||
const { sequence, formation, formateur } = sequenceData[0];
|
||||
|
||||
// Récupérer les inscriptions confirmées
|
||||
const inscriptionsConfirmees = await db
|
||||
.select({
|
||||
inscription: inscriptions,
|
||||
apprenant: apprenants,
|
||||
})
|
||||
.from(inscriptions)
|
||||
.innerJoin(apprenants, eq(inscriptions.apprenantId, apprenants.id))
|
||||
.where(
|
||||
and(
|
||||
eq(inscriptions.sequenceId, sequenceId),
|
||||
eq(inscriptions.statut, 'confirmee')
|
||||
)
|
||||
);
|
||||
|
||||
let sent = 0;
|
||||
let failed = 0;
|
||||
|
||||
for (const { inscription, apprenant } of inscriptionsConfirmees) {
|
||||
try {
|
||||
await sendRemerciementPostFormation({
|
||||
apprenantEmail: apprenant.email,
|
||||
apprenantNom: apprenant.nom,
|
||||
apprenantPrenom: apprenant.prenom,
|
||||
apprenantFonction: apprenant.fonction,
|
||||
formationNom: formation.nom,
|
||||
sequenceNom: sequence.nom,
|
||||
formateurNom: formateur?.nom,
|
||||
});
|
||||
sent++;
|
||||
} catch (error) {
|
||||
console.error(`[Remerciements] Erreur envoi à ${apprenant.email}:`, error);
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
|
||||
return { sent, failed };
|
||||
}
|
||||
Reference in New Issue
Block a user