#!/usr/bin/env node /** * Script pour créer une demande de signature avec un vrai PDF * Extrait automatiquement les placeholders DocuSeal pour positionner les signatures * * Usage: node create-signature-from-pdf.js */ const fs = require('fs'); const path = require('path'); const { S3Client, PutObjectCommand } = require('@aws-sdk/client-s3'); // Configuration const API_BASE = 'http://localhost:3000/api/odentas-sign'; const S3_BUCKET = 'odentas-sign'; const s3Client = new S3Client({ region: process.env.AWS_REGION || 'eu-west-3', credentials: { accessKeyId: process.env.AWS_ACCESS_KEY_ID, secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, }, }); /** * Extrait les placeholders DocuSeal du texte PDF * Format: {{Label;role=Role;type=signature;height=H;width=W}} */ function extractDocuSealPlaceholders(text) { const regex = /\{\{([^;]+);role=([^;]+);type=([^;]+);height=(\d+);width=(\d+)\}\}/g; const placeholders = []; let match; while ((match = regex.exec(text)) !== null) { placeholders.push({ label: match[1].trim(), role: match[2].trim(), type: match[3].trim(), height: parseInt(match[4]), width: parseInt(match[5]), textPosition: match.index, }); } return placeholders; } /** * Estime la position Y approximative d'un placeholder * Note: pdf-parse ne donne pas les coordonnées exactes, on estime */ function estimatePosition(placeholder, pdfInfo) { // Estimation simple basée sur la position dans le texte // En production, utiliser pdf.js ou pdfium pour obtenir les vraies coordonnées const totalChars = pdfInfo.text.length; const placeholderPosition = placeholder.textPosition; const relativePosition = placeholderPosition / totalChars; // Hauteur standard d'une page PDF en points (A4) const pageHeight = 842; // Position estimée (du haut vers le bas) const estimatedY = pageHeight * relativePosition; // Position X centrée ou à gauche selon le type const x = 100; // Marge gauche standard return { page: 1, // Supposons page 1 pour le moment x: x, y: Math.round(estimatedY), }; } /** * Extrait les vraies positions des placeholders avec pdf.js * (Version améliorée pour production) */ async function extractPrecisePositions(pdfPath) { // TODO: Implémenter avec pdf.js pour obtenir les vraies coordonnées // Pour l'instant, on utilise des positions fixes connues pour le template const filename = path.basename(pdfPath); // Positions connues pour le template de contrat CDDU if (filename.includes('cddu') || filename.includes('contrat')) { return [ { role: 'Employeur', page: 1, x: 50, y: 650, // Position approximative signature employeur width: 150, height: 60, }, { role: 'Salarié', page: 1, x: 350, y: 650, // Position approximative signature salarié width: 150, height: 60, }, ]; } return []; } async function main() { const args = process.argv.slice(2); if (args.length === 0) { console.error('Usage: node create-signature-from-pdf.js '); console.error('Exemple: node create-signature-from-pdf.js contrat_cddu_LYXHX3GI_240V001.pdf'); process.exit(1); } const pdfPath = args[0]; if (!fs.existsSync(pdfPath)) { console.error(`❌ Fichier introuvable: ${pdfPath}`); process.exit(1); } console.log('═══════════════════════════════════════════════════════'); console.log(' 📄 Création de signature depuis PDF'); console.log('═══════════════════════════════════════════════════════\n'); // 1. Lire le PDF console.log('📖 Lecture du PDF...'); const dataBuffer = fs.readFileSync(pdfPath); const pdfSize = Math.round(dataBuffer.length / 1024); console.log(` Taille: ${pdfSize} KB\n`); // 2. Utiliser positions fixes pour le template CDDU console.log('🔍 Détection du type de document...'); const filename = path.basename(pdfPath).toLowerCase(); if (filename.includes('cddu') || filename.includes('contrat')) { console.log(' ✅ Template CDDU détecté'); console.log(' ℹ️ Utilisation des positions pré-configurées\n'); } else { console.log(' ℹ️ Document générique, positions par défaut\n'); } // 3. Obtenir les positions précises console.log('\n📍 Calcul des positions de signature...'); const positions = await extractPrecisePositions(pdfPath); if (positions.length === 0) { console.error('❌ Impossible de déterminer les positions de signature'); process.exit(1); } positions.forEach(p => { console.log(` ${p.role}: page ${p.page}, (${p.x}, ${p.y}), ${p.width}x${p.height}px`); }); // 4. Upload du PDF vers S3 console.log('\n☁️ Upload du PDF vers S3...'); const ref = `REAL-${Date.now()}`; const s3Key = `source/real/${ref}.pdf`; await s3Client.send(new PutObjectCommand({ Bucket: S3_BUCKET, Key: s3Key, Body: dataBuffer, ContentType: 'application/pdf', Metadata: { original_filename: path.basename(pdfPath), ref: ref, }, })); console.log(` ✅ Uploadé: s3://${S3_BUCKET}/${s3Key}`); // 5. Créer la demande de signature console.log('\n✍️ Création de la demande de signature...'); const signersData = [ { name: 'Odentas Paie', email: 'paie@odentas.fr', role: 'Employeur', }, { name: 'Renaud Breviere', email: 'renaud.breviere@gmail.com', role: 'Salarié', }, ]; const signatureRequest = { ref: ref, title: `Contrat CDDU - ${path.basename(pdfPath, '.pdf')}`, sourceS3Key: s3Key, signers: signersData.map(signer => ({ name: signer.name, email: signer.email, role: signer.role, positions: positions .filter(p => p.role === signer.role) .map(p => ({ page: p.page, x: p.x, y: p.y, width: p.width, height: p.height, })), })), }; const response = await fetch(`${API_BASE}/requests/create`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(signatureRequest), }); if (!response.ok) { const error = await response.json(); console.error('❌ Erreur:', error); process.exit(1); } const result = await response.json(); // 6. Sauvegarder les infos const infoFile = 'signature-real-info.json'; fs.writeFileSync(infoFile, JSON.stringify(result, null, 2)); console.log('\n═══════════════════════════════════════════════════════'); console.log(' ✅ Demande créée avec succès !'); console.log('═══════════════════════════════════════════════════════\n'); console.log(`📋 Référence: ${result.request.ref}`); console.log(`📝 ID: ${result.request.id}\n`); console.log('🔗 URLs de signature:\n'); result.signers.forEach(signer => { const localUrl = signer.signatureUrl.replace( 'https://espace-paie.odentas.fr', 'http://localhost:3000' ); console.log(`${signer.role} (${signer.email}):`); console.log(` ${localUrl}\n`); }); console.log(`💾 Informations sauvegardées dans: ${infoFile}`); console.log('\n🚀 Pour tester:'); console.log(' 1. Ouvrir une des URLs ci-dessus'); console.log(' 2. Recevoir et valider l\'OTP'); console.log(' 3. Dessiner et valider la signature'); console.log(' 4. Répéter pour le 2ème signataire\n'); } main().catch(console.error);