/** * Service d'envoi d'emails pour les formations * Utilise le système de notification Manus pour envoyer des emails */ import { generateFormationICS } from "./icsGenerator"; import { sendEmail as sendEmailViaService, isEmailServiceConfigured } from "./_core/emailSender"; import { generateEmailFromTemplate } from "./emailTemplateGenerator"; import { EmailVariables } from "./emailTemplateUtils"; interface EmailParams { to: string; subject: string; html: string; attachments?: Array<{ filename: string; content: string; contentType: string; }>; } /** * Envoie un email via le service d'envoi configuré * Utilise Resend si RESEND_API_KEY est configuré, sinon simule l'envoi */ async function sendEmail(params: EmailParams): Promise { return await sendEmailViaService(params); } /** * Template HTML de base pour les emails (avec template personnalisé) */ async function getEmailTemplate( content: string, templateType: string = 'inscription', variables?: EmailVariables ): Promise { return await generateEmailFromTemplate(templateType, content, variables); } /** * Envoie un email de confirmation d'inscription avec invitations Outlook pour toutes les dates */ export async function sendInscriptionConfirmation(params: { apprenantEmail: string; apprenantNom: string; apprenantPrenom: string; apprenantFonction: string; formationNom: string; sequenceNom: string; dates: Array<{ dateDebut: Date; dateFin: Date; ordre: number }>; lieu: string; statut: 'confirmee' | 'liste_attente'; }): Promise { const isConfirmed = params.statut === 'confirmee'; const fonctionLabel = params.apprenantFonction === 'directeur' ? 'Directeur' : params.apprenantFonction === 'chef_service' ? 'Chef de service' : 'Autre'; // Générer la liste des dates const datesHTML = params.dates.map(date => `
Date ${date.ordre} :
Du ${date.dateDebut.toLocaleDateString('fr-FR', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric', hour: '2-digit', minute: '2-digit' })}
au ${date.dateFin.toLocaleDateString('fr-FR', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric', hour: '2-digit', minute: '2-digit' })}
`).join(''); const content = `

Confirmation d'inscription

Bonjour ${fonctionLabel} ${params.apprenantPrenom} ${params.apprenantNom},

${isConfirmed ? '

Votre inscription a été confirmée !

' : '

Vous avez été ajouté à la liste d\'attente.

' }

Détails de la formation

Formation : ${params.formationNom}

Séquence : ${params.sequenceNom}

Lieu : ${params.lieu}

Dates de formation (${params.dates.length} séance${params.dates.length > 1 ? 's' : ''}) :

${datesHTML}
${isConfirmed ? `

Des invitations Outlook sont jointes à cet email pour chaque date de formation. Merci de les ajouter à votre calendrier pour bloquer votre agenda.

Important : Les inscriptions seront bloquées 15 jours avant le début de la séquence. Après cette date, aucune modification ne sera possible.

` : '

Nous vous contacterons dès qu\'une place se libère.

' }

À bientôt pour cette formation !

`; // Générer une invitation ICS pour chaque date const attachments = isConfirmed ? params.dates.map((date, index) => ({ filename: `invitation_date_${date.ordre}.ics`, content: generateFormationICS({ formationNom: params.formationNom, sessionNom: `${params.sequenceNom} - Date ${date.ordre}`, dateDebut: date.dateDebut, dateFin: date.dateFin, lieu: params.lieu, apprenantNom: params.apprenantNom, apprenantPrenom: params.apprenantPrenom, apprenantEmail: params.apprenantEmail, }), contentType: 'text/calendar', })) : undefined; // Préparer les variables pour le remplacement (avec alias pour compatibilité) const variables: EmailVariables = { 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') || '', lieu: params.lieu, datesHTML: datesHTML, // HTML formaté de toutes les dates // Alias pour compatibilité avec les templates utilisant les noms courts nom: params.apprenantNom, prenom: params.apprenantPrenom, formation: params.formationNom, sequence: params.sequenceNom, }; return sendEmail({ to: params.apprenantEmail, subject: isConfirmed ? `Confirmation d'inscription - ${params.formationNom}` : `Liste d'attente - ${params.formationNom}`, html: await getEmailTemplate(content, 'inscription', variables), attachments, }); } /** * Envoie un email teaser pour une séquence */ export async function sendTeaserEmail(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; }): Promise { 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 => `
Date ${date.ordre} : ${date.dateDebut.toLocaleDateString('fr-FR', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' })}
`).join(''); const content = `

Votre formation approche !

Bonjour ${salutation},

Nous sommes ravis de vous accueillir prochainement pour la formation ${params.formationNom}.

Séquence : ${params.sequenceNom}

Dates :

${datesHTML} ${params.lieu ? `

Lieu : ${params.lieu}

` : ''} ${params.formateur ? `

Formateur : ${params.formateur}

` : ''}

Préparez-vous à vivre une expérience enrichissante qui vous permettra de développer vos compétences managériales.

À très bientôt !

`; 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, // Ajouter le HTML des dates lieu: params.lieu || '', formateur: params.formateur || '', }; return sendEmail({ to: params.apprenantEmail, subject: `Votre formation ${params.formationNom} approche !`, html: await getEmailTemplate(content, 'teaser', variables), }); } /** * 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; attachmentUrl2?: string; attachmentFilename2?: string; attachmentMimeType2?: string; }): Promise { 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 => `
Date ${date.ordre} :
${date.dateDebut.toLocaleDateString('fr-FR', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric', hour: '2-digit', minute: '2-digit' })}
`).join(''); const content = `

Rappel : Votre formation

Bonjour ${salutation},

Nous vous rappelons votre formation ${params.formationNom}.

Informations pratiques

Séquence : ${params.sequenceNom}

Lieu : ${params.lieu}

${params.formateur ? `

Formateur : ${params.formateur}

` : ''}

Dates :

${datesHTML}

Merci de vous présenter à l'heure indiquée.

Si vous avez des questions, n'hésitez pas à contacter le service RH.

À très bientôt !

`; 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 { let base64: string; // Vérifier si c'est une URL locale (commence par /uploads/) if (params.attachmentUrl.startsWith('/uploads/')) { // Lire le fichier depuis le système de fichiers const fs = await import('fs/promises'); const path = await import('path'); const filePath = path.join(process.cwd(), params.attachmentUrl); const fileBuffer = await fs.readFile(filePath); base64 = fileBuffer.toString('base64'); } else { // Télécharger le fichier depuis une URL externe (S3) const response = await fetch(params.attachmentUrl); const arrayBuffer = await response.arrayBuffer(); base64 = Buffer.from(arrayBuffer).toString('base64'); } attachments.push({ filename: params.attachmentFilename, content: base64, contentType: params.attachmentMimeType, }); } catch (error) { console.error('[Email] Erreur lors du chargement de la pièce jointe:', error); // Continuer l'envoi sans la pièce jointe } } // Charger la deuxième pièce jointe si présente if (params.attachmentUrl2 && params.attachmentFilename2 && params.attachmentMimeType2) { try { let base64: string; // Vérifier si c'est une URL locale (commence par /uploads/) if (params.attachmentUrl2.startsWith('/uploads/')) { // Lire le fichier depuis le système de fichiers const fs = await import('fs/promises'); const path = await import('path'); const filePath = path.join(process.cwd(), params.attachmentUrl2); const fileBuffer = await fs.readFile(filePath); base64 = fileBuffer.toString('base64'); } else { // Télécharger le fichier depuis une URL externe (S3) const response = await fetch(params.attachmentUrl2); const arrayBuffer = await response.arrayBuffer(); base64 = Buffer.from(arrayBuffer).toString('base64'); } attachments.push({ filename: params.attachmentFilename2, content: base64, contentType: params.attachmentMimeType2, }); } catch (error) { console.error('[Email] Erreur lors du chargement de la pièce jointe 2:', 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 */ export async function sendRappelJ7Email(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; attachmentUrl?: string; attachmentFilename?: string; attachmentMimeType?: string; }): Promise { 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 => `
Date ${date.ordre} :
${date.dateDebut.toLocaleDateString('fr-FR', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric', hour: '2-digit', minute: '2-digit' })}
`).join(''); const content = `

Rappel : Votre formation dans 7 jours

Bonjour ${salutation},

Nous vous rappelons que votre formation ${params.formationNom} commence dans 7 jours.

Informations pratiques

Séquence : ${params.sequenceNom}

Lieu : ${params.lieu}

${params.formateur ? `

Formateur : ${params.formateur}

` : ''}

Dates :

${datesHTML}

Merci de vous présenter à l'heure indiquée.

Si vous avez des questions, n'hésitez pas à contacter le service RH.

À très bientôt !

`; 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, // Ajouter le HTML des dates 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 { let base64: string; // Vérifier si c'est une URL locale (commence par /uploads/) if (params.attachmentUrl.startsWith('/uploads/')) { // Lire le fichier depuis le système de fichiers const fs = await import('fs/promises'); const path = await import('path'); const filePath = path.join(process.cwd(), params.attachmentUrl); const fileBuffer = await fs.readFile(filePath); base64 = fileBuffer.toString('base64'); } else { // Télécharger le fichier depuis une URL externe (S3) const response = await fetch(params.attachmentUrl); const arrayBuffer = await response.arrayBuffer(); base64 = Buffer.from(arrayBuffer).toString('base64'); } attachments.push({ filename: params.attachmentFilename, content: base64, contentType: params.attachmentMimeType, }); } catch (error) { console.error('[Email] Erreur lors du chargement de la pièce jointe:', error); // Continuer l'envoi sans la pièce jointe } } return sendEmail({ to: params.apprenantEmail, subject: `Rappel J-7 : ${params.formationNom}`, html: await getEmailTemplate(content, 'rappel', variables), attachments: attachments.length > 0 ? attachments : undefined, }); } /** * Envoie un email de rappel J-1 à un apprenant */ export async function sendRappelJ1Email(params: { apprenantEmail: string; apprenantNom: string; apprenantPrenom: string; apprenantFonction: string; formationNom: string; sequenceNom: string; dates: Array<{ dateDebut: Date; dateFin: Date; ordre: number }>; lieu?: string; formateur?: string; attachmentUrl?: string; attachmentFilename?: string; attachmentMimeType?: string; }): Promise { 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 => `
Date ${date.ordre} : ${date.dateDebut.toLocaleDateString('fr-FR', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric', hour: '2-digit', minute: '2-digit' })} - ${date.dateFin.toLocaleDateString('fr-FR', { hour: '2-digit', minute: '2-digit' })}
`).join(''); const content = `

Rappel : Votre formation commence demain !

Bonjour ${salutation},

Nous vous rappelons que votre formation ${params.formationNom} commence demain.

Informations pratiques

Séquence : ${params.sequenceNom}

${params.lieu ? `

Lieu : ${params.lieu}

` : ''} ${params.formateur ? `

Formateur : ${params.formateur}

` : ''}

Dates :

${datesHTML}

Merci de vous présenter à l'heure indiquée.

N'oubliez pas d'apporter le matériel nécessaire.

Si vous avez des questions de dernière minute, n'hésitez pas à contacter le service RH.

À demain !

`; 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, // Ajouter le HTML des dates 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 { let base64: string; // Vérifier si c'est une URL locale (commence par /uploads/) if (params.attachmentUrl.startsWith('/uploads/')) { // Lire le fichier depuis le système de fichiers const fs = await import('fs/promises'); const path = await import('path'); const filePath = path.join(process.cwd(), params.attachmentUrl); const fileBuffer = await fs.readFile(filePath); base64 = fileBuffer.toString('base64'); } else { // Télécharger le fichier depuis une URL externe (S3) const response = await fetch(params.attachmentUrl); const arrayBuffer = await response.arrayBuffer(); base64 = Buffer.from(arrayBuffer).toString('base64'); } attachments.push({ filename: params.attachmentFilename, content: base64, contentType: params.attachmentMimeType, }); } catch (error) { console.error('[Email] Erreur lors du chargement de la pièce jointe:', error); // Continuer l'envoi sans la pièce jointe } } return sendEmail({ to: params.apprenantEmail, subject: `Rappel J-1 : ${params.formationNom} - C'est demain !`, html: await getEmailTemplate(content, 'rappel', variables), attachments: attachments.length > 0 ? attachments : undefined, }); } /** * Envoie des emails groupés à tous les inscrits d'une séquence */ export async function sendGroupEmail(params: { recipients: Array<{ email: string; prenom: string; nom: string; fonction: string }>; formationNom: string; sequenceNom: string; type: 'teaser' | 'rappel' | 'rappel_j1'; dates: Array<{ dateDebut: Date; dateFin: Date; ordre: number }>; lieu?: string; formateur?: string; }): Promise<{ sent: number; failed: number }> { let sent = 0; let failed = 0; for (const recipient of params.recipients) { try { if (params.type === 'teaser') { await sendTeaserEmail({ apprenantEmail: recipient.email, apprenantPrenom: recipient.prenom, apprenantNom: recipient.nom, apprenantFonction: recipient.fonction, formationNom: params.formationNom, sequenceNom: params.sequenceNom, dates: params.dates, lieu: params.lieu, formateur: params.formateur, }); } else if (params.type === 'rappel') { await sendRappelJ7Email({ apprenantEmail: recipient.email, apprenantPrenom: recipient.prenom, apprenantNom: recipient.nom, apprenantFonction: recipient.fonction, formationNom: params.formationNom, sequenceNom: params.sequenceNom, dates: params.dates, lieu: params.lieu || '', formateur: params.formateur, }); } else { // rappel_j1 await sendRappelJ1Email({ apprenantEmail: recipient.email, apprenantPrenom: recipient.prenom, apprenantNom: recipient.nom, apprenantFonction: recipient.fonction, formationNom: params.formationNom, sequenceNom: params.sequenceNom, dates: params.dates, lieu: params.lieu, formateur: params.formateur, }); } sent++; } catch (error) { console.error(`Erreur envoi email à ${recipient.email}:`, error); failed++; } } return { sent, failed }; } /** * Envoie un email de réinitialisation de mot de passe */ export async function sendPasswordResetEmail(params: { apprenantEmail: string; apprenantNom: string; apprenantPrenom: string; resetLink: string; }): Promise { const content = `

Réinitialisation de mot de passe

Bonjour ${params.apprenantPrenom} ${params.apprenantNom},

Vous avez demandé la réinitialisation de votre mot de passe pour votre compte Manager Itinova.

Cliquez sur le bouton ci-dessous pour créer un nouveau mot de passe :

Ou copiez ce lien dans votre navigateur :

${params.resetLink}

⚠️ Important :

  • Ce lien est valide pendant 24 heures seulement
  • Il ne peut être utilisé qu'une seule fois
  • Si vous n'avez pas demandé cette réinitialisation, ignorez cet email

Pour toute question, contactez le service RH.

`; return sendEmail({ to: params.apprenantEmail, subject: 'Réinitialisation de votre mot de passe - Manager Itinova', html: await getEmailTemplate(content, 'reset_password'), }); } /** * ======================================== * SYSTÈME DE NOTIFICATIONS ENRICHI * ======================================== */ /** * Envoie un email de remerciement post-formation à un apprenant */ export async function sendRemerciementPostFormation(params: { apprenantEmail: string; apprenantNom: string; apprenantPrenom: string; apprenantFonction: string; formationNom: string; sequenceNom: string; formateurNom?: string; lienQuestionnaire?: string; }): Promise { const fonctionLabel = params.apprenantFonction === 'directeur' ? 'Directeur' : params.apprenantFonction === 'chef_service' ? 'Chef de service' : ''; const salutation = fonctionLabel ? `${fonctionLabel} ${params.apprenantPrenom} ${params.apprenantNom}` : params.apprenantPrenom; const content = `

Merci pour votre participation !

Bonjour ${salutation},

Nous tenons à vous remercier pour votre participation à la formation ${params.formationNom} (${params.sequenceNom}).

${params.formateurNom ? `

Nous espérons que les sessions animées par ${params.formateurNom} ont répondu à vos attentes.

` : ''}

Votre avis compte !

Afin d'améliorer continuellement nos formations, nous vous invitons à partager votre retour d'expérience.

${params.lienQuestionnaire ? ` ` : '

Un questionnaire de satisfaction vous sera envoyé prochainement.

'}

Nous vous souhaitons une excellente continuation dans vos fonctions et espérons vous revoir lors de prochaines formations.

Cordialement,
L'équipe Formation

`; const variables = { nomApprenant: params.apprenantNom, prenomApprenant: params.apprenantPrenom, nomFormation: params.formationNom, nomSequence: params.sequenceNom, formateur: params.formateurNom || '', }; return sendEmail({ to: params.apprenantEmail, subject: `Merci pour votre participation - ${params.formationNom}`, html: await getEmailTemplate(content, 'remerciement', variables), }); } /** * Envoie une notification au formateur pour une nouvelle inscription */ export async function sendNotificationFormateurNouvelleInscription(params: { formateurEmail: string; formateurNom: string; apprenantNom: string; apprenantPrenom: string; apprenantFonction: string; apprenantEtablissement?: string; formationNom: string; sequenceNom: string; nbInscrits: number; capaciteMax: number; dates: Array<{ dateDebut: Date; dateFin: Date; ordre: number }>; }): Promise { const fonctionLabel = params.apprenantFonction === 'directeur' ? 'Directeur' : params.apprenantFonction === 'chef_service' ? 'Chef de service' : 'Autre'; const datesHTML = params.dates.map(date => `
  • Date ${date.ordre} : ${date.dateDebut.toLocaleDateString('fr-FR', { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric' })}
  • `).join(''); const content = `

    🎓 Nouvelle inscription à votre formation

    Bonjour ${params.formateurNom},

    Un nouvel apprenant vient de s'inscrire à votre formation.

    Détails de l'inscription

    Apprenant : ${params.apprenantPrenom} ${params.apprenantNom}

    Fonction : ${fonctionLabel}

    ${params.apprenantEtablissement ? `

    Établissement : ${params.apprenantEtablissement}

    ` : ''}

    Formation : ${params.formationNom}

    Séquence : ${params.sequenceNom}

    📊 État des inscriptions

    ${params.nbInscrits} / ${params.capaciteMax} places occupées

    Dates de la formation :

      ${datesHTML}

    Cordialement,
    L'équipe Formation

    `; return sendEmail({ to: params.formateurEmail, subject: `Nouvelle inscription - ${params.formationNom} (${params.sequenceNom})`, html: await getEmailTemplate(content, 'notification_formateur'), }); } /** * Envoie une notification au formateur pour une annulation d'inscription */ export async function sendNotificationFormateurAnnulation(params: { formateurEmail: string; formateurNom: string; apprenantNom: string; apprenantPrenom: string; apprenantFonction: string; formationNom: string; sequenceNom: string; nbInscrits: number; capaciteMax: number; raisonAnnulation?: string; }): Promise { const fonctionLabel = params.apprenantFonction === 'directeur' ? 'Directeur' : params.apprenantFonction === 'chef_service' ? 'Chef de service' : 'Autre'; const content = `

    ⚠️ Annulation d'inscription

    Bonjour ${params.formateurNom},

    Un apprenant a annulé son inscription à votre formation.

    Détails de l'annulation

    Apprenant : ${params.apprenantPrenom} ${params.apprenantNom}

    Fonction : ${fonctionLabel}

    Formation : ${params.formationNom}

    Séquence : ${params.sequenceNom}

    ${params.raisonAnnulation ? `

    Raison : ${params.raisonAnnulation}

    ` : ''}

    📊 État des inscriptions après annulation

    ${params.nbInscrits} / ${params.capaciteMax} places occupées

    ${params.capaciteMax - params.nbInscrits} place(s) disponible(s)

    Cordialement,
    L'équipe Formation

    `; return sendEmail({ to: params.formateurEmail, subject: `Annulation d'inscription - ${params.formationNom} (${params.sequenceNom})`, html: await getEmailTemplate(content, 'notification_formateur'), }); } /** * Envoie une alerte aux admins quand la capacité maximale est atteinte */ export async function sendAlerteCapaciteAtteinte(params: { adminEmails: string[]; formationNom: string; sequenceNom: string; capaciteMax: number; nbInscrits: number; nbListeAttente: number; formateurNom?: string; dates: Array<{ dateDebut: Date; dateFin: Date; ordre: number }>; }): Promise<{ sent: number; failed: number }> { const datesHTML = params.dates.map(date => `
  • ${date.dateDebut.toLocaleDateString('fr-FR', { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric' })}
  • `).join(''); const content = `

    🔔 Alerte : Capacité maximale atteinte

    ⚠️ La séquence est complète

    La capacité maximale de la séquence ${params.sequenceNom} a été atteinte.

    Détails de la séquence

    Formation : ${params.formationNom}

    Séquence : ${params.sequenceNom}

    ${params.formateurNom ? `

    Formateur : ${params.formateurNom}

    ` : ''}

    Capacité : ${params.nbInscrits} / ${params.capaciteMax} (100%)

    ${params.nbListeAttente > 0 ? `

    Liste d'attente : ${params.nbListeAttente} personne(s)

    ` : ''}

    Dates prévues :

      ${datesHTML}

    Actions possibles

    • Augmenter la capacité maximale de la séquence
    • Créer une nouvelle séquence pour cette formation
    • Contacter les personnes en liste d'attente pour les informer

    Cordialement,
    Le système de gestion des formations

    `; let sent = 0; let failed = 0; for (const adminEmail of params.adminEmails) { try { await sendEmail({ to: adminEmail, subject: `🔔 Capacité atteinte - ${params.formationNom} (${params.sequenceNom})`, html: await getEmailTemplate(content, 'alerte_admin'), }); sent++; } catch (error) { console.error(`Erreur envoi alerte à ${adminEmail}:`, error); failed++; } } return { sent, failed }; } /** * Envoie une notification aux apprenants en liste d'attente quand une place se libère */ export async function sendNotificationPlaceDisponible(params: { apprenantEmail: string; apprenantNom: string; apprenantPrenom: string; apprenantFonction: string; formationNom: string; sequenceNom: string; positionListeAttente: number; lienConfirmation?: string; delaiReponse?: number; // en heures }): Promise { const fonctionLabel = params.apprenantFonction === 'directeur' ? 'Directeur' : params.apprenantFonction === 'chef_service' ? 'Chef de service' : ''; const salutation = fonctionLabel ? `${fonctionLabel} ${params.apprenantPrenom} ${params.apprenantNom}` : params.apprenantPrenom; const delai = params.delaiReponse || 48; const content = `

    🎉 Une place s'est libérée !

    Bonjour ${salutation},

    Bonne nouvelle ! Une place vient de se libérer pour la formation à laquelle vous êtes inscrit(e) en liste d'attente.

    ✅ Vous pouvez maintenant confirmer votre inscription

    Formation : ${params.formationNom}

    Séquence : ${params.sequenceNom}

    Votre position : ${params.positionListeAttente}${params.positionListeAttente === 1 ? 'er' : 'ème'} sur la liste d'attente

    ${params.lienConfirmation ? ` ` : '

    Veuillez contacter le service RH pour confirmer votre inscription.

    '}

    ⏰ Important

    Vous avez ${delai} heures pour confirmer votre inscription.

    Passé ce délai, la place sera proposée à la personne suivante sur la liste d'attente.

    Cordialement,
    L'équipe Formation

    `; const variables = { nomApprenant: params.apprenantNom, prenomApprenant: params.apprenantPrenom, nomFormation: params.formationNom, nomSequence: params.sequenceNom, }; return sendEmail({ to: params.apprenantEmail, subject: `🎉 Place disponible - ${params.formationNom}`, html: await getEmailTemplate(content, 'notification_liste_attente', variables), }); } /** * Envoie une notification à un apprenant en liste d'attente pour l'informer d'une annulation de séquence */ export async function sendNotificationAnnulationListeAttente(params: { apprenantEmail: string; apprenantNom: string; apprenantPrenom: string; apprenantFonction: string; formationNom: string; sequenceNom: string; raisonAnnulation?: string; alternativesDisponibles?: Array<{ sequenceNom: string; nbPlaces: number }>; }): Promise { const fonctionLabel = params.apprenantFonction === 'directeur' ? 'Directeur' : params.apprenantFonction === 'chef_service' ? 'Chef de service' : ''; const salutation = fonctionLabel ? `${fonctionLabel} ${params.apprenantPrenom} ${params.apprenantNom}` : params.apprenantPrenom; const alternativesHTML = params.alternativesDisponibles && params.alternativesDisponibles.length > 0 ? `

    📋 Séquences alternatives disponibles

      ${params.alternativesDisponibles.map(alt => `
    • ${alt.sequenceNom} - ${alt.nbPlaces} place(s) disponible(s)
    • `).join('')}

    Contactez le service RH pour vous inscrire à une autre séquence.

    ` : ''; const content = `

    Information : Annulation de séquence

    Bonjour ${salutation},

    Nous vous informons que la séquence de formation pour laquelle vous étiez en liste d'attente a été annulée.

    Séquence annulée

    Formation : ${params.formationNom}

    Séquence : ${params.sequenceNom}

    ${params.raisonAnnulation ? `

    Raison : ${params.raisonAnnulation}

    ` : ''}
    ${alternativesHTML}

    Nous nous excusons pour ce désagrément et restons à votre disposition pour toute question.

    Cordialement,
    L'équipe Formation

    `; return sendEmail({ to: params.apprenantEmail, subject: `Information : Annulation de séquence - ${params.formationNom}`, html: await getEmailTemplate(content, 'notification_annulation'), }); } /** * Envoie une notification au formateur quand la capacité maximale est atteinte */ export async function sendNotificationFormateurCapaciteAtteinte(params: { formateurEmail: string; formateurNom: string; formationNom: string; sequenceNom: string; capaciteMax: number; nbInscrits: number; dates: Array<{ dateDebut: Date; dateFin: Date; ordre: number }>; }): Promise { const datesHTML = params.dates.map(date => `
  • Date ${date.ordre} : ${date.dateDebut.toLocaleDateString('fr-FR', { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric' })}
  • `).join(''); const content = `

    ⚠️ Capacité maximale atteinte

    Bonjour ${params.formateurNom},

    La capacité maximale de votre formation a été atteinte.

    Détails de la séquence

    Formation : ${params.formationNom}

    Séquence : ${params.sequenceNom}

    Capacité : ${params.nbInscrits} / ${params.capaciteMax}

    Dates de la formation :

      ${datesHTML}

    Les nouvelles inscriptions seront automatiquement placées en liste d'attente.

    Cordialement,
    L'équipe Formation

    `; return sendEmail({ to: params.formateurEmail, subject: `⚠️ Capacité maximale atteinte - ${params.formationNom} (${params.sequenceNom})`, html: await getEmailTemplate(content, 'notification_formateur'), }); } /** * Envoie une notification au formateur quand une place se libère */ export async function sendNotificationFormateurPlaceDisponible(params: { formateurEmail: string; formateurNom: string; formationNom: string; sequenceNom: string; capaciteMax: number; nbInscrits: number; placesDisponibles: number; }): Promise { const content = `

    ✅ Place disponible

    Bonjour ${params.formateurNom},

    Une place s'est libérée dans votre formation suite à une annulation.

    Détails de la séquence

    Formation : ${params.formationNom}

    Séquence : ${params.sequenceNom}

    Places occupées : ${params.nbInscrits} / ${params.capaciteMax}

    Places disponibles : ${params.placesDisponibles}

    De nouvelles inscriptions peuvent maintenant être acceptées.

    Cordialement,
    L'équipe Formation

    `; return sendEmail({ to: params.formateurEmail, subject: `✅ Place disponible - ${params.formationNom} (${params.sequenceNom})`, html: await getEmailTemplate(content, 'notification_formateur'), }); } /** * Envoie une attestation de formation par email */ export async function sendAttestationEmail(params: { to: string; apprenantNom: string; apprenantPrenom: string; formationNom: string; pdfUrl: string; }): Promise { // Récupérer le template attestation depuis la base de données const { getDb } = await import('./db'); const { emailTemplates } = await import('../drizzle/schema'); const { eq } = await import('drizzle-orm'); const db = await getDb(); if (!db) { throw new Error('Database not available'); } const templates = await db.select().from(emailTemplates).where(eq(emailTemplates.type, 'attestation')).limit(1); if (templates.length === 0) { throw new Error('Template attestation not found'); } const template = templates[0]; // Remplacer les variables dans le template let content = template.bodyContent; content = content.replace(/{{prenomApprenant}}/g, params.apprenantPrenom); content = content.replace(/{{nomApprenant}}/g, params.apprenantNom); content = content.replace(/{{nomFormation}}/g, params.formationNom); // Récupérer le PDF (depuis URL ou chemin local) let pdfBuffer: Buffer; try { // Vérifier si c'est un chemin local ou une URL complète if (params.pdfUrl.startsWith('http://') || params.pdfUrl.startsWith('https://')) { // Télécharger depuis l'URL const response = await fetch(params.pdfUrl); if (!response.ok) { throw new Error(`Failed to download PDF: ${response.statusText}`); } const arrayBuffer = await response.arrayBuffer(); pdfBuffer = Buffer.from(arrayBuffer); } else { // Lire depuis le système de fichiers local const fs = await import('fs/promises'); const path = await import('path'); // Construire le chemin absolu (le chemin commence par /uploads/...) // Sur le VPS, les fichiers sont dans /var/www/formation-manager-itinova/uploads/ const absolutePath = path.join(process.cwd(), params.pdfUrl); pdfBuffer = await fs.readFile(absolutePath); } } catch (error) { console.error('[sendAttestationEmail] Error loading PDF:', error); throw error; } return sendEmail({ to: params.to, subject: `Votre attestation de formation - ${params.formationNom}`, html: await getEmailTemplate(content, 'attestation'), attachments: [{ filename: `Attestation_${params.apprenantNom}_${params.apprenantPrenom}.pdf`, content: pdfBuffer.toString('base64'), contentType: 'application/pdf', }], }); } /** * Alias pour sendPasswordResetEmail avec des paramètres plus simples */ export async function sendResetPasswordEmail(params: { email: string; prenom: string; nom: string; lienReinitialisation: string; }): Promise { return sendPasswordResetEmail({ apprenantEmail: params.email, apprenantNom: params.nom, apprenantPrenom: params.prenom, resetLink: params.lienReinitialisation, }); }