313 lines
10 KiB
TypeScript
313 lines
10 KiB
TypeScript
import { requireAuth } from '~/server/utils/auth';
|
|
import { getDocumesoDocument, checkDocumentSignatureStatus, formatRecipientName } from '~/server/utils/documeso';
|
|
import { getInterestById } from '~/server/utils/nocodb';
|
|
import { sendEmail } from '~/server/utils/email';
|
|
|
|
interface ReminderEmail {
|
|
to: string;
|
|
subject: string;
|
|
html: string;
|
|
}
|
|
|
|
export default defineEventHandler(async (event) => {
|
|
// Check authentication (x-tag header OR Keycloak session)
|
|
await requireAuth(event);
|
|
|
|
try {
|
|
const body = await readBody(event);
|
|
const { interestId, documentId } = body;
|
|
|
|
if (!interestId || !documentId) {
|
|
throw createError({
|
|
statusCode: 400,
|
|
statusMessage: 'Interest ID and Document ID are required',
|
|
});
|
|
}
|
|
|
|
// Get interest details
|
|
const interest = await getInterestById(interestId);
|
|
if (!interest) {
|
|
throw createError({
|
|
statusCode: 404,
|
|
statusMessage: 'Interest not found',
|
|
});
|
|
}
|
|
|
|
// Check if reminders are enabled for this interest
|
|
// For now, we'll assume they're always enabled unless explicitly disabled
|
|
const remindersEnabled = (interest as any)['reminder_enabled'] !== false;
|
|
|
|
if (!remindersEnabled) {
|
|
return {
|
|
success: false,
|
|
message: 'Reminders are disabled for this interest'
|
|
};
|
|
}
|
|
|
|
// Get document and check signature status
|
|
const document = await getDocumesoDocument(parseInt(documentId));
|
|
const status = await checkDocumentSignatureStatus(parseInt(documentId));
|
|
|
|
const emailsToSend: ReminderEmail[] = [];
|
|
const currentHour = new Date().getHours();
|
|
|
|
// Determine if we should send reminders based on time
|
|
const shouldSendMorningReminder = currentHour === 9;
|
|
const shouldSendAfternoonReminder = currentHour === 16;
|
|
|
|
if (!shouldSendMorningReminder && !shouldSendAfternoonReminder) {
|
|
return {
|
|
success: false,
|
|
message: 'Reminders are only sent at 9am and 4pm'
|
|
};
|
|
}
|
|
|
|
// If client hasn't signed, send reminder to sales (4pm only)
|
|
if (!status.clientSigned && shouldSendAfternoonReminder) {
|
|
const salesEmail = generateSalesReminderEmail(interest, document);
|
|
emailsToSend.push(salesEmail);
|
|
}
|
|
|
|
// If client has signed but others haven't, send reminders to them
|
|
if (status.clientSigned && !status.allSigned) {
|
|
for (const recipient of status.unsignedRecipients) {
|
|
if (recipient.signingOrder > 1) { // Skip client
|
|
const reminderEmail = generateRecipientReminderEmail(
|
|
recipient,
|
|
interest['Full Name'] || 'Client',
|
|
recipient.signingUrl
|
|
);
|
|
emailsToSend.push(reminderEmail);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Send all emails
|
|
const results = [];
|
|
for (const email of emailsToSend) {
|
|
try {
|
|
await sendReminderEmail(email);
|
|
results.push({
|
|
to: email.to,
|
|
success: true
|
|
});
|
|
} catch (error) {
|
|
console.error(`Failed to send reminder to ${email.to}:`, error);
|
|
results.push({
|
|
to: email.to,
|
|
success: false,
|
|
error: error instanceof Error ? error.message : String(error)
|
|
});
|
|
}
|
|
}
|
|
|
|
// Update last reminder sent timestamp
|
|
await updateInterest(interestId, {
|
|
'last_reminder_sent': new Date().toISOString()
|
|
} as any);
|
|
|
|
return {
|
|
success: true,
|
|
remindersSent: results.length,
|
|
results
|
|
};
|
|
} catch (error: any) {
|
|
console.error('Failed to send reminders:', error);
|
|
throw createError({
|
|
statusCode: error.statusCode || 500,
|
|
statusMessage: error.statusMessage || 'Failed to send reminders',
|
|
});
|
|
}
|
|
});
|
|
|
|
function generateRecipientReminderEmail(
|
|
recipient: any,
|
|
clientName: string,
|
|
signUrl: string
|
|
): ReminderEmail {
|
|
const recipientFirst = formatRecipientName(recipient);
|
|
const clientFormatted = clientName;
|
|
|
|
const html = `<html>
|
|
<head>
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
<!--[if !mso]><!--><meta http-equiv="X-UA-Compatible" content="IE=edge" /><!--<![endif]-->
|
|
<title>Port Nimara EOI Signature Request</title>
|
|
<style type="text/css">
|
|
table, td { mso-table-lspace:0pt; mso-table-rspace:0pt; }
|
|
img { border:0; display:block; }
|
|
p { margin:0; padding:0; }
|
|
</style>
|
|
</head>
|
|
<body style="margin:0; padding:0; background-color:#f2f2f2;">
|
|
<!--[if gte mso 9]>
|
|
<v:background xmlns:v="urn:schemas-microsoft-com:vml" fill="true">
|
|
<v:fill type="frame" src="https://s3.portnimara.com/images/Overhead_1_blur.png" color="#f2f2f2" />
|
|
</v:background>
|
|
<![endif]-->
|
|
|
|
<table role="presentation" width="100%" border="0" cellspacing="0" cellpadding="0"
|
|
style="background-image:url('https://s3.portnimara.com/images/Overhead_1_blur.png');
|
|
background-size:cover;
|
|
background-position:center;
|
|
background-color:#f2f2f2;">
|
|
<tr>
|
|
<td align="center" style="padding:30px;">
|
|
|
|
<table role="presentation" width="600" border="0" cellspacing="0" cellpadding="0"
|
|
style="background-color:#ffffff;
|
|
border-radius:8px;
|
|
overflow:hidden;
|
|
box-shadow:0 2px 4px rgba(0,0,0,0.1);">
|
|
<tr>
|
|
<td style="padding:20px; font-family:Arial, sans-serif; color:#333333;">
|
|
<!-- logo -->
|
|
<center>
|
|
<img
|
|
src="https://s3.portnimara.com/images/Port%20Nimara%20New%20Logo-Circular%20Frame_250px.png"
|
|
alt="Port Nimara Logo"
|
|
width="100"
|
|
style="margin-bottom:20px;"
|
|
/>
|
|
</center>
|
|
|
|
<!-- greeting & body -->
|
|
<p style="margin-bottom:10px; font-size:16px;">
|
|
Dear <strong>${recipientFirst}</strong>,
|
|
</p>
|
|
<p style="margin-bottom:20px; font-size:16px;">
|
|
There is an EOI from <strong>${clientFormatted}</strong> waiting to be signed.
|
|
Please click the button below to review and sign the document.
|
|
If you need any assistance, please reach out to the sales team.
|
|
</p>
|
|
|
|
<!-- CTA button -->
|
|
<p style="text-align:center; margin:30px 0;">
|
|
<a href="${signUrl}"
|
|
style="display:inline-block;
|
|
background-color:#007bff;
|
|
color:#ffffff;
|
|
text-decoration:none;
|
|
padding:10px 20px;
|
|
border-radius:5px;
|
|
font-weight:bold;">
|
|
Sign Your EOI
|
|
</a>
|
|
</p>
|
|
|
|
<!-- closing -->
|
|
<p style="font-size:16px;">
|
|
Thank you,<br/>
|
|
- The Port Nimara CRM
|
|
</p>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
</body>
|
|
</html>`;
|
|
|
|
// For testing, send to matt@portnimara.com
|
|
return {
|
|
to: 'matt@portnimara.com', // TODO: Change to recipient.email after testing
|
|
subject: `EOI Signature Reminder - ${clientName}`,
|
|
html
|
|
};
|
|
}
|
|
|
|
function generateSalesReminderEmail(interest: any, document: any): ReminderEmail {
|
|
const clientName = interest['Full Name'] || 'Client';
|
|
|
|
const html = `<html>
|
|
<head>
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
<!--[if !mso]><!--><meta http-equiv="X-UA-Compatible" content="IE=edge" /><!--<![endif]-->
|
|
<title>Port Nimara EOI Signature Reminder</title>
|
|
<style type="text/css">
|
|
table, td { mso-table-lspace:0pt; mso-table-rspace:0pt; }
|
|
img { border:0; display:block; }
|
|
p { margin:0; padding:0; }
|
|
</style>
|
|
</head>
|
|
<body style="margin:0; padding:0; background-color:#f2f2f2;">
|
|
<table role="presentation" width="100%" border="0" cellspacing="0" cellpadding="0"
|
|
style="background-image:url('https://s3.portnimara.com/images/Overhead_1_blur.png');
|
|
background-size:cover;
|
|
background-position:center;
|
|
background-color:#f2f2f2;">
|
|
<tr>
|
|
<td align="center" style="padding:30px;">
|
|
|
|
<table role="presentation" width="600" border="0" cellspacing="0" cellpadding="0"
|
|
style="background-color:#ffffff;
|
|
border-radius:8px;
|
|
overflow:hidden;
|
|
box-shadow:0 2px 4px rgba(0,0,0,0.1);">
|
|
<tr>
|
|
<td style="padding:20px; font-family:Arial, sans-serif; color:#333333;">
|
|
<!-- logo -->
|
|
<center>
|
|
<img
|
|
src="https://s3.portnimara.com/images/Port%20Nimara%20New%20Logo-Circular%20Frame_250px.png"
|
|
alt="Port Nimara Logo"
|
|
width="100"
|
|
style="margin-bottom:20px;"
|
|
/>
|
|
</center>
|
|
|
|
<!-- greeting & body -->
|
|
<p style="margin-bottom:10px; font-size:16px;">
|
|
Dear Sales Team,
|
|
</p>
|
|
<p style="margin-bottom:20px; font-size:16px;">
|
|
The EOI for <strong>${clientName}</strong> has not been signed by the client yet.
|
|
Please follow up with them to ensure the document is signed.
|
|
Document: ${document.title}
|
|
</p>
|
|
|
|
<!-- closing -->
|
|
<p style="font-size:16px;">
|
|
Thank you,<br/>
|
|
- The Port Nimara CRM
|
|
</p>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
</body>
|
|
</html>`;
|
|
|
|
return {
|
|
to: 'sales@portnimara.com',
|
|
subject: `Action Required: EOI Not Signed - ${clientName}`,
|
|
html
|
|
};
|
|
}
|
|
|
|
async function sendReminderEmail(email: ReminderEmail) {
|
|
// Use noreply@portnimara.com credentials with correct mail server
|
|
const credentials = {
|
|
host: 'mail.portnimara.com',
|
|
port: 465,
|
|
secure: true,
|
|
auth: {
|
|
user: 'noreply@portnimara.com',
|
|
pass: 'sJw6GW5G5bCI1EtBIq3J2hVm8xCOMw1kQs1puS6g0yABqkrwj'
|
|
}
|
|
};
|
|
|
|
// Send email using the existing email utility
|
|
await sendEmail({
|
|
from: 'Port Nimara CRM <noreply@portnimara.com>',
|
|
to: email.to,
|
|
subject: email.subject,
|
|
html: email.html
|
|
}, credentials);
|
|
}
|