From c6faceb038987672016348d14ae7b856240baa3c Mon Sep 17 00:00:00 2001 From: odentas Date: Sat, 27 Dec 2025 21:40:27 +0100 Subject: [PATCH] =?UTF-8?q?feat:=20Pr=C3=A9paration=20migration=20PDFMonke?= =?UTF-8?q?y=20vers=20Gotenberg?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Ajout helpers Handlebars pour remplacer filtres Liquid - Conversion template CDDU de Liquid vers Handlebars - Nouvelle API route /api/generate-contract-pdf pour Gotenberg - Configuration Docker Compose pour auto-héberger Gotenberg - Documentation complète de migration - Variables d'environnement exemple Note: Le bouton 'Créer PDF' utilise encore PDFMonkey. Pour activer Gotenberg, modifier l'appel dans ContractEditor.tsx --- .env.gotenberg.example | 8 + MIGRATION_PDFMONKEY_GOTENBERG.md | 400 ++++++++++++++++++++++++ app/api/generate-contract-pdf/route.ts | 163 ++++++++++ docker-compose.gotenberg.yml | 54 ++++ lib/handlebars-helpers.ts | 114 +++++++ templates-contrats/cddu-handlebars.html | 390 +++++++++++++++++++++++ 6 files changed, 1129 insertions(+) create mode 100644 .env.gotenberg.example create mode 100644 MIGRATION_PDFMONKEY_GOTENBERG.md create mode 100644 app/api/generate-contract-pdf/route.ts create mode 100644 docker-compose.gotenberg.yml create mode 100644 lib/handlebars-helpers.ts create mode 100644 templates-contrats/cddu-handlebars.html diff --git a/.env.gotenberg.example b/.env.gotenberg.example new file mode 100644 index 0000000..f0039c6 --- /dev/null +++ b/.env.gotenberg.example @@ -0,0 +1,8 @@ +# Variables d'environnement pour Gotenberg +# Ajouter ces lignes dans .env.local + +# URL de Gotenberg (local pour développement) +GOTENBERG_URL=http://localhost:3001 + +# En production (Railway, Coolify, VPS, etc.) +# GOTENBERG_URL=https://gotenberg.votre-domaine.com diff --git a/MIGRATION_PDFMONKEY_GOTENBERG.md b/MIGRATION_PDFMONKEY_GOTENBERG.md new file mode 100644 index 0000000..4776b63 --- /dev/null +++ b/MIGRATION_PDFMONKEY_GOTENBERG.md @@ -0,0 +1,400 @@ +# 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 diff --git a/app/api/generate-contract-pdf/route.ts b/app/api/generate-contract-pdf/route.ts new file mode 100644 index 0000000..27f95e7 --- /dev/null +++ b/app/api/generate-contract-pdf/route.ts @@ -0,0 +1,163 @@ +import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs'; +import { cookies } from 'next/headers'; +import { NextRequest, NextResponse } from 'next/server'; +import Handlebars from 'handlebars'; +import fs from 'fs/promises'; +import path from 'path'; +import { registerHandlebarsHelpers } from '@/lib/handlebars-helpers'; + +registerHandlebarsHelpers(); + +/** + * API Route pour générer un contrat PDF via Gotenberg + * POST /api/generate-contract-pdf + * + * Body attendu : + * { + * contractId: string, + * contractType: "cddu" | "rg" | "avenant", + * data: object (données du contrat) + * } + */ +export async function POST(request: NextRequest) { + try { + const supabase = createRouteHandlerClient({ cookies }); + + const { + data: { session }, + } = await supabase.auth.getSession(); + + if (!session) { + return NextResponse.json( + { error: 'Non authentifié' }, + { status: 401 } + ); + } + + const body = await request.json(); + const { contractId, contractType, data } = body; + + if (!contractId || !contractType || !data) { + return NextResponse.json( + { error: 'Données manquantes (contractId, contractType, data)' }, + { status: 400 } + ); + } + + // Vérifier que l'utilisateur a accès à ce contrat + const { data: contract, error: contractError } = await supabase + .from('contracts') + .select('*') + .eq('id', contractId) + .single(); + + if (contractError || !contract) { + return NextResponse.json( + { error: 'Contrat introuvable' }, + { status: 404 } + ); + } + + // Charger le template Handlebars selon le type de contrat + let templatePath: string; + switch (contractType) { + case 'cddu': + templatePath = path.join(process.cwd(), 'templates-contrats', 'cddu-handlebars.html'); + break; + case 'rg': + templatePath = path.join(process.cwd(), 'templates-contrats', 'rg-handlebars.html'); + break; + case 'avenant': + templatePath = path.join(process.cwd(), 'templates-contrats', 'avenant-handlebars.html'); + break; + default: + return NextResponse.json( + { error: 'Type de contrat invalide' }, + { status: 400 } + ); + } + + const templateContent = await fs.readFile(templatePath, 'utf-8'); + const template = Handlebars.compile(templateContent); + + // Générer le HTML avec les données + const html = template(data); + + // Envoyer le HTML à Gotenberg pour conversion en PDF + const gotenbergUrl = process.env.GOTENBERG_URL || 'http://localhost:3001'; + + const formData = new FormData(); + formData.append('files', new Blob([html], { type: 'text/html' }), 'index.html'); + + // Options Gotenberg + formData.append('marginTop', '1'); + formData.append('marginBottom', '1'); + formData.append('marginLeft', '1'); + formData.append('marginRight', '1'); + formData.append('paperWidth', '21'); + formData.append('paperHeight', '29.7'); + formData.append('preferCssPageSize', 'false'); + + const gotenbergResponse = await fetch(`${gotenbergUrl}/forms/chromium/convert/html`, { + method: 'POST', + body: formData, + }); + + if (!gotenbergResponse.ok) { + const errorText = await gotenbergResponse.text(); + console.error('Erreur Gotenberg:', errorText); + return NextResponse.json( + { error: 'Erreur lors de la génération du PDF', details: errorText }, + { status: 500 } + ); + } + + // Récupérer le PDF généré + const pdfBuffer = await gotenbergResponse.arrayBuffer(); + const pdfBlob = new Blob([pdfBuffer], { type: 'application/pdf' }); + + // Uploader le PDF sur Supabase Storage + const fileName = `contract_${contractId}_${Date.now()}.pdf`; + const { data: uploadData, error: uploadError } = await supabase.storage + .from('contracts') + .upload(fileName, pdfBlob, { + contentType: 'application/pdf', + upsert: false, + }); + + if (uploadError) { + console.error('Erreur upload Supabase:', uploadError); + return NextResponse.json( + { error: 'Erreur lors de l\'upload du PDF' }, + { status: 500 } + ); + } + + // Mettre à jour le contrat avec l'URL du PDF + const { data: publicUrl } = supabase.storage + .from('contracts') + .getPublicUrl(fileName); + + const { error: updateError } = await supabase + .from('contracts') + .update({ pdf_url: publicUrl.publicUrl }) + .eq('id', contractId); + + if (updateError) { + console.error('Erreur mise à jour contrat:', updateError); + } + + return NextResponse.json({ + success: true, + pdfUrl: publicUrl.publicUrl, + fileName, + }); + + } catch (error) { + console.error('Erreur génération PDF:', error); + return NextResponse.json( + { error: 'Erreur interne du serveur' }, + { status: 500 } + ); + } +} diff --git a/docker-compose.gotenberg.yml b/docker-compose.gotenberg.yml new file mode 100644 index 0000000..ece1f3f --- /dev/null +++ b/docker-compose.gotenberg.yml @@ -0,0 +1,54 @@ +version: '3.8' + +services: + gotenberg: + image: gotenberg/gotenberg:8 + container_name: odentas-gotenberg + restart: unless-stopped + ports: + - "3001:3000" + environment: + # Configuration de sécurité + - GOTENBERG_API_TIMEOUT=30s + - GOTENBERG_API_ROOT_PATH=/ + + # Limites de ressources pour éviter la surcharge + - GOTENBERG_CHROMIUM_MAX_QUEUE_SIZE=10 + - GOTENBERG_CHROMIUM_AUTO_START=true + + # Options de conversion PDF + - GOTENBERG_CHROMIUM_DISABLE_JAVASCRIPT=false + - GOTENBERG_CHROMIUM_ALLOW_LIST=^file:///tmp/.* + + # Limites de ressources Docker + deploy: + resources: + limits: + cpus: '2' + memory: 2G + reservations: + cpus: '0.5' + memory: 512M + + # Health check pour vérifier que Gotenberg est opérationnel + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:3000/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + + # Volumes pour logs et cache (optionnel) + volumes: + - gotenberg-cache:/tmp/gotenberg + + networks: + - odentas-network + +volumes: + gotenberg-cache: + driver: local + +networks: + odentas-network: + driver: bridge diff --git a/lib/handlebars-helpers.ts b/lib/handlebars-helpers.ts new file mode 100644 index 0000000..f26127d --- /dev/null +++ b/lib/handlebars-helpers.ts @@ -0,0 +1,114 @@ +/** + * Helpers Handlebars personnalisés pour remplacer les filtres Liquid + * Utilisés pour la génération de contrats PDF avec Gotenberg + */ + +import Handlebars from 'handlebars'; + +/** + * Enregistre tous les helpers Handlebars personnalisés + */ +export function registerHandlebarsHelpers() { + // Helper pour remplacer "remove_first" de Liquid + Handlebars.registerHelper('removeFirst', function(str: string, pattern: string) { + if (!str) return ''; + return str.replace(pattern, ''); + }); + + // Helper pour remplacer "contains" de Liquid + Handlebars.registerHelper('contains', function(str: string, pattern: string) { + if (!str) return false; + return str.includes(pattern); + }); + + // Helper pour remplacer "strip" de Liquid (trim) + Handlebars.registerHelper('strip', function(str: string) { + if (!str) return ''; + return str.trim(); + }); + + // Helper pour remplacer "join" de Liquid + Handlebars.registerHelper('join', function(array: any[], separator: string) { + if (!Array.isArray(array)) return ''; + return array.join(separator); + }); + + // Helper pour split (diviser une chaîne) + Handlebars.registerHelper('split', function(str: string, delimiter: string) { + if (!str) return []; + return str.split(delimiter); + }); + + // Helper pour vérifier l'égalité + Handlebars.registerHelper('eq', function(a: any, b: any) { + return a === b; + }); + + // Helper pour vérifier l'inégalité + Handlebars.registerHelper('ne', function(a: any, b: any) { + return a !== b; + }); + + // Helper pour vérifier >= + Handlebars.registerHelper('gte', function(a: number, b: number) { + return a >= b; + }); + + // Helper pour vérifier > + Handlebars.registerHelper('gt', function(a: number, b: number) { + return a > b; + }); + + // Helper pour vérifier == + Handlebars.registerHelper('lte', function(a: number, b: number) { + return a <= b; + }); + + // Helper pour vérifier < + Handlebars.registerHelper('lt', function(a: number, b: number) { + return a < b; + }); + + // Helper pour vérifier si une valeur est vide + Handlebars.registerHelper('isEmpty', function(value: any) { + return !value || value === '' || value === 'n/a' || value === 0; + }); + + // Helper pour vérifier si une valeur n'est pas vide + Handlebars.registerHelper('isNotEmpty', function(value: any) { + return value && value !== '' && value !== 'n/a' && value !== 0; + }); + + // Helper OR logique + Handlebars.registerHelper('or', function(...args: any[]) { + // Enlever le dernier argument qui est l'objet options de Handlebars + const values = args.slice(0, -1); + return values.some(v => !!v); + }); + + // Helper AND logique + Handlebars.registerHelper('and', function(...args: any[]) { + // Enlever le dernier argument qui est l'objet options de Handlebars + const values = args.slice(0, -1); + return values.every(v => !!v); + }); + + // Helper pour formatter un nombre avec virgules + Handlebars.registerHelper('formatNumber', function(value: number) { + if (value === null || value === undefined) return ''; + return value.toString().replace('.', ','); + }); + + // Helper raw pour éviter l'échappement HTML + Handlebars.registerHelper('raw', function(options) { + return options.fn(); + }); + + // Helper pour comparer avec plusieurs valeurs (utile pour les CCN) + Handlebars.registerHelper('includesAny', function(str: string, ...values: any[]) { + if (!str) return false; + // Enlever le dernier argument (options de Handlebars) + const searchValues = values.slice(0, -1); + return searchValues.some(v => str.includes(v)); + }); +} diff --git a/templates-contrats/cddu-handlebars.html b/templates-contrats/cddu-handlebars.html new file mode 100644 index 0000000..5931f4c --- /dev/null +++ b/templates-contrats/cddu-handlebars.html @@ -0,0 +1,390 @@ + + + + + + + + +
+ +
+ +

