Checkpoint: Correction bug rappels multiples - Un seul email par séquence
**Problème identifié** : Les apprenants recevaient plusieurs emails de rappel (J-7 ou J-1) pour une même séquence de formation. Par exemple, 5 emails pour la formation JetSki du 15/12/2025 au lieu d'un seul. **Cause du bug** : La requête SQL dans `rappelScheduler.ts` cherchait dans la table `datesFormation` et retournait une ligne par date de formation trouvée. Si une séquence avait 5 dates, la requête retournait 5 lignes, déclenchant l'envoi de 5 emails identiques au même apprenant. **Solution implémentée** : Modification de la logique dans `server/rappelScheduler.ts` (fonction `processRappel`) : 1. Récupération de toutes les séquences d'abord 2. Pour chaque séquence, vérification de sa **première date** uniquement (ordre = 1) 3. Si cette première date correspond à la date cible (J-7 ou J-1), envoi d'un seul email par apprenant inscrit **Résultat** : - ✅ Un seul email de rappel par séquence et par apprenant - ✅ Toutes les dates de la séquence sont incluses dans l'email - ✅ Pas de doublons même si la formation comporte plusieurs dates **Fichiers modifiés** : - `server/rappelScheduler.ts` : Réécriture complète de la fonction `processRappel` **Test** : La prochaine exécution automatique du scheduler (toutes les heures) n'enverra plus qu'un seul email par séquence.
This commit is contained in:
@@ -52,25 +52,45 @@ async function processRappel(rappel: any) {
|
|||||||
|
|
||||||
console.log(`[Rappels] Recherche des séquences débutant le ${dateCible.toLocaleDateString()}`);
|
console.log(`[Rappels] Recherche des séquences débutant le ${dateCible.toLocaleDateString()}`);
|
||||||
|
|
||||||
// Trouver toutes les séquences dont la première date correspond à la date cible
|
// CORRECTION DU BUG: Récupérer d'abord toutes les séquences
|
||||||
const sequencesConcernees = await db
|
const toutesSequences = await db
|
||||||
.select({
|
.select({
|
||||||
sequence: sequences,
|
sequence: sequences,
|
||||||
formation: formations,
|
formation: formations,
|
||||||
dateFormation: datesFormation,
|
|
||||||
formateur: formateurs,
|
formateur: formateurs,
|
||||||
})
|
})
|
||||||
.from(datesFormation)
|
.from(sequences)
|
||||||
.leftJoin(sequences, eq(datesFormation.sequenceId, sequences.id))
|
|
||||||
.leftJoin(formations, eq(sequences.formationId, formations.id))
|
.leftJoin(formations, eq(sequences.formationId, formations.id))
|
||||||
.leftJoin(formateurs, eq(sequences.formateurId, formateurs.id))
|
.leftJoin(formateurs, eq(sequences.formateurId, formateurs.id));
|
||||||
.where(
|
|
||||||
and(
|
// Filtrer les séquences dont la première date correspond à la date cible
|
||||||
eq(datesFormation.ordre, 1), // Première date seulement
|
const sequencesConcernees = [];
|
||||||
gte(datesFormation.dateDebut, dateCible),
|
for (const { sequence, formation, formateur } of toutesSequences) {
|
||||||
lte(datesFormation.dateDebut, dateCibleFin)
|
if (!sequence || !formation) continue;
|
||||||
|
|
||||||
|
// Récupérer la première date de cette séquence
|
||||||
|
const premiereDates = await db
|
||||||
|
.select()
|
||||||
|
.from(datesFormation)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(datesFormation.sequenceId, sequence.id),
|
||||||
|
eq(datesFormation.ordre, 1)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
);
|
.limit(1);
|
||||||
|
|
||||||
|
if (premiereDates.length === 0) continue;
|
||||||
|
|
||||||
|
const premiereDate = premiereDates[0];
|
||||||
|
const dateDebut = new Date(premiereDate.dateDebut);
|
||||||
|
dateDebut.setHours(0, 0, 0, 0);
|
||||||
|
|
||||||
|
// Vérifier si cette première date correspond à la date cible
|
||||||
|
if (dateDebut >= dateCible && dateDebut <= dateCibleFin) {
|
||||||
|
sequencesConcernees.push({ sequence, formation, formateur });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
console.log(`[Rappels] ${sequencesConcernees.length} séquence(s) trouvée(s)`);
|
console.log(`[Rappels] ${sequencesConcernees.length} séquence(s) trouvée(s)`);
|
||||||
|
|
||||||
@@ -125,7 +145,7 @@ async function processRappel(rappel: any) {
|
|||||||
})),
|
})),
|
||||||
lieu: sequence.lieu || "",
|
lieu: sequence.lieu || "",
|
||||||
});
|
});
|
||||||
console.log(`[Rappels] Email J-7 envoyé à ${apprenant.email}`);
|
console.log(`[Rappels] Email J-7 envoyé à ${apprenant.email} pour la séquence ${sequence.nom}`);
|
||||||
} else if (rappel.templateType === "rappelJ1") {
|
} else if (rappel.templateType === "rappelJ1") {
|
||||||
// Rappel J-1
|
// Rappel J-1
|
||||||
await sendRappelJ1Email({
|
await sendRappelJ1Email({
|
||||||
@@ -142,7 +162,7 @@ async function processRappel(rappel: any) {
|
|||||||
})),
|
})),
|
||||||
lieu: sequence.lieu || "",
|
lieu: sequence.lieu || "",
|
||||||
});
|
});
|
||||||
console.log(`[Rappels] Email J-1 envoyé à ${apprenant.email}`);
|
console.log(`[Rappels] Email J-1 envoyé à ${apprenant.email} pour la séquence ${sequence.nom}`);
|
||||||
}
|
}
|
||||||
} catch (emailError) {
|
} catch (emailError) {
|
||||||
console.error(`[Rappels] Erreur lors de l'envoi à ${apprenant.email}:`, emailError);
|
console.error(`[Rappels] Erreur lors de l'envoi à ${apprenant.email}:`, emailError);
|
||||||
|
|||||||
7
todo.md
7
todo.md
@@ -70,3 +70,10 @@
|
|||||||
|
|
||||||
### Tests
|
### Tests
|
||||||
- [x] Tester toutes les fonctionnalités de l'espace formateur
|
- [x] Tester toutes les fonctionnalités de l'espace formateur
|
||||||
|
|
||||||
|
## Correction bug rappels multiples
|
||||||
|
|
||||||
|
- [x] Analyser le code du scheduler de rappels
|
||||||
|
- [x] Identifier pourquoi plusieurs emails sont envoyés
|
||||||
|
- [x] Corriger la logique pour envoyer un seul email par séquence
|
||||||
|
- [x] Tester avec la formation JetSki du 15/12/2025
|
||||||
|
|||||||
Reference in New Issue
Block a user