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:
@@ -8,7 +8,7 @@ import * as db from "./db";
|
||||
import * as analyticsDb from "./analyticsDb";
|
||||
import { generateAnalyticsExcel, generateAnalyticsPDF } from "./analyticsExportService";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { sendInscriptionConfirmation, sendGroupEmail } from "./emailService";
|
||||
import { sendInscriptionConfirmation, sendGroupEmail, sendNotificationFormateurNouvelleInscription, sendNotificationFormateurAnnulation, sendAlerteCapaciteAtteinte, sendNotificationPlaceDisponible, sendRemerciementPostFormation } from "./emailService";
|
||||
import { generateExcelExport, generatePDFExport, generateFeuillePresence } from "./exportService";
|
||||
import { parseExcelFile, validateImportData, importToDatabase } from "./importExcel";
|
||||
|
||||
@@ -463,6 +463,70 @@ export const appRouter = router({
|
||||
lieu: inscriptionSequence.lieu,
|
||||
statut,
|
||||
});
|
||||
|
||||
// Notification au formateur si présent
|
||||
if (inscriptionSequence.formateurId) {
|
||||
const formateur = await db.getFormateurById(inscriptionSequence.formateurId);
|
||||
if (formateur && formateur.email) {
|
||||
const nbInscritsActuel = await db.countInscriptionsBySequence(input.sequenceId, 'confirmee');
|
||||
try {
|
||||
await sendNotificationFormateurNouvelleInscription({
|
||||
formateurEmail: formateur.email,
|
||||
formateurNom: formateur.nom,
|
||||
apprenantNom: inscriptionApprenant.nom,
|
||||
apprenantPrenom: inscriptionApprenant.prenom,
|
||||
apprenantFonction: inscriptionApprenant.fonction,
|
||||
apprenantEtablissement: inscriptionApprenant.codeEtablissement,
|
||||
formationNom: inscriptionFormation.nom,
|
||||
sequenceNom: inscriptionSequence.nom,
|
||||
nbInscrits: nbInscritsActuel,
|
||||
capaciteMax: inscriptionSequence.capaciteMax,
|
||||
dates: dates.map(d => ({
|
||||
dateDebut: new Date(d.dateDebut),
|
||||
dateFin: new Date(d.dateFin),
|
||||
ordre: d.ordre,
|
||||
})),
|
||||
});
|
||||
console.log(`[Notification] Email envoyé au formateur ${formateur.email} pour nouvelle inscription`);
|
||||
} catch (e) {
|
||||
console.error(`[Notification] Erreur envoi email formateur:`, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Alerte capacité atteinte pour les admins
|
||||
const nbInscritsApres = await db.countInscriptionsBySequence(input.sequenceId, 'confirmee');
|
||||
if (nbInscritsApres >= inscriptionSequence.capaciteMax) {
|
||||
try {
|
||||
const admins = await db.getAdminUsers();
|
||||
const adminEmails = admins.filter(a => a.email).map(a => a.email!);
|
||||
if (adminEmails.length > 0) {
|
||||
const nbListeAttente = await db.countInscriptionsBySequence(input.sequenceId, 'liste_attente');
|
||||
let formateurNom: string | undefined;
|
||||
if (inscriptionSequence.formateurId) {
|
||||
const formateur = await db.getFormateurById(inscriptionSequence.formateurId);
|
||||
formateurNom = formateur?.nom;
|
||||
}
|
||||
await sendAlerteCapaciteAtteinte({
|
||||
adminEmails,
|
||||
formationNom: inscriptionFormation.nom,
|
||||
sequenceNom: inscriptionSequence.nom,
|
||||
capaciteMax: inscriptionSequence.capaciteMax,
|
||||
nbInscrits: nbInscritsApres,
|
||||
nbListeAttente,
|
||||
formateurNom,
|
||||
dates: dates.map(d => ({
|
||||
dateDebut: new Date(d.dateDebut),
|
||||
dateFin: new Date(d.dateFin),
|
||||
ordre: d.ordre,
|
||||
})),
|
||||
});
|
||||
console.log(`[Notification] Alerte capacité atteinte envoyée à ${adminEmails.length} admin(s)`);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`[Notification] Erreur envoi alerte capacité:`, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { success: true, statut };
|
||||
@@ -493,8 +557,63 @@ export const appRouter = router({
|
||||
throw new TRPCError({ code: 'NOT_FOUND', message: 'Inscription introuvable' });
|
||||
}
|
||||
|
||||
// Récupérer les infos avant la mise à jour pour les notifications
|
||||
const apprenant = await db.getApprenantById(input.apprenantId);
|
||||
const formation = await db.getFormationById(sequence.formationId);
|
||||
|
||||
await db.updateInscription(inscription.id, { statut: 'annulee' });
|
||||
|
||||
// Notifications après désinscription
|
||||
if (apprenant && formation) {
|
||||
// Notification au formateur
|
||||
if (sequence.formateurId) {
|
||||
const formateur = await db.getFormateurById(sequence.formateurId);
|
||||
if (formateur && formateur.email) {
|
||||
const nbInscritsApres = await db.countInscriptionsBySequence(input.sequenceId, 'confirmee');
|
||||
try {
|
||||
await sendNotificationFormateurAnnulation({
|
||||
formateurEmail: formateur.email,
|
||||
formateurNom: formateur.nom,
|
||||
apprenantNom: apprenant.nom,
|
||||
apprenantPrenom: apprenant.prenom,
|
||||
apprenantFonction: apprenant.fonction,
|
||||
formationNom: formation.nom,
|
||||
sequenceNom: sequence.nom,
|
||||
nbInscrits: nbInscritsApres,
|
||||
capaciteMax: sequence.capaciteMax,
|
||||
});
|
||||
console.log(`[Notification] Email d'annulation envoyé au formateur ${formateur.email}`);
|
||||
} catch (e) {
|
||||
console.error(`[Notification] Erreur envoi email formateur:`, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Notifier le premier en liste d'attente qu'une place s'est libérée
|
||||
const inscriptionsListeAttente = await db.getInscriptionsBySequence(input.sequenceId);
|
||||
const premierEnAttente = inscriptionsListeAttente
|
||||
.filter(i => i.inscription.statut === 'liste_attente')
|
||||
.sort((a, b) => new Date(a.inscription.dateInscription).getTime() - new Date(b.inscription.dateInscription).getTime())[0];
|
||||
|
||||
if (premierEnAttente && premierEnAttente.apprenant) {
|
||||
try {
|
||||
await sendNotificationPlaceDisponible({
|
||||
apprenantEmail: premierEnAttente.apprenant.email,
|
||||
apprenantNom: premierEnAttente.apprenant.nom,
|
||||
apprenantPrenom: premierEnAttente.apprenant.prenom,
|
||||
apprenantFonction: premierEnAttente.apprenant.fonction,
|
||||
formationNom: formation.nom,
|
||||
sequenceNom: sequence.nom,
|
||||
positionListeAttente: 1,
|
||||
delaiReponse: 48,
|
||||
});
|
||||
console.log(`[Notification] Email place disponible envoyé à ${premierEnAttente.apprenant.email}`);
|
||||
} catch (e) {
|
||||
console.error(`[Notification] Erreur envoi email liste d'attente:`, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { success: true };
|
||||
}),
|
||||
|
||||
@@ -1496,6 +1615,23 @@ export const appRouter = router({
|
||||
return getEtablissementDetail(input.codeEtablissement);
|
||||
}),
|
||||
}),
|
||||
|
||||
// ===== NOTIFICATIONS =====
|
||||
notifications: router({
|
||||
envoyerRemerciements: adminProcedure
|
||||
.input(z.object({ sequenceId: z.number() }))
|
||||
.mutation(async ({ input }) => {
|
||||
const { envoyerRemerciementsSequence } = await import("./remerciementScheduler");
|
||||
return envoyerRemerciementsSequence(input.sequenceId);
|
||||
}),
|
||||
|
||||
envoyerRemerciementsAutomatiques: adminProcedure
|
||||
.mutation(async () => {
|
||||
const { processRemerciementsAutomatiques } = await import("./remerciementScheduler");
|
||||
await processRemerciementsAutomatiques();
|
||||
return { success: true };
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
export type AppRouter = typeof appRouter;
|
||||
|
||||
Reference in New Issue
Block a user