2025-06-17 15:17:19 +02:00
|
|
|
// Documenso API client utilities
|
|
|
|
|
interface DocumensoConfig {
|
2025-06-12 21:54:47 +02:00
|
|
|
apiUrl: string;
|
2025-06-12 21:50:01 +02:00
|
|
|
apiKey: string;
|
2025-06-10 13:59:09 +02:00
|
|
|
}
|
|
|
|
|
|
2025-06-17 15:17:19 +02:00
|
|
|
interface DocumensoRecipient {
|
2025-06-12 21:54:47 +02:00
|
|
|
id: number;
|
|
|
|
|
documentId: number;
|
|
|
|
|
email: string;
|
|
|
|
|
name: string;
|
|
|
|
|
role: 'SIGNER' | 'APPROVER' | 'VIEWER';
|
|
|
|
|
signingOrder: number;
|
|
|
|
|
token: string;
|
|
|
|
|
signedAt: string | null;
|
|
|
|
|
readStatus: 'NOT_OPENED' | 'OPENED';
|
|
|
|
|
signingStatus: 'NOT_SIGNED' | 'SIGNED';
|
|
|
|
|
sendStatus: 'NOT_SENT' | 'SENT';
|
|
|
|
|
signingUrl: string;
|
|
|
|
|
}
|
2025-06-10 13:59:09 +02:00
|
|
|
|
2025-06-17 15:17:19 +02:00
|
|
|
interface DocumensoDocument {
|
2025-06-12 21:54:47 +02:00
|
|
|
id: number;
|
|
|
|
|
externalId: string;
|
|
|
|
|
userId: number;
|
|
|
|
|
teamId: number;
|
|
|
|
|
title: string;
|
|
|
|
|
status: 'DRAFT' | 'PENDING' | 'COMPLETED' | 'CANCELLED';
|
|
|
|
|
documentDataId: string;
|
|
|
|
|
createdAt: string;
|
|
|
|
|
updatedAt: string;
|
|
|
|
|
completedAt: string | null;
|
2025-06-17 15:17:19 +02:00
|
|
|
recipients: DocumensoRecipient[];
|
2025-06-10 13:59:09 +02:00
|
|
|
}
|
|
|
|
|
|
2025-06-17 15:17:19 +02:00
|
|
|
interface DocumensoListResponse {
|
|
|
|
|
documents: DocumensoDocument[];
|
2025-06-12 21:54:47 +02:00
|
|
|
total: number;
|
|
|
|
|
page: number;
|
|
|
|
|
perPage: number;
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-17 15:17:19 +02:00
|
|
|
// Get Documenso configuration from environment variables
|
|
|
|
|
const getDocumensoConfig = (): DocumensoConfig => {
|
|
|
|
|
const apiUrl = process.env.NUXT_DOCUMENSO_BASE_URL;
|
|
|
|
|
const apiKey = process.env.NUXT_DOCUMENSO_API_KEY;
|
|
|
|
|
|
|
|
|
|
if (!apiUrl || !apiKey) {
|
|
|
|
|
throw createError({
|
|
|
|
|
statusCode: 500,
|
|
|
|
|
statusMessage: 'Documenso configuration missing. Please check NUXT_DOCUMENSO_BASE_URL and NUXT_DOCUMENSO_API_KEY environment variables.'
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-12 21:54:47 +02:00
|
|
|
return {
|
2025-06-17 15:17:19 +02:00
|
|
|
apiUrl: `${apiUrl}/api/v1`,
|
|
|
|
|
apiKey: `Bearer ${apiKey}`
|
2025-06-12 21:54:47 +02:00
|
|
|
};
|
2025-06-10 13:59:09 +02:00
|
|
|
};
|
|
|
|
|
|
2025-06-12 21:54:47 +02:00
|
|
|
// Fetch a single document by ID
|
2025-06-17 15:17:19 +02:00
|
|
|
export const getDocumensoDocument = async (documentId: number): Promise<DocumensoDocument> => {
|
|
|
|
|
const config = getDocumensoConfig();
|
2025-06-10 13:59:09 +02:00
|
|
|
|
|
|
|
|
try {
|
2025-06-17 15:17:19 +02:00
|
|
|
const response = await $fetch<DocumensoDocument>(`${config.apiUrl}/documents/${documentId}`, {
|
2025-06-12 21:54:47 +02:00
|
|
|
headers: {
|
|
|
|
|
'Authorization': config.apiKey,
|
|
|
|
|
'Content-Type': 'application/json'
|
|
|
|
|
}
|
2025-06-10 13:59:09 +02:00
|
|
|
});
|
|
|
|
|
|
2025-06-12 21:54:47 +02:00
|
|
|
return response;
|
2025-06-10 13:59:09 +02:00
|
|
|
} catch (error) {
|
2025-06-17 15:17:19 +02:00
|
|
|
console.error('Failed to fetch Documenso document:', error);
|
2025-06-10 13:59:09 +02:00
|
|
|
throw error;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2025-06-12 21:54:47 +02:00
|
|
|
// Search documents by external ID (e.g., 'loi-94')
|
2025-06-17 15:17:19 +02:00
|
|
|
export const searchDocumensoDocuments = async (externalId?: string): Promise<DocumensoDocument[]> => {
|
|
|
|
|
const config = getDocumensoConfig();
|
2025-06-10 13:59:09 +02:00
|
|
|
|
2025-06-12 21:50:01 +02:00
|
|
|
try {
|
2025-06-17 15:17:19 +02:00
|
|
|
const response = await $fetch<DocumensoListResponse>(`${config.apiUrl}/documents`, {
|
2025-06-12 21:54:47 +02:00
|
|
|
headers: {
|
|
|
|
|
'Authorization': config.apiKey,
|
|
|
|
|
'Content-Type': 'application/json'
|
|
|
|
|
},
|
|
|
|
|
params: {
|
|
|
|
|
perPage: 100
|
|
|
|
|
}
|
2025-06-12 21:50:01 +02:00
|
|
|
});
|
|
|
|
|
|
2025-06-12 21:54:47 +02:00
|
|
|
// If externalId is provided, filter by it
|
|
|
|
|
if (externalId) {
|
2025-06-17 15:17:19 +02:00
|
|
|
return response.documents.filter((doc: DocumensoDocument) => doc.externalId === externalId);
|
2025-06-12 21:54:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return response.documents;
|
2025-06-12 21:50:01 +02:00
|
|
|
} catch (error) {
|
2025-06-17 15:17:19 +02:00
|
|
|
console.error('Failed to search Documenso documents:', error);
|
2025-06-12 21:50:01 +02:00
|
|
|
throw error;
|
|
|
|
|
}
|
2025-06-10 13:59:09 +02:00
|
|
|
};
|
|
|
|
|
|
2025-06-12 21:54:47 +02:00
|
|
|
// Get document by external ID (e.g., 'loi-94')
|
2025-06-17 15:17:19 +02:00
|
|
|
export const getDocumensoDocumentByExternalId = async (externalId: string): Promise<DocumensoDocument | null> => {
|
|
|
|
|
const documents = await searchDocumensoDocuments(externalId);
|
2025-06-12 21:54:47 +02:00
|
|
|
return documents.length > 0 ? documents[0] : null;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Check signature status for a document
|
|
|
|
|
export const checkDocumentSignatureStatus = async (documentId: number): Promise<{
|
|
|
|
|
documentStatus: string;
|
2025-06-17 15:17:19 +02:00
|
|
|
unsignedRecipients: DocumensoRecipient[];
|
|
|
|
|
signedRecipients: DocumensoRecipient[];
|
2025-06-12 21:54:47 +02:00
|
|
|
clientSigned: boolean;
|
|
|
|
|
allSigned: boolean;
|
|
|
|
|
}> => {
|
2025-06-17 15:17:19 +02:00
|
|
|
const document = await getDocumensoDocument(documentId);
|
2025-06-10 13:59:09 +02:00
|
|
|
|
2025-06-17 15:17:19 +02:00
|
|
|
const unsignedRecipients = document.recipients.filter((r: DocumensoRecipient) => r.signingStatus === 'NOT_SIGNED');
|
|
|
|
|
const signedRecipients = document.recipients.filter((r: DocumensoRecipient) => r.signingStatus === 'SIGNED');
|
2025-06-12 21:54:47 +02:00
|
|
|
|
|
|
|
|
// Check if client (signingOrder = 1) has signed
|
2025-06-17 15:17:19 +02:00
|
|
|
const clientRecipient = document.recipients.find((r: DocumensoRecipient) => r.signingOrder === 1);
|
2025-06-12 21:54:47 +02:00
|
|
|
const clientSigned = clientRecipient ? clientRecipient.signingStatus === 'SIGNED' : false;
|
|
|
|
|
|
|
|
|
|
const allSigned = unsignedRecipients.length === 0;
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
documentStatus: document.status,
|
|
|
|
|
unsignedRecipients,
|
|
|
|
|
signedRecipients,
|
|
|
|
|
clientSigned,
|
|
|
|
|
allSigned
|
|
|
|
|
};
|
2025-06-10 13:59:09 +02:00
|
|
|
};
|
|
|
|
|
|
2025-06-12 21:54:47 +02:00
|
|
|
// Get recipients who need to sign (excluding client)
|
2025-06-17 15:17:19 +02:00
|
|
|
export const getRecipientsToRemind = async (documentId: number): Promise<DocumensoRecipient[]> => {
|
2025-06-12 21:54:47 +02:00
|
|
|
const status = await checkDocumentSignatureStatus(documentId);
|
2025-06-12 21:50:01 +02:00
|
|
|
|
2025-06-12 21:54:47 +02:00
|
|
|
// Only remind if client has signed
|
|
|
|
|
if (!status.clientSigned) {
|
|
|
|
|
return [];
|
2025-06-12 21:50:01 +02:00
|
|
|
}
|
2025-06-12 21:54:47 +02:00
|
|
|
|
|
|
|
|
// Return unsigned recipients with signingOrder > 1
|
2025-06-17 15:17:19 +02:00
|
|
|
return status.unsignedRecipients.filter((r: DocumensoRecipient) => r.signingOrder > 1);
|
2025-06-12 21:54:47 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Format recipient name for emails
|
2025-06-17 15:17:19 +02:00
|
|
|
export const formatRecipientName = (recipient: DocumensoRecipient): string => {
|
2025-06-12 21:54:47 +02:00
|
|
|
const firstName = recipient.name.split(' ')[0];
|
|
|
|
|
return firstName;
|
2025-06-10 13:59:09 +02:00
|
|
|
};
|
|
|
|
|
|
2025-06-12 21:54:47 +02:00
|
|
|
// Get signing URL for a recipient
|
2025-06-17 15:17:19 +02:00
|
|
|
export const getSigningUrl = (recipient: DocumensoRecipient): string => {
|
2025-06-12 21:54:47 +02:00
|
|
|
return recipient.signingUrl;
|
2025-06-10 13:59:09 +02:00
|
|
|
};
|