CONTRAT D'ENGAGEMENT +{{#eq employee_catpro "Artiste"}}ARTISTE{{/eq}} +{{#if (and (eq employee_civ "Madame") (eq employee_catpro "Technicien"))}}TECHNICIENNE{{/if}} +{{#if (and (eq employee_civ "Monsieur") (eq employee_catpro "Technicien"))}}TECHNICIEN{{/if}} +{{#if (and (eq employee_civ "Madame") (eq employee_catpro "Metteur en scène"))}}
ARTISTE CADRE{{/if}} +{{#if (and (eq employee_civ "Monsieur") (eq employee_catpro "Metteur en scène"))}}ARTISTE CADRE{{/if}} +

+ +

Entre les {{#eq employee_civ "Monsieur"}}soussignés{{else}}soussignées{{/eq}} :

+ + +

d'une part,

+ +

et :

+ + + +

d'autre part.

+ +

+ Le présent contrat est conclu dans le cadre de la législation du travail, des usages en vigueur dans la + profession, de l'article L. 1242-2° du Code du travail et de l'accord interbranche sur le recours au + contrat à durée déterminée d'usage dans le spectacle du 12/10/1998. Il est, en outre, régi par les + dispositions de la {{join CCN ', '}}{{#if (contains (join CCN ', ') "Convention Collective Nationale de l'Édition")}} et de ses annexes afférentes à l'Édition Phonographique{{/if}}. +

+ +

+ Il a été convenu et arrêté ce qui suit : +

+ +
+

OBJET

+ {{employee_civ}} {{employee_firstname}} {{employee_lastname}} est {{#eq employee_civ "Monsieur"}}engagé{{else}}engagée{{/eq}} selon l'objet suivant : +
    +
  • Profession : {{employee_profession}}
  • +
  • Code emploi : {{employee_codeprofession}}
  • +
  • + {{#if (and (eq structure_spectacle "Oui") (ne type_numobjet "Administratif"))}} + Spectacle : {{ spectacle }} + {{else if (includesAny (join CCN ', ') "Convention Collective Nationale de la Production Audiovisuelle" "Convention Collective Nationale de l'Édition")}} + Production : {{ spectacle }} + {{/if}} +
  • +
  • {{#isNotEmpty numobjet}}Numéro d'objet : {{numobjet}}{{else}}Le numéro d'objet de cette production est en cours d'attribution.{{/isNotEmpty}}
  • +
+
+ +
+

DURÉE DE L'ENGAGEMENT

+

+ {{#eq date_debut date_fin}} + Le présent engagement couvre la journée du {{ date_debut }}, pour + {{else}} + {{#isNotEmpty dates_travaillees}} + Le présent engagement couvre la période du {{ date_debut }} au {{ date_fin }} pour les dates travaillées suivantes : +

    + {{#each (split dates_travaillees ";")}} +
  • - {{strip this}}{{#unless @last}};{{/unless}}
  • + {{/each}} +
+ {{else}} + Le présent engagement couvre la période du {{ date_debut }} au {{ date_fin }}. + {{/isNotEmpty}} + Pour + {{/eq}} + + {{#eq employee_catpro "Artiste"}} + un total de + {{#if (and (gte cachets.representations 1) (gte cachets.repetitions 1))}} + {{cachets.representations}} {{#eq cachets.representations 1}}cachet{{else}}cachets{{/eq}} de représentation et {{cachets.repetitions}} {{#eq cachets.repetitions 1}}service{{else}}services{{/eq}} de répétition. + {{/if}} + {{#if (and (gte cachets.representations 1) (eq cachets.repetitions 0))}} + {{cachets.representations}} {{#eq cachets.representations 1}}cachet{{else}}cachets{{/eq}}{{#if (includesAny (join CCN ', ') "Convention Collective Nationale de la Production Audiovisuelle" "Convention Collective Nationale de l'Édition")}} d'enregistrement.{{else}} de représentation.{{/if}} + {{/if}} + {{#if (and (eq cachets.representations 0) (gte cachets.repetitions 1))}} + {{cachets.repetitions}} {{#eq cachets.repetitions 1}}service{{else}}services{{/eq}} de répétition. + {{/if}} + {{/eq}} + + {{#eq employee_catpro "Technicien"}} + un total de {{cachets.heures}} heures de travail{{#eq cachets.heuresparjour 0}}.{{/eq}}{{#gte cachets.heuresparjour 1}}, à raison de {{cachets.heuresparjour}} heures par jour de travail.{{/gte}} + {{/eq}} + + {{#eq employee_catpro "Metteur en scène"}} + {{#if (and (gte cachets.representations 1) (gt cachets.heures 0))}} + un total de {{cachets.representations}} {{#eq cachets.representations 1}}cachet{{else}}cachets{{/eq}} de représentation et {{cachets.heures}} heures de travail. + {{/if}} + {{#if (eq cachets.representations 0)}} + un total de {{cachets.heures}} heures de travail. + {{/if}} + {{#if (and (gte cachets.representations 1) (eq cachets.heures 0))}} + un total de {{cachets.representations}} {{#eq cachets.representations 1}}cachet{{else}}cachets{{/eq}} de représentation. + {{/if}} + {{/eq}} +

+ +

+ {{#if (gte cachets.repetitions 1)}}La durée totale des répétitions sera de {{cachets.heures}} heures{{#eq cachets.heuresparjour 0}}.{{/eq}}{{#gte cachets.heuresparjour 1}}, à raison de {{cachets.heuresparjour}} heures par journée de répétition.{{/gte}}{{/if}} +

+ {{#isNotEmpty autreprecision_duree}}

{{autreprecision_duree}}

{{/isNotEmpty}} +
+ +
+ Il ne nous sera, en aucun cas, fait obligation de proroger le présent engagement à expiration. La fin de la période d'engagement prévue + aux présentes, prorogée éventuellement de la durée de dépassement, en constitue le terme. Il n'y a lieu à aucun préavis. +
+ +
+

LIEUX D'ENGAGEMENT ET HORAIRES DE TRAVAIL

+

+ {{structure_name}} communiquera à {{employee_firstname}} {{employee_lastname}} les lieux + {{#if (includesAny (join CCN ', ') "Convention Collective Nationale de la Production Audiovisuelle" "Convention Collective Nationale de l'Édition")}} + de travail + {{else if (and (gte cachets.representations 1) (eq cachets.repetitions 0))}} + des représentations + {{else if (and (eq cachets.representations 0) (gte cachets.repetitions 1))}} + des répétitions + {{else if (eq employee_catpro "Technicien")}} + d'engagement + {{else if (and (gte cachets.representations 1) (gte cachets.repetitions 1))}} + des répétitions et des représentations + {{/if}} + {{#if (and (eq employee_catpro "Metteur en scène") (eq cachets.representations 0))}}d'exercice de sa fonction{{/if}}, ainsi que ses horaires de travail. +

+
+ +
+

RÉMUNÉRATION

+

+ Il sera alloué à {{employee_firstname}} {{employee_lastname}} à titre de salaire la somme de {{salaire_brut}} euros bruts. +

+ {{#isNotEmpty precisions_salaire}} +

+ À titre informatif, la répartition de ce salaire brut est la suivante : {{precisions_salaire}}. +

+ {{/isNotEmpty}} + {{#if (and (isNotEmpty panierrepas) (isNotEmpty hebergement))}} +

{{employee_firstname}} {{employee_lastname}} percevra {{panierrepas}} + {{#eq panierrepas "1"}}panier repas principal{{else}}paniers repas principaux,{{/eq}} et {{hebergement}} {{#eq hebergement "1"}}indemnité{{else}}indemnités{{/eq}} d'hébergement et petit-déjeuner, + {{#if (and (eq panierrepasccn "Oui") (eq hebergementccn "Oui"))}} + selon les conditions prévues par la Convention Collective. + {{else if (and (eq panierrepasccn "Non") (eq hebergementccn "Oui"))}} + à hauteur de {{montantpanierrepas}} euros par panier repas principal, et selon les conditions prévues par la Convention Collective pour l'indemnité hébergement et petit-déjeuner. + {{else if (and (eq panierrepasccn "Oui") (eq hebergementccn "Non"))}} + selon les conditions prévues par la Convention Collective pour les paniers repas principaux, et à hauteur de {{montanthebergement}} euros par indemnité hébergement et petit-déjeuner. + {{else if (and (eq panierrepasccn "Non") (eq hebergementccn "Non"))}} + à hauteur de {{montantpanierrepas}} euros par panier repas principal et à hauteur de {{montanthebergement}} euros par indemnité hébergement et petit-déjeuner. + {{/if}} +

+ {{/if}} + {{#isNotEmpty autreprecision_salaire}}

{{autreprecision_salaire}}

{{/isNotEmpty}} +
+ +
+

RETRAITE ET CONGÉS PAYÉS

+

+ Les cotisations de retraite seront versées à AUDIENS - 7 rue Jean Bleuzen - 92177 VANVES Cedex. L'employeur acquittera ses + contributions à la caisse des Congés Spectacles conformément à la législation et dans la limite des plafonds applicables en vigueur. +

+
+ +
+

ABSENCE-MALADIE

+

+ En cas de maladie ou d'empêchement d'assurer + {{#if (eq employee_catpro "Metteur en scène")}} + ses missions de mise en scène, + {{else if (contains (join CCN ', ') "Convention Collective Nationale de la Production Audiovisuelle")}} + ses missions de {{employee_profession}}, + {{else if (contains (join CCN ', ') "Convention Collective Nationale de l'Édition")}} + un enregistrement, + {{else}} + une répétition ou une représentation, + {{/if}} + {{employee_firstname}} {{employee_lastname}} sera {{#eq employee_civ "Monsieur"}}tenu{{else}}tenue{{/eq}} + d'en aviser {{structure_name}} dans un délai de 24 heures en précisant la durée probable de son absence. En cas de prolongation d'arrêt de travail, + {{employee_firstname}} {{employee_lastname}} devra transmettre à {{structure_name}}, dans les plus brefs délais, le certificat médical + justifiant de cette prolongation. En tout état de cause, les parties conviennent expressément qu'en cas de maladie de {{employee_firstname}} {{employee_lastname}}, + le présent contrat pourra être résilié de plein droit par {{structure_name}} et ce, dans le respect des dispositions de la convention collective applicable. +

+
+ +
+

DROIT DE PRIORITÉ ET D'EXCLUSIVITÉ

+

+ Le présent contrat donne à {{structure_name}} une priorité absolue sur tous les autres engagements que pourrait conclure par ailleurs {{employee_firstname}} {{employee_lastname}}, sur la période de l'engagement. + La dérogation éventuelle à cette clause devra faire l'objet d'un accord écrit de {{structure_name}}. +

+

+ {{employee_firstname}} {{employee_lastname}} ne pourra en aucun cas refuser sa présence + {{#if (eq employee_catpro "Metteur en scène")}} + sur ses lieux de travail et aux répétitions + {{else if (contains (join CCN ', ') "Convention Collective Nationale de la Production Audiovisuelle")}} + sur les lieux de production + {{else if (contains (join CCN ', ') "Convention Collective Nationale de l'Édition")}} + sur les lieux d'enregistrement + {{else}} + à une répétition ou à une représentation + {{/if}} + pour cause d'engagement extérieur, à quelque moment qu'il·elle ait été prévenu + {{#if (eq employee_catpro "Metteur en scène")}} + de ses horaires et jours de travail et de l'existence de répétitons. + {{else if (contains (join CCN ', ') "Convention Collective Nationale de la Production Audiovisuelle")}} + de ses horaires, jours et lieux de travail. + {{else if (contains (join CCN ', ') "Convention Collective Nationale de l'Édition")}} + de cet session d'enregistrement. + {{else}} + de l'existence de cette répétition ou représentation. + {{/if}} +

+
+ +
+

MÉDECINE DU TRAVAIL

+

+ {{employee_firstname}} {{employee_lastname}} déclare avoir satisfait aux obligations relatives à la Médecine du travail et communiquera + à {{structure_name}} l'attestation annuelle qui lui a été délivrée par cet organisme. +

+
+ +
+

ASSURANCES

+

+ {{employee_firstname}} {{employee_lastname}} est {{#eq employee_civ "Monsieur"}}tenu{{else}}tenue{{/eq}} d'assurer contre tous les risques tous les objets lui appartenant. {{structure_name}} + déclare avoir souscrit les assurances nécessaires à la couverture des risques liés{{#if (contains (join CCN ', ') "Convention Collective Nationale de la Production Audiovisuelle")}} à la production audiovisuelle.{{else if (contains (join CCN ', ') "Convention Collective Nationale de l'Édition")}} à l'édition phonographique.{{else}} aux représentations du spectacle.{{/if}} +

+
+ +
+

LITIGES

+

+ En cas de litige portant sur l'interprétation ou l'application du présent contrat, les parties conviennent de s'en remettre à l'appréciation des + tribunaux compétents, mais seulement après épuisement des voies amiables (conciliation, arbitrage). +

+
+ +
+

PROTECTION DES DONNÉES PERSONNELLES

+

+ Aux fins de gestion du personnel et de traitement des rémunérations, nous sommes amenés à solliciter des données personnelles vous concernant + à l'occasion de la conclusion, l'exécution et le cas échéant, la rupture de votre contrat de travail. +

+

+ La signature du présent contrat vaut autorisation pour la société de collecter, d'enregistrer et de stocker les données nécessaires. +

+

+ Outre les services internes de {{structure_name}}, les destinataires de ces données sont, à ce jour, les organismes de sécurité sociale, + les caisses de retraite et de prévoyance, la mutuelle, France Travail Spectacle, les services des impôts, le service de médecine du travail, les organismes conventionnels et la société + Odentas Media SAS, notre prestataire de gestion de la paie. +

+

+ Ces informations sont réservées à l'usage des services concernés et ne peuvent être communiquées qu'à ces destinataires. +

+

+ Vous bénéficiez notamment d'un droit d'accès, de rectification et d'effacement des informations vous concernant, que vous pouvez exercer + en adressant directement une demande au responsable de ces traitements : {{nom_responsable_traitement}}, {{qualite_responsable_traitement}}, {{email_responsable_traitement}}. +

+
+ +
+

+ Fait en double exemplaire, +

+

+ {{#if (contains structure_ville "Le ")}} + Au {{removeFirst structure_ville "Le "}}, le {{ date_signature }}. + {{else}} + À {{ structure_ville }}, le {{ date_signature }}. + {{/if}} +

+
+ +
+
+
{{#eq employee_civ "Monsieur"}}Le salarié :{{else}}La salariée :{{/eq}}
+
{{employee_civ}} {{employee_firstname}} {{employee_lastname}}
+
+
[Signature Employé - À intégrer via Docuseal]
+



+
+ {{#eq mineur1618 "Oui"}} +
+
{{#eq representant_civ "Monsieur"}}Le représentant légal{{else}}La représentante légale{{/eq}}{{#eq employee_civ "Madame"}} de la salariée :{{else}} du salarié :{{/eq}}
+
{{representant_civ}} {{representant_nom}}
+
[Signature Représentant - À intégrer via Docuseal]
+

+
+ {{/eq}} +
+
L'employeur:
+
Pour {{structure_name}},
+ {{#eq delegation "Oui"}}
Pour le représentant légal et par délégation,
{{/eq}} +
{{structure_signataire}},
+
{{structure_signatairequalite}}.
+
+
[Signature Employeur - À intégrer via Docuseal]
+
+
+ + +