espace-paie-odentas/lib/emailService.ts

424 lines
No EOL
14 KiB
TypeScript

// lib/emailService.ts
import { SESClient, SendEmailCommand } from "@aws-sdk/client-ses";
import { readFileSync } from "fs";
import { join } from "path";
interface EmailOptions {
toEmail: string;
ccEmail?: string;
subject: string;
templateName: string;
templateData: Record<string, string>;
fromEmail?: string;
fromName?: string;
}
export async function sendEmail({
toEmail,
ccEmail,
subject,
templateName,
templateData,
fromEmail,
fromName
}: EmailOptions) {
const region = process.env.AWS_REGION || "eu-west-3";
const from = fromEmail || process.env.AWS_SES_FROM;
if (!from) {
throw new Error("Missing AWS_SES_FROM environment variable");
}
try {
// Lire le template d'email
const templatePath = join(process.cwd(), 'templates-mails', `${templateName}.html`);
let html = readFileSync(templatePath, 'utf8');
// Remplacer les variables dans le template
Object.entries(templateData).forEach(([key, value]) => {
const regex = new RegExp(`{{${key}}}`, 'g');
html = html.replace(regex, value || '—');
});
// Créer le texte brut simple
const textContent = Object.entries(templateData)
.map(([key, value]) => `${key}: ${value}`)
.join('\n');
const ses = new SESClient({ region });
// Nettoyer les emails pour supprimer tout espace ou caractère de contrôle
const cleanToEmail = toEmail.replace(/\s+/g, '').trim();
const cleanCcEmail = ccEmail ? ccEmail.replace(/\s+/g, '').trim() : undefined;
const destination: any = { ToAddresses: [cleanToEmail] };
// Ne pas ajouter CcAddresses si ccEmail est vide ou invalide
if (cleanCcEmail && cleanCcEmail.length > 0 && cleanCcEmail.includes('@')) {
destination.CcAddresses = [cleanCcEmail];
}
// Construire le champ Source avec validation
let sourceField: string;
if (fromName) {
// Si from contient déjà des chevrons, on les retire d'abord
const cleanFrom = from.replace(/[<>\s]/g, '').trim();
sourceField = `${fromName} <${cleanFrom}>`;
} else {
sourceField = from.replace(/\s+/g, '').trim();
}
console.log('[sendEmail] Source field:', sourceField);
console.log('[sendEmail] To:', cleanToEmail);
console.log('[sendEmail] CC:', cleanCcEmail || 'none');
const cmd = new SendEmailCommand({
Destination: destination,
Source: sourceField,
Message: {
Subject: { Data: subject, Charset: "UTF-8" },
Body: {
Html: { Data: html, Charset: "UTF-8" },
Text: { Data: textContent, Charset: "UTF-8" },
},
},
});
await ses.send(cmd);
console.log(`Email sent successfully to ${toEmail}${ccEmail && ccEmail.trim() ? ` (CC: ${ccEmail})` : ''}`);
} catch (error) {
console.error('Error sending email:', error);
throw error;
}
}
export async function sendContractNotifications(
contractData: any,
organizationData: any
) {
try {
const emailNotifs = organizationData.organization_details?.email_notifs;
const emailNotifsCC = organizationData.organization_details?.email_notifs_cc;
// Validation des emails
const isValidEmail = (email: string) => {
return email && email.includes('@') && email.includes('.') && email.trim().length > 0;
};
// Use the new universal email system
if (emailNotifs && isValidEmail(emailNotifs)) {
const { sendUniversalEmailV2 } = await import('./emailTemplateService');
// Valider l'email CC s'il existe
const validatedCcEmail = (emailNotifsCC && isValidEmail(emailNotifsCC)) ? emailNotifsCC : undefined;
await sendUniversalEmailV2({
type: 'contract-created',
toEmail: emailNotifs,
ccEmail: validatedCcEmail,
subject: `Nouveau CDDU créé - ${contractData.employee_name}`,
data: {
firstName: organizationData.organization_details?.prenom_contact || "",
employeeName: contractData.employee_name,
contractReference: contractData.contract_number || contractData.reference,
startDate: formatDateFR(contractData.start_date),
profession: contractData.profession || contractData.role,
organizationName: organizationData.name || contractData.structure,
// Ajouts pour la carte d'information
companyName: organizationData.name || contractData.structure,
employerCode: organizationData.organization_details?.code_employeur || "",
userName: organizationData.organization_details?.prenom_contact || "cher client",
ctaUrl: `${process.env.NEXT_PUBLIC_BASE_URL || 'https://paie.odentas.fr'}/contrats/${contractData.id}`,
customMessage: `Un nouveau contrat CDDU a été créé pour ${contractData.employee_name}.`
}
});
} else {
console.warn('No valid notification email configured for organization:', {
orgId: organizationData.id,
orgName: organizationData.name,
emailNotifs,
emailNotifsCC
});
}
console.log('Contract notifications sent successfully (universal system)');
} catch (error) {
console.error('Error sending contract notifications:', error);
}
}
export async function sendContractUpdateNotifications(
contractData: any,
organizationData: any
) {
try {
console.log('🔍 [EMAIL DEBUG] sendContractUpdateNotifications called with:', {
contractId: contractData?.id,
orgId: organizationData?.id,
orgName: organizationData?.name,
hasOrgDetails: !!organizationData?.organization_details,
orgDetails: organizationData?.organization_details
});
// Fix: Si organization_details est un tableau, prendre le premier élément
let orgDetails = organizationData?.organization_details;
if (Array.isArray(orgDetails)) {
console.log('🔍 [EMAIL DEBUG] organization_details is array, taking first element');
orgDetails = orgDetails[0] || {};
}
const emailNotifs = orgDetails?.email_notifs;
const emailNotifsCC = orgDetails?.email_notifs_cc;
console.log('🔍 [EMAIL DEBUG] Email addresses found:', {
emailNotifs,
emailNotifsCC,
emailNotifsType: typeof emailNotifs,
emailNotifsCCType: typeof emailNotifsCC
});
// Use the new universal email system
if (emailNotifs) {
// Validation des emails
const isValidEmail = (email: string) => {
return email && email.includes('@') && email.includes('.') && email.trim().length > 0;
};
if (isValidEmail(emailNotifs)) {
const { sendUniversalEmailV2 } = await import('./emailTemplateService');
// Valider l'email CC s'il existe
const validatedCcEmail = (emailNotifsCC && isValidEmail(emailNotifsCC)) ? emailNotifsCC : undefined;
console.log('🔍 [EMAIL DEBUG] Sending email with validated addresses:', {
toEmail: emailNotifs,
ccEmail: validatedCcEmail
});
await sendUniversalEmailV2({
type: 'contract-updated',
toEmail: emailNotifs,
ccEmail: validatedCcEmail,
subject: `CDDU modifié - ${contractData.employee_name}`,
data: {
firstName: orgDetails?.prenom_contact || "",
employeeName: contractData.employee_name,
contractReference: contractData.contract_number || contractData.reference,
startDate: formatDateFR(contractData.start_date),
profession: contractData.profession || contractData.role,
organizationName: organizationData.name || contractData.structure,
// Ajouts pour la carte d'information
companyName: organizationData.name || contractData.structure,
employerCode: orgDetails?.code_employeur || "",
userName: orgDetails?.prenom_contact || "cher client",
ctaUrl: `${process.env.NEXT_PUBLIC_BASE_URL || 'https://paie.odentas.fr'}/contrats/${contractData.id}`,
customMessage: `Votre contrat CDDU pour ${contractData.employee_name} a été modifié.`
}
});
} else {
console.warn('Invalid notification email for contract update:', {
orgId: organizationData.id,
orgName: organizationData.name,
emailNotifs,
emailNotifsCC
});
}
} else {
console.warn('No notification email configured for contract update:', {
orgId: organizationData.id,
orgName: organizationData.name,
hasOrgDetails: !!orgDetails,
orgDetails
});
}
console.log('Contract update notifications sent successfully (universal system)');
} catch (error) {
console.error('Error sending contract update notifications:', error);
}
}
export async function sendContractCancellationNotifications(
contractData: any,
organizationData: any
) {
try {
const emailNotifs = organizationData.organization_details?.email_notifs;
const emailNotifsCC = organizationData.organization_details?.email_notifs_cc;
// Use the new universal email system
if (emailNotifs) {
// Validation des emails
const isValidEmail = (email: string) => {
return email && email.includes('@') && email.includes('.') && email.trim().length > 0;
};
if (isValidEmail(emailNotifs)) {
const { sendUniversalEmailV2 } = await import('./emailTemplateService');
// Valider l'email CC s'il existe
const validatedCcEmail = (emailNotifsCC && isValidEmail(emailNotifsCC)) ? emailNotifsCC : undefined;
await sendUniversalEmailV2({
type: 'contract-cancelled',
toEmail: emailNotifs,
ccEmail: validatedCcEmail,
subject: `CDDU annulé - ${contractData.employee_name}`,
data: {
firstName: organizationData.organization_details?.prenom_contact || "",
employeeName: contractData.employee_name,
contractReference: contractData.contract_number || contractData.reference,
startDate: formatDateFR(contractData.start_date),
profession: contractData.profession || contractData.role,
organizationName: organizationData.name || contractData.structure,
productionName: contractData.production_name || contractData.analytique || "Production principale",
companyName: organizationData.name || contractData.structure,
employerCode: organizationData.organization_details?.code_employeur || contractData.employer_code || "",
userName: organizationData.organization_details?.prenom_contact || "cher client",
ctaUrl: `${process.env.NEXT_PUBLIC_BASE_URL || 'https://paie.odentas.fr'}/contrats`,
supportUrl: 'mailto:paie@odentas.fr',
customMessage: `Votre contrat CDDU pour ${contractData.employee_name} a été annulé.`
}
});
} else {
console.warn('Invalid notification email for contract cancellation:', {
orgId: organizationData.id,
orgName: organizationData.name,
emailNotifs,
emailNotifsCC
});
}
}
console.log('Contract cancellation notifications sent successfully (universal system)');
} catch (error) {
console.error('Error sending contract cancellation notifications:', error);
}
}
function formatDateFR(dateStr: string | null): string {
if (!dateStr) return "—";
try {
return new Date(dateStr).toLocaleDateString("fr-FR", {
day: "2-digit",
month: "2-digit",
year: "numeric"
});
} catch {
return dateStr;
}
}
// Fonction pour envoyer une invitation
export async function sendInviteEmail({
userEmail,
inviteToken,
organizationName,
fromEmail = 'paie@odentas.fr',
fromName = 'Odentas Paie'
}: {
userEmail: string;
inviteToken: string;
organizationName: string;
fromEmail?: string;
fromName?: string;
}) {
try {
const activationUrl = `${process.env.NEXT_PUBLIC_BASE_URL || 'https://paie.odentas.fr'}/activate?token=${inviteToken}`;
const templateData = {
activationUrl,
organizationName,
supportUrl: 'mailto:paie@odentas.fr'
};
await sendEmail({
toEmail: userEmail,
subject: `Invitation à rejoindre ${organizationName} sur Odentas Paie`,
templateName: 'invite',
templateData,
fromEmail,
fromName
});
console.log('Invitation sent successfully to:', userEmail);
} catch (error) {
console.error('Error sending invitation:', error);
throw error;
}
}
// Fonction pour envoyer une notification de facture
export async function sendInvoiceNotification({
userEmail,
invoiceData,
fromEmail = 'paie@odentas.fr',
fromName = 'Odentas Paie'
}: {
userEmail: string;
invoiceData: any;
fromEmail?: string;
fromName?: string;
}) {
try {
const templateData = {
firstName: invoiceData.firstName || "",
invoiceNumber: invoiceData.invoiceNumber,
amount: invoiceData.amount,
dueDate: invoiceData.dueDate,
organizationName: invoiceData.organizationName,
ctaUrl: `${process.env.NEXT_PUBLIC_BASE_URL || 'https://paie.odentas.fr'}/facturation`,
supportUrl: 'mailto:paie@odentas.fr'
};
await sendEmail({
toEmail: userEmail,
subject: `Nouvelle facture - ${invoiceData.invoiceNumber}`,
templateName: 'facture',
templateData,
fromEmail,
fromName
});
console.log('Invoice notification sent successfully to:', userEmail);
} catch (error) {
console.error('Error sending invoice notification:', error);
throw error;
}
}
// Fonction pour envoyer une demande de signature électronique
export async function sendSignatureRequest({
userEmail,
documentData,
fromEmail = 'paie@odentas.fr',
fromName = 'Odentas Paie'
}: {
userEmail: string;
documentData: any;
fromEmail?: string;
fromName?: string;
}) {
try {
const templateData = {
firstName: documentData.firstName || "",
documentTitle: documentData.documentTitle,
organizationName: documentData.organizationName,
ctaUrl: documentData.signatureUrl,
supportUrl: 'mailto:paie@odentas.fr'
};
await sendEmail({
toEmail: userEmail,
subject: `Signature électronique requise - ${documentData.documentTitle}`,
templateName: 'signature-electronique',
templateData,
fromEmail,
fromName
});
console.log('Signature request sent successfully to:', userEmail);
} catch (error) {
console.error('Error sending signature request:', error);
throw error;
}
}