2025-06-09 22:40:37 +02:00
import { getInterestById , updateInterest } from '~/server/utils/nocodb' ;
interface DocumensoRecipient {
id : number ;
name : string ;
email : string ;
role : 'SIGNER' | 'APPROVER' ;
signingOrder : number ;
signingUrl? : string ;
}
interface DocumensoResponse {
2025-06-09 23:09:32 +02:00
documentId : number ;
recipients : Array < {
recipientId : number ;
name : string ;
email : string ;
token : string ;
role : 'SIGNER' | 'APPROVER' ;
signingOrder : number ;
signingUrl : string ;
} > ;
2025-06-09 22:40:37 +02:00
}
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" } ) ;
}
// 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' ] }
] ;
2025-06-09 22:56:10 +02:00
// Address is optional - use a default if not provided
const address = interest [ 'Address' ] || 'Not Provided' ;
2025-06-09 22:40:37 +02:00
const missingFields = requiredFields . filter ( f = > ! f . value ) . map ( f = > f . field ) ;
if ( missingFields . length > 0 ) {
throw createError ( {
statusCode : 400 ,
2025-06-09 22:56:10 +02:00
statusMessage : ` Please fill in the following required fields before generating EOI: ${ missingFields . join ( ', ' ) } . You can update these fields in the interest details form. `
2025-06-09 22:40:37 +02:00
} ) ;
}
// 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
2025-06-09 22:50:06 +02:00
const documensoApiKey = process . env . NUXT_DOCUMENSO_API_KEY ;
const documensoBaseUrl = process . env . NUXT_DOCUMENSO_BASE_URL ;
2025-06-09 22:40:37 +02:00
const templateId = '9' ;
2025-06-09 22:50:06 +02:00
if ( ! documensoApiKey || ! documensoBaseUrl ) {
throw createError ( {
statusCode : 500 ,
statusMessage : "Documenso configuration missing. Please check NUXT_DOCUMENSO_API_KEY and NUXT_DOCUMENSO_BASE_URL environment variables."
} ) ;
}
2025-06-09 22:40:37 +02:00
// 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' ] ,
2025-06-09 22:56:10 +02:00
"Address" : address ,
2025-06-09 22:40:37 +02:00
"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."
} ) ;
}
2025-06-09 23:09:32 +02:00
// 3. Send document (moves from draft to active and sends emails)
2025-06-09 22:40:37 +02:00
try {
2025-06-09 23:09:32 +02:00
const sendResponse = await fetch ( ` ${ documensoBaseUrl } /api/v1/documents/ ${ documentResponse . documentId } /send ` , {
2025-06-09 22:40:37 +02:00
method : 'POST' ,
headers : {
'Content-Type' : 'application/json' ,
'Authorization' : ` Bearer ${ documensoApiKey } `
} ,
body : JSON.stringify ( {
2025-06-09 23:09:32 +02:00
sendEmail : true ,
2025-06-09 22:40:37 +02:00
sendCompletionEmails : true
} )
} ) ;
2025-06-09 23:09:32 +02:00
if ( ! sendResponse . ok ) {
const errorText = await sendResponse . text ( ) ;
console . error ( 'Failed to send document:' , errorText ) ;
throw new Error ( ` Failed to send document: ${ sendResponse . statusText } ` ) ;
2025-06-09 22:40:37 +02:00
}
2025-06-09 23:09:32 +02:00
console . log ( 'Document sent successfully' ) ;
2025-06-09 22:40:37 +02:00
} catch ( error ) {
2025-06-09 23:09:32 +02:00
console . error ( 'Document send error:' , error ) ;
throw createError ( {
statusCode : 500 ,
statusMessage : "Document created but failed to send. Please check Documenso dashboard."
} ) ;
2025-06-09 22:40:37 +02:00
}
// 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-US' , {
year : 'numeric' ,
month : '2-digit' ,
day : '2-digit' ,
hour : '2-digit' ,
minute : '2-digit' ,
hour12 : true
} ) ;
const extraComments = interest [ 'Extra Comments' ] || '' ;
const updatedComments = extraComments + ( extraComments ? '\n' : '' ) + ` EOI Sent ${ dateTimeString } ` ;
const updateData : any = {
'EOI Status' : 'Waiting for Signatures' ,
'Sales Process Level' : 'LOI and NDA Sent' ,
'EOI Time Sent' : currentDate . toISOString ( ) ,
'Extra Comments' : updatedComments
} ;
// Add signing links to update data
if ( signingLinks [ 'Client' ] ) {
updateData [ 'EOI Client Link' ] = signingLinks [ 'Client' ] ;
}
if ( signingLinks [ 'David Mizrahi' ] ) {
updateData [ 'EOI David Link' ] = signingLinks [ 'David Mizrahi' ] ;
}
if ( signingLinks [ 'Oscar Faragher' ] ) {
updateData [ 'EOI Oscar Link' ] = signingLinks [ 'Oscar Faragher' ] ;
}
await updateInterest ( interestId , updateData ) ;
return {
success : true ,
2025-06-09 23:09:32 +02:00
documentId : documentResponse.documentId ,
2025-06-09 22:40:37 +02:00
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" ,
} ) ;
}
}
} ) ;