424 lines
No EOL
14 KiB
TypeScript
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;
|
|
}
|
|
} |