- Upload de fichiers avec fallback automatique vers stockage local - Configuration Nginx pour uploads jusqu'à 10 Mo - Colonne "Pièce jointe" dans le tableau de gestion des rappels - Support des fichiers locaux et S3 dans l'envoi d'emails - Correction de l'encoding base64 pour SMTP/Nodemailer - Table logsrappels créée dans la base de données MySQL du VPS
1177 lines
42 KiB
TypeScript
1177 lines
42 KiB
TypeScript
/**
|
|
* 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<boolean> {
|
|
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<string> {
|
|
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<boolean> {
|
|
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 => `
|
|
<div class="date-item">
|
|
<strong>Date ${date.ordre} :</strong><br/>
|
|
Du ${date.dateDebut.toLocaleDateString('fr-FR', {
|
|
weekday: 'long',
|
|
year: 'numeric',
|
|
month: 'long',
|
|
day: 'numeric',
|
|
hour: '2-digit',
|
|
minute: '2-digit'
|
|
})}<br/>
|
|
au ${date.dateFin.toLocaleDateString('fr-FR', {
|
|
weekday: 'long',
|
|
year: 'numeric',
|
|
month: 'long',
|
|
day: 'numeric',
|
|
hour: '2-digit',
|
|
minute: '2-digit'
|
|
})}
|
|
</div>
|
|
`).join('');
|
|
|
|
const content = `
|
|
<h2>Confirmation d'inscription</h2>
|
|
<p>Bonjour ${fonctionLabel} ${params.apprenantPrenom} ${params.apprenantNom},</p>
|
|
|
|
${isConfirmed
|
|
? '<p><strong>Votre inscription a été confirmée !</strong></p>'
|
|
: '<p><strong>Vous avez été ajouté à la liste d\'attente.</strong></p>'
|
|
}
|
|
|
|
<div class="info-box">
|
|
<h3>Détails de la formation</h3>
|
|
<p><strong>Formation :</strong> ${params.formationNom}</p>
|
|
<p><strong>Séquence :</strong> ${params.sequenceNom}</p>
|
|
<p><strong>Lieu :</strong> ${params.lieu}</p>
|
|
<h4>Dates de formation (${params.dates.length} séance${params.dates.length > 1 ? 's' : ''}) :</h4>
|
|
${datesHTML}
|
|
</div>
|
|
|
|
${isConfirmed
|
|
? `<p>Des invitations Outlook sont jointes à cet email pour chaque date de formation. Merci de les ajouter à votre calendrier pour bloquer votre agenda.</p>
|
|
<p><strong>Important :</strong> Les inscriptions seront bloquées 15 jours avant le début de la séquence. Après cette date, aucune modification ne sera possible.</p>`
|
|
: '<p>Nous vous contacterons dès qu\'une place se libère.</p>'
|
|
}
|
|
|
|
<p>À bientôt pour cette formation !</p>
|
|
`;
|
|
|
|
// 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<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> ${date.dateDebut.toLocaleDateString('fr-FR', {
|
|
weekday: 'long',
|
|
year: 'numeric',
|
|
month: 'long',
|
|
day: 'numeric'
|
|
})}
|
|
</div>
|
|
`).join('');
|
|
|
|
const content = `
|
|
<h2>Votre formation approche !</h2>
|
|
<p>Bonjour ${salutation},</p>
|
|
|
|
<p>Nous sommes ravis de vous accueillir prochainement pour la formation <strong>${params.formationNom}</strong>.</p>
|
|
|
|
<div class="info-box">
|
|
<p><strong>Séquence :</strong> ${params.sequenceNom}</p>
|
|
<h4>Dates :</h4>
|
|
${datesHTML}
|
|
${params.lieu ? `<p><strong>Lieu :</strong> ${params.lieu}</p>` : ''}
|
|
${params.formateur ? `<p><strong>Formateur :</strong> ${params.formateur}</p>` : ''}
|
|
</div>
|
|
|
|
<p>Préparez-vous à vivre une expérience enrichissante qui vous permettra de développer vos compétences managériales.</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, // 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;
|
|
}): 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 {
|
|
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 : ${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<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 dans 7 jours</h2>
|
|
<p>Bonjour ${salutation},</p>
|
|
|
|
<p>Nous vous rappelons que votre formation <strong>${params.formationNom}</strong> commence dans 7 jours.</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, // 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<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> ${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'
|
|
})}
|
|
</div>
|
|
`).join('');
|
|
|
|
const content = `
|
|
<h2>Rappel : Votre formation commence demain !</h2>
|
|
<p>Bonjour ${salutation},</p>
|
|
|
|
<p>Nous vous rappelons que votre formation <strong>${params.formationNom}</strong> commence <strong>demain</strong>.</p>
|
|
|
|
<div class="info-box">
|
|
<h3>Informations pratiques</h3>
|
|
<p><strong>Séquence :</strong> ${params.sequenceNom}</p>
|
|
${params.lieu ? `<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>N'oubliez pas d'apporter le matériel nécessaire.</p>
|
|
|
|
<p>Si vous avez des questions de dernière minute, n'hésitez pas à contacter le service RH.</p>
|
|
|
|
<p>À demain !</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, // 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<boolean> {
|
|
const content = `
|
|
<h2>Réinitialisation de mot de passe</h2>
|
|
<p>Bonjour ${params.apprenantPrenom} ${params.apprenantNom},</p>
|
|
|
|
<p>Vous avez demandé la réinitialisation de votre mot de passe pour votre compte Manager Itinova.</p>
|
|
|
|
<p>Cliquez sur le bouton ci-dessous pour créer un nouveau mot de passe :</p>
|
|
|
|
<div style="text-align: center; margin: 30px 0;">
|
|
<a href="${params.resetLink}" class="button">Réinitialiser mon mot de passe</a>
|
|
</div>
|
|
|
|
<p>Ou copiez ce lien dans votre navigateur :</p>
|
|
<div style="background-color: white; padding: 15px; border-radius: 4px; word-break: break-all; font-family: monospace; font-size: 12px; border: 1px solid #e5e7eb;">
|
|
${params.resetLink}
|
|
</div>
|
|
|
|
<div class="info-box" style="background-color: #fef3c7; border-left-color: #f59e0b;">
|
|
<p><strong>⚠️ Important :</strong></p>
|
|
<ul>
|
|
<li>Ce lien est valide pendant <strong>24 heures</strong> seulement</li>
|
|
<li>Il ne peut être utilisé qu'<strong>une seule fois</strong></li>
|
|
<li>Si vous n'avez pas demandé cette réinitialisation, ignorez cet email</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<p>Pour toute question, contactez le service RH.</p>
|
|
`;
|
|
|
|
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<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 content = `
|
|
<h2>Merci pour votre participation !</h2>
|
|
<p>Bonjour ${salutation},</p>
|
|
|
|
<p>Nous tenons à vous remercier pour votre participation à la formation <strong>${params.formationNom}</strong> (${params.sequenceNom}).</p>
|
|
|
|
${params.formateurNom ? `<p>Nous espérons que les sessions animées par <strong>${params.formateurNom}</strong> ont répondu à vos attentes.</p>` : ''}
|
|
|
|
<div class="info-box">
|
|
<h3>Votre avis compte !</h3>
|
|
<p>Afin d'améliorer continuellement nos formations, nous vous invitons à partager votre retour d'expérience.</p>
|
|
${params.lienQuestionnaire ? `
|
|
<div style="text-align: center; margin: 20px 0;">
|
|
<a href="${params.lienQuestionnaire}" class="button">Donner mon avis</a>
|
|
</div>
|
|
` : '<p>Un questionnaire de satisfaction vous sera envoyé prochainement.</p>'}
|
|
</div>
|
|
|
|
<p>Nous vous souhaitons une excellente continuation dans vos fonctions et espérons vous revoir lors de prochaines formations.</p>
|
|
|
|
<p>Cordialement,<br/>L'équipe Formation</p>
|
|
`;
|
|
|
|
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<boolean> {
|
|
const fonctionLabel = params.apprenantFonction === 'directeur' ? 'Directeur' :
|
|
params.apprenantFonction === 'chef_service' ? 'Chef de service' : 'Autre';
|
|
|
|
const datesHTML = params.dates.map(date => `
|
|
<li>Date ${date.ordre} : ${date.dateDebut.toLocaleDateString('fr-FR', {
|
|
weekday: 'long',
|
|
day: 'numeric',
|
|
month: 'long',
|
|
year: 'numeric'
|
|
})}</li>
|
|
`).join('');
|
|
|
|
const content = `
|
|
<h2>🎓 Nouvelle inscription à votre formation</h2>
|
|
<p>Bonjour ${params.formateurNom},</p>
|
|
|
|
<p>Un nouvel apprenant vient de s'inscrire à votre formation.</p>
|
|
|
|
<div class="info-box">
|
|
<h3>Détails de l'inscription</h3>
|
|
<p><strong>Apprenant :</strong> ${params.apprenantPrenom} ${params.apprenantNom}</p>
|
|
<p><strong>Fonction :</strong> ${fonctionLabel}</p>
|
|
${params.apprenantEtablissement ? `<p><strong>Établissement :</strong> ${params.apprenantEtablissement}</p>` : ''}
|
|
<p><strong>Formation :</strong> ${params.formationNom}</p>
|
|
<p><strong>Séquence :</strong> ${params.sequenceNom}</p>
|
|
</div>
|
|
|
|
<div class="info-box" style="background-color: #dbeafe; border-left-color: #3b82f6;">
|
|
<h3>📊 État des inscriptions</h3>
|
|
<p><strong>${params.nbInscrits} / ${params.capaciteMax}</strong> places occupées</p>
|
|
<div style="background-color: #e5e7eb; border-radius: 4px; height: 20px; margin: 10px 0;">
|
|
<div style="background-color: #3b82f6; border-radius: 4px; height: 20px; width: ${Math.min(100, (params.nbInscrits / params.capaciteMax) * 100)}%;"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<h4>Dates de la formation :</h4>
|
|
<ul>${datesHTML}</ul>
|
|
|
|
<p>Cordialement,<br/>L'équipe Formation</p>
|
|
`;
|
|
|
|
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<boolean> {
|
|
const fonctionLabel = params.apprenantFonction === 'directeur' ? 'Directeur' :
|
|
params.apprenantFonction === 'chef_service' ? 'Chef de service' : 'Autre';
|
|
|
|
const content = `
|
|
<h2>⚠️ Annulation d'inscription</h2>
|
|
<p>Bonjour ${params.formateurNom},</p>
|
|
|
|
<p>Un apprenant a annulé son inscription à votre formation.</p>
|
|
|
|
<div class="info-box" style="background-color: #fef3c7; border-left-color: #f59e0b;">
|
|
<h3>Détails de l'annulation</h3>
|
|
<p><strong>Apprenant :</strong> ${params.apprenantPrenom} ${params.apprenantNom}</p>
|
|
<p><strong>Fonction :</strong> ${fonctionLabel}</p>
|
|
<p><strong>Formation :</strong> ${params.formationNom}</p>
|
|
<p><strong>Séquence :</strong> ${params.sequenceNom}</p>
|
|
${params.raisonAnnulation ? `<p><strong>Raison :</strong> ${params.raisonAnnulation}</p>` : ''}
|
|
</div>
|
|
|
|
<div class="info-box" style="background-color: #dbeafe; border-left-color: #3b82f6;">
|
|
<h3>📊 État des inscriptions après annulation</h3>
|
|
<p><strong>${params.nbInscrits} / ${params.capaciteMax}</strong> places occupées</p>
|
|
<p><strong>${params.capaciteMax - params.nbInscrits}</strong> place(s) disponible(s)</p>
|
|
</div>
|
|
|
|
<p>Cordialement,<br/>L'équipe Formation</p>
|
|
`;
|
|
|
|
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 => `
|
|
<li>${date.dateDebut.toLocaleDateString('fr-FR', {
|
|
weekday: 'long',
|
|
day: 'numeric',
|
|
month: 'long',
|
|
year: 'numeric'
|
|
})}</li>
|
|
`).join('');
|
|
|
|
const content = `
|
|
<h2>🔔 Alerte : Capacité maximale atteinte</h2>
|
|
|
|
<div class="info-box" style="background-color: #fee2e2; border-left-color: #ef4444;">
|
|
<h3>⚠️ La séquence est complète</h3>
|
|
<p>La capacité maximale de la séquence <strong>${params.sequenceNom}</strong> a été atteinte.</p>
|
|
</div>
|
|
|
|
<div class="info-box">
|
|
<h3>Détails de la séquence</h3>
|
|
<p><strong>Formation :</strong> ${params.formationNom}</p>
|
|
<p><strong>Séquence :</strong> ${params.sequenceNom}</p>
|
|
${params.formateurNom ? `<p><strong>Formateur :</strong> ${params.formateurNom}</p>` : ''}
|
|
<p><strong>Capacité :</strong> ${params.nbInscrits} / ${params.capaciteMax} (100%)</p>
|
|
${params.nbListeAttente > 0 ? `<p><strong>Liste d'attente :</strong> ${params.nbListeAttente} personne(s)</p>` : ''}
|
|
</div>
|
|
|
|
<h4>Dates prévues :</h4>
|
|
<ul>${datesHTML}</ul>
|
|
|
|
<div class="info-box" style="background-color: #dbeafe; border-left-color: #3b82f6;">
|
|
<h3>Actions possibles</h3>
|
|
<ul>
|
|
<li>Augmenter la capacité maximale de la séquence</li>
|
|
<li>Créer une nouvelle séquence pour cette formation</li>
|
|
<li>Contacter les personnes en liste d'attente pour les informer</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<p>Cordialement,<br/>Le système de gestion des formations</p>
|
|
`;
|
|
|
|
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<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 delai = params.delaiReponse || 48;
|
|
|
|
const content = `
|
|
<h2>🎉 Une place s'est libérée !</h2>
|
|
<p>Bonjour ${salutation},</p>
|
|
|
|
<p>Bonne nouvelle ! Une place vient de se libérer pour la formation à laquelle vous êtes inscrit(e) en liste d'attente.</p>
|
|
|
|
<div class="info-box" style="background-color: #d1fae5; border-left-color: #10b981;">
|
|
<h3>✅ Vous pouvez maintenant confirmer votre inscription</h3>
|
|
<p><strong>Formation :</strong> ${params.formationNom}</p>
|
|
<p><strong>Séquence :</strong> ${params.sequenceNom}</p>
|
|
<p><strong>Votre position :</strong> ${params.positionListeAttente}${params.positionListeAttente === 1 ? 'er' : 'ème'} sur la liste d'attente</p>
|
|
</div>
|
|
|
|
${params.lienConfirmation ? `
|
|
<div style="text-align: center; margin: 30px 0;">
|
|
<a href="${params.lienConfirmation}" class="button">Confirmer mon inscription</a>
|
|
</div>
|
|
` : '<p>Veuillez contacter le service RH pour confirmer votre inscription.</p>'}
|
|
|
|
<div class="info-box" style="background-color: #fef3c7; border-left-color: #f59e0b;">
|
|
<h3>⏰ Important</h3>
|
|
<p>Vous avez <strong>${delai} heures</strong> pour confirmer votre inscription.</p>
|
|
<p>Passé ce délai, la place sera proposée à la personne suivante sur la liste d'attente.</p>
|
|
</div>
|
|
|
|
<p>Cordialement,<br/>L'équipe Formation</p>
|
|
`;
|
|
|
|
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<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 alternativesHTML = params.alternativesDisponibles && params.alternativesDisponibles.length > 0
|
|
? `
|
|
<div class="info-box" style="background-color: #dbeafe; border-left-color: #3b82f6;">
|
|
<h3>📋 Séquences alternatives disponibles</h3>
|
|
<ul>
|
|
${params.alternativesDisponibles.map(alt => `
|
|
<li><strong>${alt.sequenceNom}</strong> - ${alt.nbPlaces} place(s) disponible(s)</li>
|
|
`).join('')}
|
|
</ul>
|
|
<p>Contactez le service RH pour vous inscrire à une autre séquence.</p>
|
|
</div>
|
|
` : '';
|
|
|
|
const content = `
|
|
<h2>Information : Annulation de séquence</h2>
|
|
<p>Bonjour ${salutation},</p>
|
|
|
|
<p>Nous vous informons que la séquence de formation pour laquelle vous étiez en liste d'attente a été annulée.</p>
|
|
|
|
<div class="info-box" style="background-color: #fee2e2; border-left-color: #ef4444;">
|
|
<h3>Séquence annulée</h3>
|
|
<p><strong>Formation :</strong> ${params.formationNom}</p>
|
|
<p><strong>Séquence :</strong> ${params.sequenceNom}</p>
|
|
${params.raisonAnnulation ? `<p><strong>Raison :</strong> ${params.raisonAnnulation}</p>` : ''}
|
|
</div>
|
|
|
|
${alternativesHTML}
|
|
|
|
<p>Nous nous excusons pour ce désagrément et restons à votre disposition pour toute question.</p>
|
|
|
|
<p>Cordialement,<br/>L'équipe Formation</p>
|
|
`;
|
|
|
|
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<boolean> {
|
|
const datesHTML = params.dates.map(date => `
|
|
<li>Date ${date.ordre} : ${date.dateDebut.toLocaleDateString('fr-FR', {
|
|
weekday: 'long',
|
|
day: 'numeric',
|
|
month: 'long',
|
|
year: 'numeric'
|
|
})}</li>
|
|
`).join('');
|
|
|
|
const content = `
|
|
<h2>⚠️ Capacité maximale atteinte</h2>
|
|
<p>Bonjour ${params.formateurNom},</p>
|
|
|
|
<p>La capacité maximale de votre formation a été atteinte.</p>
|
|
|
|
<div class="info-box" style="background-color: #fee2e2; border-left-color: #ef4444;">
|
|
<h3>Détails de la séquence</h3>
|
|
<p><strong>Formation :</strong> ${params.formationNom}</p>
|
|
<p><strong>Séquence :</strong> ${params.sequenceNom}</p>
|
|
<p><strong>Capacité :</strong> ${params.nbInscrits} / ${params.capaciteMax}</p>
|
|
</div>
|
|
|
|
<h4>Dates de la formation :</h4>
|
|
<ul>${datesHTML}</ul>
|
|
|
|
<p>Les nouvelles inscriptions seront automatiquement placées en liste d'attente.</p>
|
|
|
|
<p>Cordialement,<br/>L'équipe Formation</p>
|
|
`;
|
|
|
|
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<boolean> {
|
|
const content = `
|
|
<h2>✅ Place disponible</h2>
|
|
<p>Bonjour ${params.formateurNom},</p>
|
|
|
|
<p>Une place s'est libérée dans votre formation suite à une annulation.</p>
|
|
|
|
<div class="info-box" style="background-color: #d1fae5; border-left-color: #10b981;">
|
|
<h3>Détails de la séquence</h3>
|
|
<p><strong>Formation :</strong> ${params.formationNom}</p>
|
|
<p><strong>Séquence :</strong> ${params.sequenceNom}</p>
|
|
<p><strong>Places occupées :</strong> ${params.nbInscrits} / ${params.capaciteMax}</p>
|
|
<p><strong>Places disponibles :</strong> ${params.placesDisponibles}</p>
|
|
</div>
|
|
|
|
<p>De nouvelles inscriptions peuvent maintenant être acceptées.</p>
|
|
|
|
<p>Cordialement,<br/>L'équipe Formation</p>
|
|
`;
|
|
|
|
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<boolean> {
|
|
const content = `
|
|
<h2>Votre attestation de formation</h2>
|
|
|
|
<p>Bonjour ${params.apprenantPrenom} ${params.apprenantNom},</p>
|
|
|
|
<p>Nous avons le plaisir de vous transmettre votre attestation de formation pour :</p>
|
|
|
|
<div class="info-box" style="background-color: #dbeafe; border-left-color: #3b82f6;">
|
|
<h3>${params.formationNom}</h3>
|
|
</div>
|
|
|
|
<p>Vous pouvez télécharger votre attestation en cliquant sur le lien ci-dessous :</p>
|
|
|
|
<div style="text-align: center; margin: 30px 0;">
|
|
<a href="${params.pdfUrl}" style="display: inline-block; padding: 12px 30px; background-color: #3b82f6; color: white; text-decoration: none; border-radius: 5px; font-weight: bold;">
|
|
📄 Télécharger mon attestation
|
|
</a>
|
|
</div>
|
|
|
|
<p>Cette attestation certifie votre participation à la formation et peut être utilisée pour votre dossier professionnel.</p>
|
|
|
|
<p>Nous vous remercions pour votre participation et vous souhaitons une excellente continuation.</p>
|
|
|
|
<p>Cordialement,<br/>L'équipe Formation</p>
|
|
`;
|
|
|
|
return sendEmail({
|
|
to: params.to,
|
|
subject: `Votre attestation de formation - ${params.formationNom}`,
|
|
html: await getEmailTemplate(content, 'attestation'),
|
|
});
|
|
}
|
|
|
|
|
|
/**
|
|
* Alias pour sendPasswordResetEmail avec des paramètres plus simples
|
|
*/
|
|
export async function sendResetPasswordEmail(params: {
|
|
email: string;
|
|
prenom: string;
|
|
nom: string;
|
|
lienReinitialisation: string;
|
|
}): Promise<boolean> {
|
|
return sendPasswordResetEmail({
|
|
apprenantEmail: params.email,
|
|
apprenantNom: params.nom,
|
|
apprenantPrenom: params.prenom,
|
|
resetLink: params.lienReinitialisation,
|
|
});
|
|
}
|