port-nimara-client-portal/server/api/email/generate-eoi-document.ts

312 lines
9.9 KiB
TypeScript

import { getInterestById, updateInterest } from '~/server/utils/nocodb';
interface DocumensoRecipient {
id: number;
name: string;
email: string;
role: 'SIGNER' | 'APPROVER';
signingOrder: number;
signingUrl?: string;
}
interface DocumensoResponse {
documentId: number;
recipients: Array<{
recipientId: number;
name: string;
email: string;
token: string;
role: 'SIGNER' | 'APPROVER';
signingOrder: number;
signingUrl: string;
}>;
}
export default defineEventHandler(async (event) => {
const xTagHeader = getRequestHeader(event, "x-tag");
if (!xTagHeader || (xTagHeader !== "094ut234" && xTagHeader !== "pjnvü1230")) {
throw createError({ statusCode: 401, statusMessage: "unauthenticated" });
}
try {
const body = await readBody(event);
const { interestId } = body;
if (!interestId) {
throw createError({ statusCode: 400, statusMessage: "Interest ID is required" });
}
// Get the interest data
const interest = await getInterestById(interestId);
if (!interest) {
throw createError({ statusCode: 404, statusMessage: "Interest not found" });
}
// Check if EOI already exists (has signature links)
if (interest['Signature Link Client'] && interest['Signature Link CC'] && interest['Signature Link Developer']) {
console.log('EOI already exists, returning existing links');
return {
success: true,
documentId: 'existing',
clientSigningUrl: interest['Signature Link Client'],
signingLinks: {
'Client': interest['Signature Link Client'],
'CC': interest['Signature Link CC'],
'Developer': interest['Signature Link Developer']
}
};
}
// Validate required fields
const requiredFields = [
{ field: 'Full Name', value: interest['Full Name'] },
{ field: 'Email Address', value: interest['Email Address'] },
{ field: 'Yacht Name', value: interest['Yacht Name'] },
{ field: 'Length', value: interest['Length'] },
{ field: 'Width', value: interest['Width'] },
{ field: 'Depth', value: interest['Depth'] }
];
// Address is optional - use a default if not provided
const address = interest['Address'] || 'Not Provided';
const missingFields = requiredFields.filter(f => !f.value).map(f => f.field);
if (missingFields.length > 0) {
throw createError({
statusCode: 400,
statusMessage: `Please fill in the following required fields before generating EOI: ${missingFields.join(', ')}. You can update these fields in the interest details form.`
});
}
// Get linked berths
const berthsResponse = await $fetch<{ list: Array<{ 'Mooring Number': string }> }>(
"/api/get-interest-berths",
{
headers: {
"x-tag": xTagHeader,
},
params: {
interestId: interestId,
linkType: "berths",
},
}
);
const berths = berthsResponse.list || [];
if (berths.length === 0) {
throw createError({
statusCode: 400,
statusMessage: "No berths linked to this interest. Please link at least one berth."
});
}
// Concatenate berth numbers
const berthNumbers = berths.map(b => b['Mooring Number']).join(', ');
// Documenso API configuration
const documensoApiKey = process.env.NUXT_DOCUMENSO_API_KEY;
const documensoBaseUrl = process.env.NUXT_DOCUMENSO_BASE_URL;
const templateId = '9';
if (!documensoApiKey || !documensoBaseUrl) {
throw createError({
statusCode: 500,
statusMessage: "Documenso configuration missing. Please check NUXT_DOCUMENSO_API_KEY and NUXT_DOCUMENSO_BASE_URL environment variables."
});
}
// 1. Get template (optional - just to verify it exists)
try {
const templateResponse = await fetch(`${documensoBaseUrl}/api/v1/templates/${templateId}`, {
method: 'GET',
headers: {
'Authorization': `Bearer ${documensoApiKey}`
}
});
if (!templateResponse.ok) {
throw new Error(`Template not found: ${templateResponse.statusText}`);
}
} catch (error) {
console.error('Failed to verify template:', error);
throw createError({
statusCode: 500,
statusMessage: "Failed to verify template. Please check Documenso configuration."
});
}
// 2. Create document
const createDocumentPayload = {
meta: {
message: `Dear ${interest['Full Name']},\n\nThank you for your interest in a berth at Port Nimara. Please click the link above to sign your LOI.\n\nBest Regards,\nPort Nimara Team`,
subject: "Your LOI is ready to be signed",
redirectUrl: "https://portnimara.com",
distributionMethod: "SEQUENTIAL"
},
title: `${interest['Full Name']}-EOI-NDA`,
externalId: `loi-${interestId}`,
formValues: {
"Name": interest['Full Name'],
"Draft": interest['Depth'],
"Email": interest['Email Address'],
"Width": interest['Width'],
"Length": interest['Length'],
"Address": address,
"Lease_10": false,
"Purchase": true,
"Yacht Name": interest['Yacht Name'],
"Berth Number": berthNumbers
},
recipients: [
{
id: 155,
name: interest['Full Name'],
role: "SIGNER",
email: interest['Email Address'],
signingOrder: 1
},
{
id: 156,
name: "David Mizrahi",
role: "SIGNER",
email: "dm@portnimara.com",
signingOrder: 3
},
{
id: 157,
name: "Oscar Faragher",
role: "APPROVER",
email: "sales@portnimara.com",
signingOrder: 2
}
]
};
let documentResponse: DocumensoResponse;
try {
const response = await fetch(`${documensoBaseUrl}/api/v1/templates/${templateId}/generate-document`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${documensoApiKey}`
},
body: JSON.stringify(createDocumentPayload)
});
if (!response.ok) {
const errorText = await response.text();
console.error('Failed to create document:', errorText);
throw new Error(`Failed to create document: ${response.statusText}`);
}
documentResponse = await response.json();
} catch (error) {
console.error('Document creation error:', error);
throw createError({
statusCode: 500,
statusMessage: "Failed to create EOI document. Please try again."
});
}
// 3. Send document (moves from draft to active and sends emails)
try {
const sendResponse = await fetch(`${documensoBaseUrl}/api/v1/documents/${documentResponse.documentId}/send`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${documensoApiKey}`
},
body: JSON.stringify({
sendEmail: true,
sendCompletionEmails: true
})
});
if (!sendResponse.ok) {
const errorText = await sendResponse.text();
console.error('Failed to send document:', errorText);
throw new Error(`Failed to send document: ${sendResponse.statusText}`);
}
console.log('Document sent successfully');
} catch (error) {
console.error('Document send error:', error);
throw createError({
statusCode: 500,
statusMessage: "Document created but failed to send. Please check Documenso dashboard."
});
}
// Extract signing URLs from recipients
const signingLinks: Record<string, string> = {};
if (documentResponse.recipients) {
documentResponse.recipients.forEach(recipient => {
if (recipient.signingUrl) {
if (recipient.email === interest['Email Address']) {
signingLinks['Client'] = recipient.signingUrl;
} else if (recipient.email === 'dm@portnimara.com') {
signingLinks['David Mizrahi'] = recipient.signingUrl;
} else if (recipient.email === 'sales@portnimara.com') {
signingLinks['Oscar Faragher'] = recipient.signingUrl;
}
}
});
}
// 4. Update interest record
const currentDate = new Date();
const dateTimeString = currentDate.toLocaleString('en-GB', {
day: '2-digit',
month: '2-digit',
year: 'numeric',
hour: '2-digit',
minute: '2-digit',
hour12: false
});
const extraComments = interest['Extra Comments'] || '';
const updatedComments = extraComments + (extraComments ? '\n' : '') + `EOI Generated ${dateTimeString}`;
const updateData: any = {
'EOI Status': 'Waiting for Signatures',
'Sales Process Level': 'LOI and NDA Sent',
// Don't set EOI Time Sent here - only when email is sent or link is copied
'Extra Comments': updatedComments
};
// Add signing links to update data with new column names
if (signingLinks['Client']) {
updateData['Signature Link Client'] = signingLinks['Client'];
}
if (signingLinks['David Mizrahi']) {
updateData['Signature Link Developer'] = signingLinks['David Mizrahi'];
}
if (signingLinks['Oscar Faragher']) {
updateData['Signature Link CC'] = signingLinks['Oscar Faragher'];
}
await updateInterest(interestId, updateData);
return {
success: true,
documentId: documentResponse.documentId,
clientSigningUrl: signingLinks['Client'] || '',
signingLinks: signingLinks
};
} catch (error) {
console.error('Failed to generate EOI document:', error);
if (error instanceof Error) {
throw createError({
statusCode: 500,
statusMessage: error.message || "Failed to generate EOI document"
});
} else {
throw createError({
statusCode: 500,
statusMessage: "An unexpected error occurred",
});
}
}
});