Checkpoint: Amélioration du bouton "Tester le rappel" pour utiliser automatiquement le bon template configuré :
- Modification de la procédure testerRappel pour détecter automatiquement le premier rappel actif configuré pour la séquence - Suppression du paramètre typeRappel (n'est plus nécessaire) - Création de la fonction sendRappelEmail générique qui utilise le templateType du rappel configuré - Le bouton frontend envoie maintenant le bon template (rappel 1, 3, 4, 5, 6, etc.) selon la configuration de la séquence - Gestion des pièces jointes configurées dans le rappel - Mise à jour du fichier todo.md pour marquer la tâche comme terminée
This commit is contained in:
@@ -836,8 +836,7 @@ export default function AdminSequences() {
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (sequence.dates && sequence.dates.length > 0) {
|
if (sequence.dates && sequence.dates.length > 0) {
|
||||||
testerRappelMutation.mutate({
|
testerRappelMutation.mutate({
|
||||||
sequenceId: sequence.id,
|
sequenceId: sequence.id
|
||||||
typeRappel: 'rappel' as const
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
toast.error("Cette séquence n'a pas de dates configurées");
|
toast.error("Cette séquence n'a pas de dates configurées");
|
||||||
|
|||||||
@@ -216,6 +216,105 @@ export async function sendTeaserEmail(params: {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Envoie un email de rappel générique avec le template spécifié
|
||||||
|
*/
|
||||||
|
export async function sendRappelEmail(params: {
|
||||||
|
apprenantEmail: string;
|
||||||
|
apprenantPrenom: string;
|
||||||
|
apprenantNom: string;
|
||||||
|
apprenantFonction: string;
|
||||||
|
formationNom: string;
|
||||||
|
sequenceNom: string;
|
||||||
|
dates: Array<{ dateDebut: Date; dateFin: Date; ordre: number }>;
|
||||||
|
lieu: string;
|
||||||
|
formateur?: string;
|
||||||
|
templateType: string;
|
||||||
|
attachmentUrl?: string;
|
||||||
|
attachmentFilename?: string;
|
||||||
|
attachmentMimeType?: string;
|
||||||
|
}): Promise<boolean> {
|
||||||
|
const fonctionLabel = params.apprenantFonction === 'directeur' ? 'Directeur' :
|
||||||
|
params.apprenantFonction === 'chef_service' ? 'Chef de service' : '';
|
||||||
|
const salutation = fonctionLabel ? `${fonctionLabel} ${params.apprenantPrenom} ${params.apprenantNom}` : params.apprenantPrenom;
|
||||||
|
|
||||||
|
const datesHTML = params.dates.map(date => `
|
||||||
|
<div class="date-item">
|
||||||
|
<strong>Date ${date.ordre} :</strong><br/>
|
||||||
|
${date.dateDebut.toLocaleDateString('fr-FR', {
|
||||||
|
weekday: 'long',
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'long',
|
||||||
|
day: 'numeric',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit'
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
`).join('');
|
||||||
|
|
||||||
|
const content = `
|
||||||
|
<h2>Rappel : Votre formation</h2>
|
||||||
|
<p>Bonjour ${salutation},</p>
|
||||||
|
|
||||||
|
<p>Nous vous rappelons votre formation <strong>${params.formationNom}</strong>.</p>
|
||||||
|
|
||||||
|
<div class="info-box">
|
||||||
|
<h3>Informations pratiques</h3>
|
||||||
|
<p><strong>Séquence :</strong> ${params.sequenceNom}</p>
|
||||||
|
<p><strong>Lieu :</strong> ${params.lieu}</p>
|
||||||
|
${params.formateur ? `<p><strong>Formateur :</strong> ${params.formateur}</p>` : ''}
|
||||||
|
<h4>Dates :</h4>
|
||||||
|
${datesHTML}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p><strong>Merci de vous présenter à l'heure indiquée.</strong></p>
|
||||||
|
|
||||||
|
<p>Si vous avez des questions, n'hésitez pas à contacter le service RH.</p>
|
||||||
|
|
||||||
|
<p>À très bientôt !</p>
|
||||||
|
`;
|
||||||
|
|
||||||
|
const variables = {
|
||||||
|
nomApprenant: params.apprenantNom,
|
||||||
|
prenomApprenant: params.apprenantPrenom,
|
||||||
|
nomFormation: params.formationNom,
|
||||||
|
nomSequence: params.sequenceNom,
|
||||||
|
dateDebut: params.dates[0]?.dateDebut.toLocaleDateString('fr-FR') || '',
|
||||||
|
dateFin: params.dates[params.dates.length - 1]?.dateFin.toLocaleDateString('fr-FR') || '',
|
||||||
|
datesHTML: datesHTML,
|
||||||
|
lieu: params.lieu,
|
||||||
|
formateur: params.formateur || '',
|
||||||
|
};
|
||||||
|
|
||||||
|
// Préparer les pièces jointes si nécessaire
|
||||||
|
const attachments: Array<{ filename: string; content: string; contentType: string }> = [];
|
||||||
|
|
||||||
|
if (params.attachmentUrl && params.attachmentFilename && params.attachmentMimeType) {
|
||||||
|
try {
|
||||||
|
// Télécharger le fichier depuis S3 et le convertir en base64
|
||||||
|
const response = await fetch(params.attachmentUrl);
|
||||||
|
const buffer = await response.arrayBuffer();
|
||||||
|
const base64 = Buffer.from(buffer).toString('base64');
|
||||||
|
|
||||||
|
attachments.push({
|
||||||
|
filename: params.attachmentFilename,
|
||||||
|
content: base64,
|
||||||
|
contentType: params.attachmentMimeType,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[Email] Erreur lors du téléchargement de la pièce jointe:', error);
|
||||||
|
// Continuer l'envoi sans la pièce jointe
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sendEmail({
|
||||||
|
to: params.apprenantEmail,
|
||||||
|
subject: `Rappel : ${params.formationNom}`,
|
||||||
|
html: await getEmailTemplate(content, params.templateType, variables),
|
||||||
|
attachments: attachments.length > 0 ? attachments : undefined,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Envoie un email de rappel J-7
|
* Envoie un email de rappel J-7
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1379,7 +1379,6 @@ export const appRouter = router({
|
|||||||
testerRappel: adminProcedure
|
testerRappel: adminProcedure
|
||||||
.input(z.object({
|
.input(z.object({
|
||||||
sequenceId: z.number(),
|
sequenceId: z.number(),
|
||||||
typeRappel: z.enum(['rappel', 'rappelJ1']),
|
|
||||||
}))
|
}))
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input }) => {
|
||||||
const sequence = await db.getSequenceById(input.sequenceId);
|
const sequence = await db.getSequenceById(input.sequenceId);
|
||||||
@@ -1402,6 +1401,21 @@ export const appRouter = router({
|
|||||||
// Récupérer les dates de la séquence
|
// Récupérer les dates de la séquence
|
||||||
const dates = await db.getDatesBySequence(input.sequenceId);
|
const dates = await db.getDatesBySequence(input.sequenceId);
|
||||||
|
|
||||||
|
// Récupérer les rappels configurés pour la première date de la séquence
|
||||||
|
if (dates.length === 0) {
|
||||||
|
throw new TRPCError({ code: 'BAD_REQUEST', message: 'Cette séquence n\'a pas de dates configurées' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const rappelsConfigures = await db.getRappelsByDateFormation(dates[0].id);
|
||||||
|
const rappelsActifs = rappelsConfigures.filter((r: any) => r.actif);
|
||||||
|
|
||||||
|
if (rappelsActifs.length === 0) {
|
||||||
|
throw new TRPCError({ code: 'BAD_REQUEST', message: 'Aucun rappel actif configuré pour cette séquence' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Utiliser le premier rappel actif trouvé
|
||||||
|
const rappelAUtiliser = rappelsActifs[0];
|
||||||
|
|
||||||
// Récupérer les inscrits
|
// Récupérer les inscrits
|
||||||
const inscrits = await db.getInscriptionsBySequence(input.sequenceId);
|
const inscrits = await db.getInscriptionsBySequence(input.sequenceId);
|
||||||
const inscritsConfirmes = inscrits.filter(i => i.inscription.statut === 'confirmee');
|
const inscritsConfirmes = inscrits.filter(i => i.inscription.statut === 'confirmee');
|
||||||
@@ -1418,42 +1432,28 @@ export const appRouter = router({
|
|||||||
throw new TRPCError({ code: 'BAD_REQUEST', message: 'Apprenant introuvable ou sans email' });
|
throw new TRPCError({ code: 'BAD_REQUEST', message: 'Apprenant introuvable ou sans email' });
|
||||||
}
|
}
|
||||||
|
|
||||||
const { sendRappelJ7Email, sendRappelJ1Email } = await import('./emailService');
|
const { sendRappelEmail } = await import('./emailService');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (input.typeRappel === 'rappel') {
|
await sendRappelEmail({
|
||||||
await sendRappelJ7Email({
|
apprenantEmail: apprenant.email,
|
||||||
apprenantEmail: apprenant.email,
|
apprenantPrenom: apprenant.prenom,
|
||||||
apprenantPrenom: apprenant.prenom,
|
apprenantNom: apprenant.nom,
|
||||||
apprenantNom: apprenant.nom,
|
apprenantFonction: apprenant.fonction || '',
|
||||||
apprenantFonction: apprenant.fonction || '',
|
formationNom: formation.nom,
|
||||||
formationNom: formation.nom,
|
sequenceNom: sequence.nom,
|
||||||
sequenceNom: sequence.nom,
|
dates: dates.map((d: any) => ({
|
||||||
dates: dates.map((d: any) => ({
|
dateDebut: d.dateDebut,
|
||||||
dateDebut: d.dateDebut,
|
dateFin: d.dateFin,
|
||||||
dateFin: d.dateFin,
|
ordre: d.ordre
|
||||||
ordre: d.ordre
|
})),
|
||||||
})),
|
lieu: sequence.lieu || '',
|
||||||
lieu: sequence.lieu || '',
|
formateur: formateurNom,
|
||||||
formateur: formateurNom,
|
templateType: rappelAUtiliser.templateType,
|
||||||
});
|
attachmentUrl: rappelAUtiliser.urlFichier || undefined,
|
||||||
} else {
|
attachmentFilename: rappelAUtiliser.nomFichier || undefined,
|
||||||
await sendRappelJ1Email({
|
attachmentMimeType: rappelAUtiliser.typeFichier || undefined,
|
||||||
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 || '',
|
|
||||||
formateur: formateurNom,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
|
|||||||
1
todo.md
1
todo.md
@@ -819,3 +819,4 @@
|
|||||||
- [ ] Corriger les erreurs TypeScript dans les fonctions de gestion des logs
|
- [ ] Corriger les erreurs TypeScript dans les fonctions de gestion des logs
|
||||||
- [x] Corriger l'erreur de validation du type de rappel dans la procédure testerRappel (typeRappel invalide)
|
- [x] Corriger l'erreur de validation du type de rappel dans la procédure testerRappel (typeRappel invalide)
|
||||||
- [x] Corriger l'erreur "Cannot read properties of undefined (reading 'replace')" lors de l'envoi de test de rappel
|
- [x] Corriger l'erreur "Cannot read properties of undefined (reading 'replace')" lors de l'envoi de test de rappel
|
||||||
|
- [x] Améliorer le bouton "Tester le rappel" pour utiliser automatiquement le bon template configuré pour la séquence (au lieu de toujours envoyer rappel J-7)
|
||||||
|
|||||||
Reference in New Issue
Block a user