# Migration PDFMonkey → Gotenberg ## Contexte Ce guide explique comment migrer la génération de PDF des contrats de travail de **PDFMonkey** (SaaS avec templates Liquid) vers **Gotenberg** (auto-hébergé). ## Avantages de Gotenberg - Auto-hébergé : contrôle total, pas de dépendance externe - Gratuit et open-source - Rapide et performant - Supporte HTML/CSS nativement - API simple et bien documentée - Déployable sur Vercel, Railway, Coolify, etc. ## Architecture de la Solution ``` ┌─────────────────┐ │ Next.js App │ │ (Formulaire) │ └────────┬────────┘ │ 1. Envoie données JSON ↓ ┌─────────────────────────┐ │ API Route Next.js │ │ /api/generate-contract │ │ │ │ - Compile template │ │ Handlebars │ │ - Génère HTML │ └────────┬────────────────┘ │ 2. Envoie HTML ↓ ┌─────────────────────────┐ │ Gotenberg Service │ │ (Docker/Auto-hébergé) │ │ │ │ - Convertit HTML → PDF │ │ - Retourne le PDF │ └────────┬────────────────┘ │ 3. Reçoit PDF ↓ ┌─────────────────────────┐ │ Supabase Storage │ │ (Stockage PDF) │ └─────────────────────────┘ ``` ## Fichiers Créés ### 1. Helpers Handlebars **Fichier** : `lib/handlebars-helpers.ts` Remplace les filtres Liquid par des helpers Handlebars : - `removeFirst` : équivalent de `remove_first` - `contains` : vérifier si une chaîne contient un pattern - `split` : diviser une chaîne - `eq`, `ne`, `gte`, `gt` : comparaisons - `isEmpty`, `isNotEmpty` : vérifier les valeurs vides - `includesAny` : vérifier plusieurs valeurs (pour les CCN) ### 2. Template Handlebars **Fichier** : `templates-contrats/cddu-handlebars.html` Template HTML/Handlebars qui remplace le template Liquid PDFMonkey. **Principales conversions** : | Liquid | Handlebars | |--------|------------| | `{% if condition %}` | `{{#if condition}}` | | `{% elsif %}` | `{{else if}}` | | `{% assign var = value %}` | Variables pré-calculées en JS | | `{{ var \| filter }}` | `{{helper var}}` | | `{% for item in array %}` | `{{#each array}}` | ### 3. API Route Gotenberg **Fichier** : `app/api/generate-contract-pdf/route.ts` Route API qui : 1. Authentifie l'utilisateur 2. Charge le template Handlebars 3. Compile le template avec les données 4. Envoie le HTML à Gotenberg 5. Reçoit le PDF 6. Upload sur Supabase Storage 7. Retourne l'URL du PDF ### 4. Docker Compose **Fichier** : `docker-compose.gotenberg.yml` Configuration Docker pour auto-héberger Gotenberg. ## Installation ### Étape 1 : Installer les dépendances ```bash npm install handlebars npm install --save-dev @types/handlebars ``` ### Étape 2 : Configurer les variables d'environnement Ajouter dans `.env.local` : ```bash # URL de Gotenberg (local ou auto-hébergé) GOTENBERG_URL=http://localhost:3001 # Ou en production (exemple avec Railway/Coolify) # GOTENBERG_URL=https://gotenberg.votre-domaine.com ``` ### Étape 3 : Déployer Gotenberg localement ```bash docker-compose -f docker-compose.gotenberg.yml up -d ``` Vérifier que Gotenberg fonctionne : ```bash curl http://localhost:3001/health ``` ### Étape 4 : Tester l'API Créer un fichier de test `test-generate-pdf.ts` : ```typescript const response = await fetch('/api/generate-contract-pdf', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ contractId: 'abc123', contractType: 'cddu', data: { structure_name: 'Association Compagnie Lazara', employee_firstname: 'Jean', employee_lastname: 'GOLTIER', // ... autres données }, }), }); const result = await response.json(); console.log('PDF généré:', result.pdfUrl); ``` ## Déploiement en Production ### Option 1 : Railway (recommandé) 1. Créer un nouveau service sur Railway 2. Déployer l'image Docker : `gotenberg/gotenberg:8` 3. Exposer le port `3000` 4. Récupérer l'URL publique 5. Mettre à jour `GOTENBERG_URL` dans Vercel ### Option 2 : Coolify 1. Créer un nouveau service 2. Source : Docker Image 3. Image : `gotenberg/gotenberg:8` 4. Port : `3000` 5. Générer un domaine public 6. Mettre à jour `GOTENBERG_URL` ### Option 3 : VPS auto-hébergé ```bash # Sur le VPS git clone cd Projet\ Nouvel\ Espace\ Paie docker-compose -f docker-compose.gotenberg.yml up -d # Configurer Nginx reverse proxy sudo nano /etc/nginx/sites-available/gotenberg # Contenu : server { listen 80; server_name gotenberg.votre-domaine.com; location / { proxy_pass http://localhost:3001; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } } # Activer le site sudo ln -s /etc/nginx/sites-available/gotenberg /etc/nginx/sites-enabled/ sudo systemctl reload nginx ``` ## Intégration dans le Code Existant ### Modifier le formulaire de création de contrat Dans `app/staff/contrats/[id]/page.tsx` ou le composant concerné : ```typescript const handleGeneratePDF = async () => { setIsGenerating(true); try { const response = await fetch('/api/generate-contract-pdf', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ contractId: contract.id, contractType: contract.regime === 'CDDU' ? 'cddu' : 'rg', data: prepareContractData(contract), }), }); const result = await response.json(); if (result.success) { toast.success('PDF généré avec succès'); // Ouvrir le PDF dans un nouvel onglet window.open(result.pdfUrl, '_blank'); } else { toast.error('Erreur lors de la génération du PDF'); } } catch (error) { console.error('Erreur:', error); toast.error('Erreur lors de la génération du PDF'); } finally { setIsGenerating(false); } }; // Fonction pour préparer les données function prepareContractData(contract: Contract) { return { structure_name: contract.organization.name, structure_adresse: contract.organization.address, structure_cpville: contract.organization.postal_code, structure_ville: contract.organization.city, structure_siret: contract.organization.siret, employee_firstname: contract.employee.first_name, employee_lastname: contract.employee.last_name, employee_dob: formatDate(contract.employee.birth_date), // ... mapper toutes les données imageUrl: contract.organization.logo_data_uri, }; } ``` ## Différences avec PDFMonkey | Aspect | PDFMonkey | Gotenberg | |--------|-----------|-----------| | **Hébergement** | SaaS externe | Auto-hébergé | | **Coût** | Payant (par document) | Gratuit | | **Template** | Liquid (éditeur en ligne) | Handlebars (code local) | | **Latence** | Variable (réseau externe) | Faible (interne) | | **Contrôle** | Limité | Total | | **Maintenance** | Aucune | Docker + monitoring | ## Gestion des Templates ### Modifier un template 1. Éditer `templates-contrats/cddu-handlebars.html` 2. Tester localement 3. Commit + push 4. Déploiement automatique via Vercel ### Créer un nouveau template 1. Dupliquer `cddu-handlebars.html` 2. Adapter le contenu 3. Ajouter le cas dans l'API route : ```typescript case 'nouveau-type': templatePath = path.join(process.cwd(), 'templates-contrats', 'nouveau-type.html'); break; ``` ## Monitoring et Logs ### Vérifier les logs Gotenberg ```bash docker logs odentas-gotenberg --tail 100 -f ``` ### Health check ```bash curl http://localhost:3001/health ``` ### Métriques de performance Ajouter dans l'API route : ```typescript const startTime = Date.now(); // ... génération PDF const duration = Date.now() - startTime; console.log(`PDF généré en ${duration}ms`); ``` ## Troubleshooting ### Erreur : Gotenberg inaccessible **Symptôme** : `fetch failed` ou timeout **Solutions** : 1. Vérifier que Gotenberg est démarré : `docker ps` 2. Vérifier le health check : `curl http://localhost:3001/health` 3. Vérifier les logs : `docker logs odentas-gotenberg` 4. Vérifier la variable `GOTENBERG_URL` ### Erreur : Template non trouvé **Symptôme** : `ENOENT: no such file or directory` **Solutions** : 1. Vérifier le chemin du template 2. Vérifier que le template existe bien dans `templates-contrats/` 3. En production Vercel, vérifier que les templates sont inclus dans le build ### Erreur : Rendu incorrect du PDF **Symptôme** : Mise en page cassée, polices manquantes **Solutions** : 1. Vérifier le CSS dans le template 2. Utiliser des polices web-safe ou inclure les fonts en base64 3. Tester le HTML seul dans un navigateur 4. Ajuster les marges dans l'API route ### Performance lente **Symptôme** : Génération de PDF > 5 secondes **Solutions** : 1. Augmenter les ressources Docker 2. Optimiser les images (compression, taille) 3. Réduire la complexité du HTML/CSS 4. Utiliser un cache pour les templates compilés ## Checklist de Migration - [ ] Installer les dépendances npm - [ ] Créer les helpers Handlebars - [ ] Convertir le template CDDU - [ ] Créer l'API route - [ ] Déployer Gotenberg localement - [ ] Tester la génération de PDF en local - [ ] Déployer Gotenberg en production - [ ] Configurer la variable `GOTENBERG_URL` sur Vercel - [ ] Intégrer dans le formulaire de contrat - [ ] Tester en production - [ ] Migrer les autres templates (RG, Avenants) - [ ] Désactiver PDFMonkey ## Rollback Si besoin de revenir à PDFMonkey : 1. Réactiver les appels à l'API PDFMonkey 2. Commenter les appels à `/api/generate-contract-pdf` 3. Garder le code Gotenberg en standby ## Next Steps Une fois la migration terminée : 1. **Migrer les autres templates** : RG, Avenants, etc. 2. **Optimiser les performances** : cache de templates, parallélisation 3. **Ajouter des analytics** : temps de génération, taux de succès 4. **Backup automatique** : sauvegarder les PDFs générés 5. **Versioning des templates** : Git + tags pour suivre les changements ## Support En cas de problème, consulter : - [Documentation Gotenberg](https://gotenberg.dev/) - [Documentation Handlebars](https://handlebarsjs.com/) - Logs Docker : `docker logs odentas-gotenberg` - Logs Next.js : Console Vercel --- **Auteur** : Équipe Odentas **Date** : Décembre 2025 **Version** : 1.0