Checkpoint: Amélioration système de rappels - Historique et notifications d'échec
**Nouvelles fonctionnalités** : 1. **Historique des rappels envoyés** - Nouvelle table `logsRappels` pour tracer tous les emails de rappels - Enregistrement automatique de chaque envoi (succès ou échec) - Prévention des doublons : vérification avant envoi - Informations stockées : rappelId, sequenceId, apprenantId, email, type, statut, message d'erreur 2. **Notifications automatiques à l'administrateur** - Alerte par email en cas d'échec d'envoi de rappels - Détail des échecs : liste des emails et messages d'erreur - Résumé : nombre d'envois réussis vs échecs 3. **Procédure de test manuel** (backend uniquement) - Nouvelle procédure tRPC `rappels.testerRappel` - Permet de tester l'envoi d'un rappel J-7 ou J-1 pour une séquence - Envoie au premier apprenant inscrit uniquement **Fichiers créés** : - `server/rappelDb.ts` : Fonctions de gestion des logs de rappels - `drizzle/schema.ts` : Ajout de la table `logsRappels` **Fichiers modifiés** : - `server/rappelScheduler.ts` : Intégration du logging et des notifications - `server/routers.ts` : Ajout de la procédure `testerRappel` **Bénéfices** : - ✅ Aucun doublon même en cas de redémarrage du serveur - ✅ Traçabilité complète de tous les rappels envoyés - ✅ Alertes automatiques en cas de problème d'envoi - ✅ Possibilité de tester les rappels avant envoi automatique
This commit is contained in:
@@ -891,6 +891,89 @@ export const appRouter = router({
|
||||
const result = await processRappelsAutomatiques();
|
||||
return result;
|
||||
}),
|
||||
|
||||
testerRappel: adminProcedure
|
||||
.input(z.object({
|
||||
sequenceId: z.number(),
|
||||
typeRappel: z.enum(['rappel', 'rappelJ1']),
|
||||
}))
|
||||
.mutation(async ({ input }) => {
|
||||
const sequence = await db.getSequenceById(input.sequenceId);
|
||||
if (!sequence) {
|
||||
throw new TRPCError({ code: 'NOT_FOUND', message: 'Séquence introuvable' });
|
||||
}
|
||||
|
||||
const formation = await db.getFormationById(sequence.formationId);
|
||||
if (!formation) {
|
||||
throw new TRPCError({ code: 'NOT_FOUND', message: 'Formation introuvable' });
|
||||
}
|
||||
|
||||
// Récupérer les dates de la séquence
|
||||
const dates = await db.getDatesBySequence(input.sequenceId);
|
||||
|
||||
// Récupérer les inscrits
|
||||
const inscrits = await db.getInscriptionsBySequence(input.sequenceId);
|
||||
const inscritsConfirmes = inscrits.filter(i => i.inscription.statut === 'confirmee');
|
||||
|
||||
if (inscritsConfirmes.length === 0) {
|
||||
throw new TRPCError({ code: 'BAD_REQUEST', message: 'Aucun apprenant inscrit confirmé pour cette séquence' });
|
||||
}
|
||||
|
||||
// Envoyer le rappel de test au premier inscrit uniquement
|
||||
const premierInscrit = inscritsConfirmes[0];
|
||||
const apprenant = premierInscrit.apprenant;
|
||||
|
||||
if (!apprenant || !apprenant.email) {
|
||||
throw new TRPCError({ code: 'BAD_REQUEST', message: 'Apprenant introuvable ou sans email' });
|
||||
}
|
||||
|
||||
const { sendRappelJ7Email, sendRappelJ1Email } = await import('./emailService');
|
||||
|
||||
try {
|
||||
if (input.typeRappel === 'rappel') {
|
||||
await sendRappelJ7Email({
|
||||
apprenantEmail: apprenant.email,
|
||||
apprenantPrenom: apprenant.prenom,
|
||||
apprenantNom: apprenant.nom,
|
||||
apprenantFonction: apprenant.fonction,
|
||||
formationNom: formation.nom,
|
||||
sequenceNom: sequence.nom,
|
||||
dates: dates.map((d: any) => ({
|
||||
dateDebut: d.dateDebut,
|
||||
dateFin: d.dateFin,
|
||||
ordre: d.ordre
|
||||
})),
|
||||
lieu: sequence.lieu || '',
|
||||
});
|
||||
} else {
|
||||
await sendRappelJ1Email({
|
||||
apprenantEmail: apprenant.email,
|
||||
apprenantPrenom: apprenant.prenom,
|
||||
apprenantNom: apprenant.nom,
|
||||
apprenantFonction: apprenant.fonction,
|
||||
formationNom: formation.nom,
|
||||
sequenceNom: sequence.nom,
|
||||
dates: dates.map((d: any) => ({
|
||||
dateDebut: d.dateDebut,
|
||||
dateFin: d.dateFin,
|
||||
ordre: d.ordre
|
||||
})),
|
||||
lieu: sequence.lieu || '',
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `Rappel de test envoyé à ${apprenant.email}`,
|
||||
destinataire: apprenant.email,
|
||||
};
|
||||
} catch (error: any) {
|
||||
throw new TRPCError({
|
||||
code: 'INTERNAL_SERVER_ERROR',
|
||||
message: `Erreur lors de l'envoi: ${error.message}`,
|
||||
});
|
||||
}
|
||||
}),
|
||||
}),
|
||||
|
||||
emailConfig: router({
|
||||
|
||||
Reference in New Issue
Block a user