espace-paie-odentas/app/api/docuseal-signature/route.ts
2025-10-12 17:05:46 +02:00

398 lines
No EOL
14 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { NextRequest, NextResponse } from 'next/server';
import { createSbServiceRole } from '@/lib/supabaseServer';
import { DynamoDBClient, PutItemCommand, UpdateItemCommand } from '@aws-sdk/client-dynamodb';
import { S3Client, GetObjectCommand, PutObjectCommand } from '@aws-sdk/client-s3';
import { sendUniversalEmailV2, renderUniversalEmailV2, EmailDataV2 } from '@/lib/emailTemplateService';
import { ENV } from '@/lib/cleanEnv';
import axios from 'axios';
// Configuration AWS
const region = ENV.AWS_REGION;
const dynamoDBClient = new DynamoDBClient({
region,
credentials: {
accessKeyId: ENV.AWS_ACCESS_KEY_ID,
secretAccessKey: ENV.AWS_SECRET_ACCESS_KEY
}
});
// Vérification des variables d'environnement au démarrage
console.log('🔧 Configuration DynamoDB:', {
region,
hasAccessKey: !!ENV.AWS_ACCESS_KEY_ID,
hasSecretKey: !!ENV.AWS_SECRET_ACCESS_KEY,
accessKeyLength: ENV.AWS_ACCESS_KEY_ID?.length,
secretKeyLength: ENV.AWS_SECRET_ACCESS_KEY?.length
});
const s3Client = new S3Client({ region });
// Envoi d'e-mails géré via le service de templates universels
export async function POST(request: NextRequest) {
console.log('=== DocuSeal Signature API ===');
try {
// Utiliser le service role pour les opérations serveur
const supabase = createSbServiceRole();
const data = await request.json();
console.log('Données reçues:', JSON.stringify(data, null, 2));
const {
contractId,
pdfS3Key,
employerEmail,
employeeEmail,
reference,
employeeName,
startDate,
role,
analytique,
structure,
signerFirstName,
employerCode,
employeeFirstName,
matricule,
contractType = 'CDDU'
} = data;
// Validation des champs requis
if (!contractId || !pdfS3Key || !employerEmail || !employeeEmail) {
return NextResponse.json(
{ error: 'Champs manquants: contractId, pdfS3Key, employerEmail, employeeEmail' },
{ status: 400 }
);
}
// Extraire le submission_id depuis le nom du fichier PDF
// pdfS3Key format: "unsigned-contracts/contrat_cddu_REFERENCE.pdf"
// submission_id attendu: "contrats/contrat_cddu_REFERENCE" (format pour DynamoDB)
const pdfFileName = pdfS3Key.split('/').pop() || ''; // "contrat_cddu_REFERENCE.pdf"
const contractFileName = pdfFileName.replace('.pdf', ''); // "contrat_cddu_REFERENCE"
const submissionId = `contrats/${contractFileName}`; // "contrats/contrat_cddu_REFERENCE"
console.log('📝 [SUBMISSION] pdfS3Key:', pdfS3Key);
console.log('📝 [SUBMISSION] pdfFileName:', pdfFileName);
console.log('📝 [SUBMISSION] contractFileName:', contractFileName);
console.log('📝 [SUBMISSION] submission_id généré:', submissionId);
const formattedDate = formatDate(startDate);
// Étape 1 : Stocker les données du contrat dans DynamoDB
console.log('🗄️ [DYNAMODB] Tentative de stockage pour submission_id:', submissionId);
try {
await storeContractData(submissionId, {
employerEmail,
employeeEmail,
reference,
salarie: employeeName,
date: startDate,
poste: role,
analytique,
structure,
prenom_signataire: signerFirstName,
code_employeur: employerCode,
prenom_salarie: employeeFirstName,
matricule,
typecontrat: contractType
});
console.log('✅ [DYNAMODB] Données du contrat stockées avec succès dans DynamoDB');
} catch (dynamoError) {
console.error('❌ [DYNAMODB] Erreur détaillée lors du stockage:', {
error: dynamoError,
message: dynamoError instanceof Error ? dynamoError.message : 'Erreur inconnue',
stack: dynamoError instanceof Error ? dynamoError.stack : undefined
});
// Continue le processus même si DynamoDB échoue
}
// Étape 2 : Récupération du PDF depuis S3
const getObjectCommand = new GetObjectCommand({
Bucket: ENV.AWS_S3_BUCKET,
Key: pdfS3Key,
});
const s3Object = await s3Client.send(getObjectCommand);
const bodyBytes = await s3Object.Body?.transformToByteArray();
if (!bodyBytes) {
throw new Error('Impossible de lire le contenu du fichier PDF depuis S3');
}
const pdfBase64 = Buffer.from(bodyBytes).toString('base64');
console.log('PDF récupéré depuis S3:', pdfS3Key);
// Extraire le nom de fichier depuis la clé S3 (exemple: "unsigned-contracts/contrat_cddu_6ET5Z3XW_DEMO002.pdf")
const docusealFileName = pdfS3Key.split('/').pop() || `contrat_${submissionId}.pdf`;
console.log('Nom de fichier extrait pour DocuSeal:', docusealFileName);
// Debug de la configuration DocuSeal
console.log('🔧 [DOCUSEAL] Configuration:', {
hasToken: !!ENV.DOCUSEAL_TOKEN,
tokenLength: ENV.DOCUSEAL_TOKEN.length,
tokenPreview: ENV.DOCUSEAL_TOKEN.substring(0, 10) + '...',
apiBase: ENV.DOCUSEAL_API_BASE,
apiBaseRaw: process.env.DOCUSEAL_API_BASE,
rawTokenLength: process.env.DOCUSEAL_TOKEN?.length
});
// Étape 3 : Création du template DocuSeal
const templateResponse = await axios.post(`${ENV.DOCUSEAL_API_BASE}/templates/pdf`, {
name: `CDDU - ${reference}`,
documents: [{ name: docusealFileName, file: pdfBase64 }]
}, {
headers: {
'X-Auth-Token': ENV.DOCUSEAL_TOKEN,
'Content-Type': 'application/json'
}
});
const templateId = templateResponse.data.id;
console.log('Template DocuSeal créé:', templateResponse.data);
// Étape 4 : Création de la soumission DocuSeal
const submissionResponse = await axios.post(`${ENV.DOCUSEAL_API_BASE}/submissions`, {
template_id: templateId,
send_email: false,
submitters: [
{ role: 'Employeur', email: employerEmail },
{ role: 'Salarié', email: employeeEmail }
]
}, {
headers: {
'X-Auth-Token': ENV.DOCUSEAL_TOKEN,
'Content-Type': 'application/json'
}
});
console.log('Soumission DocuSeal créée:', submissionResponse.data);
// Récupérer le submission_id DocuSeal
const docusealSubmissionId = submissionResponse.data[0].submission_id;
console.log(`DocuSeal submission_id: ${docusealSubmissionId}`);
// Mettre à jour DynamoDB avec le docusealSubID
console.log('🗄️ [DYNAMODB] Mise à jour avec docusealSubID:', docusealSubmissionId);
try {
await updateContractWithDocusealId(submissionId, docusealSubmissionId);
console.log('✅ [DYNAMODB] Mise à jour réussie avec docusealSubID');
} catch (updateError) {
console.error('❌ [DYNAMODB] Erreur lors de la mise à jour:', updateError);
}
// Récupérer les informations pour le lien de signature
const employerSubmission = submissionResponse.data.find((sub: any) => sub.role === 'Employeur');
const embedCode = employerSubmission.slug;
const signatureLink = `https://staging.paie.odentas.fr/odentas-sign?docuseal_id=${embedCode}`;
// Étape 5 : Envoi de l'email de signature via le template universel (employeur)
const emailData: EmailDataV2 = {
firstName: signerFirstName,
organizationName: structure,
employerCode: employerCode,
employeeName: employeeName,
documentType: contractType || 'Contrat',
contractReference: reference,
status: 'En attente',
ctaUrl: signatureLink,
};
// Rendu HTML pour archivage puis envoi
const rendered = await renderUniversalEmailV2({
type: 'signature-request-employer',
toEmail: employerEmail,
subject: `Demande de signature ${reference}`,
data: emailData,
});
const messageId = await sendUniversalEmailV2({
type: 'signature-request-employer',
toEmail: employerEmail,
subject: `Demande de signature ${reference}`,
data: emailData,
});
// Étape 6 : Upload de l'email HTML rendu sur S3 et logging
const emailHtml = rendered.html;
const emailLink = await uploadEmailToS3(emailHtml, messageId);
console.log('Email HTML uploadé sur S3:', emailLink);
// Étape 7 : Mise à jour du contrat dans Supabase avec les infos DocuSeal
console.log('🗄️ [SUPABASE] Début de la mise à jour du contrat:', {
contractId,
templateId,
docusealSubmissionId,
signatureLink
});
const supabaseResult = await supabase
.from('cddu_contracts')
.update({
docuseal_template_id: templateId,
docuseal_submission_id: docusealSubmissionId,
signature_status: 'En attente',
signature_link: signatureLink,
updated_at: new Date().toISOString()
})
.eq('id', contractId);
console.log('🗄️ [SUPABASE] Résultat de la mise à jour:', {
error: supabaseResult.error,
status: supabaseResult.status,
statusText: supabaseResult.statusText,
count: supabaseResult.count
});
if (supabaseResult.error) {
console.error('❌ [SUPABASE] Erreur lors de la mise à jour:', supabaseResult.error);
throw new Error(`Erreur Supabase: ${supabaseResult.error.message}`);
}
console.log('✅ [SUPABASE] Contrat mis à jour avec succès dans Supabase');
return NextResponse.json({
success: true,
message: 'Signature électronique initiée avec succès',
data: {
templateId,
submissionId: docusealSubmissionId,
signatureLink,
emailLink
}
});
} catch (error) {
console.error('Erreur lors de l\'initiation de la signature:', error);
return NextResponse.json(
{
error: 'Erreur lors de l\'initiation de la signature électronique',
details: error instanceof Error ? error.message : 'Erreur inconnue'
},
{ status: 500 }
);
}
}
// Fonction pour formater la date
function formatDate(dateStr: string): string {
try {
const dateObj = new Date(dateStr);
if (isNaN(dateObj.getTime())) {
console.error('Date invalide:', dateStr);
return dateStr;
}
const day = String(dateObj.getUTCDate()).padStart(2, '0');
const month = String(dateObj.getUTCMonth() + 1).padStart(2, '0');
const year = dateObj.getUTCFullYear();
return `${day}/${month}/${year}`;
} catch (error) {
console.error('Erreur lors du formatage de la date:', error);
return dateStr;
}
}
// Fonction pour stocker les données du contrat dans DynamoDB
async function storeContractData(submissionId: string, data: any) {
console.log('🗄️ [DYNAMODB] storeContractData - Données à sauvegarder:', {
submissionId,
dataKeys: Object.keys(data),
data
});
const command = new PutItemCommand({
TableName: 'DocuSealNotification',
Item: {
submission_id: { S: submissionId },
employerEmail: { S: data.employerEmail || '' },
employeeEmail: { S: data.employeeEmail || '' },
reference: { S: data.reference || '' },
salarie: { S: data.salarie || '' },
date: { S: data.date || '' },
poste: { S: data.poste || '' },
analytique: { S: data.analytique || '' },
structure: { S: data.structure || '' },
prenom_signataire: { S: data.prenom_signataire || '' },
code_employeur: { S: data.code_employeur || '' },
prenom_salarie: { S: data.prenom_salarie || '' },
matricule: { S: data.matricule || '' },
typecontrat: { S: data.typecontrat || '' }
}
});
console.log('🗄️ [DYNAMODB] Commande DynamoDB préparée:', {
TableName: command.input.TableName,
submissionId: command.input.Item?.submission_id
});
try {
const result = await dynamoDBClient.send(command);
console.log('✅ [DYNAMODB] PutItem réussi:', result);
return result;
} catch (error) {
console.error('❌ [DYNAMODB] Erreur détaillée dans storeContractData:', {
error,
errorName: error instanceof Error ? error.name : 'Unknown',
errorMessage: error instanceof Error ? error.message : 'Unknown error',
errorCode: (error as any)?.code,
errorRequestId: (error as any)?.$metadata?.requestId
});
// Ne pas faire échouer tout le processus pour DynamoDB
console.log('⚠️ Continuing without DynamoDB storage...');
throw error; // Re-throw pour que le catch parent puisse logger
}
}
// Fonction pour mettre à jour l'enregistrement dans DynamoDB
async function updateContractWithDocusealId(submissionId: string, docusealSubID: string | number) {
console.log('🗄️ [DYNAMODB] updateContractWithDocusealId:', { submissionId, docusealSubID });
const command = new UpdateItemCommand({
TableName: 'DocuSealNotification',
Key: {
submission_id: { S: submissionId }
},
UpdateExpression: 'set docusealSubID = :docusealSubID',
ExpressionAttributeValues: {
':docusealSubID': { S: String(docusealSubID) } // Convertir en string
},
ReturnValues: 'UPDATED_NEW'
});
try {
const result = await dynamoDBClient.send(command);
console.log('✅ [DYNAMODB] UpdateItem réussi:', result);
return result;
} catch (error) {
console.error('❌ [DYNAMODB] Erreur détaillée dans updateContractWithDocusealId:', {
error,
errorName: error instanceof Error ? error.name : 'Unknown',
errorMessage: error instanceof Error ? error.message : 'Unknown error',
errorCode: (error as any)?.code,
errorRequestId: (error as any)?.$metadata?.requestId
});
// Ne pas faire échouer tout le processus pour DynamoDB
console.log('⚠️ Continuing without DynamoDB update...');
throw error; // Re-throw pour le catch parent
}
}
// Fonction pour uploader l'email HTML sur S3
async function uploadEmailToS3(emailHtml: string, key: string): Promise<string> {
const command = new PutObjectCommand({
Bucket: ENV.S3_BUCKET_NAME_EMAILS,
Key: `${key}.html`,
Body: emailHtml,
ContentType: 'text/html',
ACL: 'public-read'
});
try {
await s3Client.send(command);
return `https://${ENV.S3_BUCKET_NAME_EMAILS}.s3.amazonaws.com/${key}.html`;
} catch (error) {
console.error('Erreur lors de l\'upload de l\'email sur S3:', error);
throw new Error('Échec de l\'upload de l\'email HTML sur S3.');
}
}
// Anciennes fonctions HTML/SES supprimées au profit du système universel