312 lines
9.9 KiB
TypeScript
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",
|
|
});
|
|
}
|
|
}
|
|
});
|