{
  "name": "Generation des conventions de formation",
  "nodes": [
    {
      "id": "0afcb596-7724-45df-a0a5-f4d0f9878808",
      "name": "Note dossiers",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -460,
        -340
      ],
      "parameters": {
        "color": 4,
        "width": 520,
        "height": 280,
        "content": "## 1. Onglet Dossiers (Google Sheets)\n\n`id_dossier` | `raison_sociale` | `siret` | `adresse_client` | `email_signataire` | `nom_signataire` | `titre_formation` | `objectifs` | `date_debut` | `date_fin` | `duree_heures` | `nb_stagiaires` | `prix_ht` | `modalite` | `lieu` | `statut_convention`\n\nLa convention est generee lorsque `statut_convention` est vide ou vaut `a_generer`."
      },
      "typeVersion": 1
    },
    {
      "id": "7bf2d611-dbb2-447e-ac41-90799ff97a48",
      "name": "Note pdf",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        640,
        -340
      ],
      "parameters": {
        "color": 3,
        "width": 460,
        "height": 280,
        "content": "## 2. Conversion PDF (optionnelle)\n\nLe node HTTP Request envoie le HTML a un convertisseur PDF de votre choix (Gotenberg auto-heberge, PDFShift, Docamatic, API interne...).\n\nRemplacez l'URL et la methode d'authentification. Le node est en `continueRegularOutput` : si la conversion echoue, l'email part quand meme avec la convention en HTML."
      },
      "typeVersion": 1
    },
    {
      "id": "c0dd3abb-f88e-4969-af8d-51e5cc5e386a",
      "name": "Note signature",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -460,
        320
      ],
      "parameters": {
        "color": 5,
        "width": 460,
        "height": 200,
        "content": "## 3. Retour de signature\n\n`POST /convention-signee` avec :\n`{ id_dossier, signataire, date_signature, url_document }`\n\nLa ligne du dossier passe alors en `signee` : c'est votre preuve pour l'audit."
      },
      "typeVersion": 1
    },
    {
      "id": "1457ca5a-80e2-49fc-a3d2-7b974f5bc51e",
      "name": "Chaque jour a 8h",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -420,
        -20
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 8 * * 1-5"
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "56949e36-85a0-41d7-acd3-c4fc7ec53079",
      "name": "Lire les dossiers de formation",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        -200,
        -20
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Dossiers"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "REMPLACER_PAR_ID_DU_GOOGLE_SHEET"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "0419b000-b4af-4470-a24c-d9a2bdd4d23a",
      "name": "Reperer les conventions a generer",
      "type": "n8n-nodes-base.code",
      "position": [
        20,
        -20
      ],
      "parameters": {
        "jsCode": "// Ne conserve que les dossiers dont la convention reste a produire.\nconst A_GENERER = ['', 'a_generer', 'a generer', 'todo'];\n\nreturn $input.all()\n  .filter((item) => {\n    const statut = String(item.json.statut_convention || '').trim().toLowerCase();\n    return A_GENERER.includes(statut) && Boolean(item.json.email_signataire);\n  })\n  .map((item) => ({ json: { ...item.json, source: 'planification' } }));"
      },
      "typeVersion": 2
    },
    {
      "id": "5883d80b-4d5b-42aa-a135-5939b2fbd9ae",
      "name": "Convention a generer ?",
      "type": "n8n-nodes-base.if",
      "position": [
        240,
        -20
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "353a13d5-da47-4622-aba4-5fb016ba87cc",
              "operator": {
                "type": "string",
                "operation": "notEmpty",
                "singleValue": true
              },
              "leftValue": "={{ $json.email_signataire }}",
              "rightValue": ""
            }
          ]
        },
        "looseTypeValidation": true
      },
      "typeVersion": 2.2
    },
    {
      "id": "816b2461-93d2-49c7-a4df-3cac29a99bd9",
      "name": "Aucune convention a generer",
      "type": "n8n-nodes-base.noOp",
      "position": [
        460,
        180
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "48bb2757-d3a5-4659-a1b2-ad773538b549",
      "name": "Calculer duree prix et mentions legales",
      "type": "n8n-nodes-base.code",
      "position": [
        460,
        -160
      ],
      "parameters": {
        "jsCode": "// Calcule les montants et prepare les mentions obligatoires (art. L6353-1 / D6353-1).\nconst TVA = 0.2;          // mettre 0 si exoneration art. 261-4-4 a du CGI\nconst EXONERATION = false;\n\nreturn $input.all().map((item) => {\n  const l = item.json;\n\n  const heures = Number(l.duree_heures || 0);\n  const stagiaires = Number(l.nb_stagiaires || 1);\n  const prixHt = Number(String(l.prix_ht || 0).replace(',', '.'));\n  const tva = EXONERATION ? 0 : Math.round(prixHt * TVA * 100) / 100;\n  const prixTtc = Math.round((prixHt + tva) * 100) / 100;\n\n  const mentions = [\n    'Objet, nature et duree de l action de formation',\n    'Effectif forme et modalites de deroulement',\n    'Prix de l action et modalites de reglement',\n    'Moyens pedagogiques, techniques et d encadrement',\n    'Modalites d evaluation et de sanction de la formation',\n    'Conditions de dedit et de resiliation',\n  ];\n\n  return {\n    json: {\n      ...l,\n      duree_heures: heures,\n      nb_stagiaires: stagiaires,\n      prix_ht: prixHt,\n      montant_tva: tva,\n      prix_ttc: prixTtc,\n      cout_horaire: heures > 0 ? Math.round((prixHt / heures) * 100) / 100 : 0,\n      exoneration_tva: EXONERATION,\n      mentions_obligatoires: mentions,\n      reference_convention: 'CONV-' + new Date().getFullYear() + '-' + String(l.id_dossier || '').padStart(4, '0'),\n    },\n  };\n});"
      },
      "typeVersion": 2
    },
    {
      "id": "6c332b13-9bae-4529-a1c1-6a2641e24f3b",
      "name": "Generer la convention",
      "type": "n8n-nodes-base.code",
      "position": [
        680,
        -160
      ],
      "parameters": {
        "jsCode": "// Produit le HTML de la convention de formation professionnelle.\nconst ORGANISME = {\n  nom: 'Votre Organisme de Formation',\n  adresse: '1 rue de la Formation, 75000 Paris',\n  siret: '000 000 000 00000',\n  nda: '00000000000',\n  representant: 'Prenom Nom, gerant',\n};\n\nconst euro = (n) => Number(n || 0).toLocaleString('fr-FR', { minimumFractionDigits: 2 }) + ' EUR';\nconst fr = (d) => {\n  const date = new Date(d);\n  return isNaN(date.getTime()) ? String(d) : date.toLocaleDateString('fr-FR');\n};\n\nreturn $input.all().map((item) => {\n  const l = item.json;\n  const h = [];\n\n  h.push('<h1>Convention de formation professionnelle</h1>');\n  h.push('<p>(Articles L6353-1 et suivants du Code du travail)</p>');\n  h.push('<p>Reference : <strong>' + l.reference_convention + '</strong></p>');\n  h.push('<h3>Entre les soussignes</h3>');\n  h.push('<p><strong>' + ORGANISME.nom + '</strong>, ' + ORGANISME.adresse + ', SIRET ' + ORGANISME.siret + ', declaration d activite n. ' + ORGANISME.nda + ', represente par ' + ORGANISME.representant + ' (ci-apres l organisme de formation)</p>');\n  h.push('<p>et</p>');\n  h.push('<p><strong>' + (l.raison_sociale || '') + '</strong>, ' + (l.adresse_client || '') + ', SIRET ' + (l.siret || '') + ', represente par ' + (l.nom_signataire || '') + ' (ci-apres le client)</p>');\n  h.push('<h3>Article 1 - Objet</h3>');\n  h.push('<p>L organisme organise l action de formation suivante : <strong>' + (l.titre_formation || '') + '</strong>.</p>');\n  h.push('<p>Objectifs : ' + (l.objectifs || '') + '</p>');\n  h.push('<h3>Article 2 - Nature, duree et effectif</h3>');\n  h.push('<p>Action de formation - ' + (l.modalite || 'presentiel') + ' - ' + l.duree_heures + ' heures, du ' + fr(l.date_debut) + ' au ' + fr(l.date_fin) + ', ' + l.nb_stagiaires + ' stagiaire(s). Lieu : ' + (l.lieu || '') + '.</p>');\n  h.push('<h3>Article 3 - Prix</h3>');\n  h.push('<p>Prix HT : ' + euro(l.prix_ht) + ' - TVA : ' + euro(l.montant_tva) + ' - Total : <strong>' + euro(l.prix_ttc) + '</strong>' + (l.exoneration_tva ? ' (exoneration art. 261-4-4 a du CGI)' : '') + '</p>');\n  h.push('<h3>Article 4 - Mentions obligatoires</h3><ul>');\n  for (const m of l.mentions_obligatoires || []) h.push('<li>' + m + '</li>');\n  h.push('</ul>');\n  h.push('<h3>Article 5 - Dedit et resiliation</h3>');\n  h.push('<p>En cas de renoncement du client moins de 14 jours avant le debut de l action, l organisme retient 30 % du prix a titre de dedommagement. En cas de realisation partielle, seules les heures realisees sont facturees.</p>');\n  h.push('<h3>Article 6 - Litiges</h3>');\n  h.push('<p>A defaut d accord amiable, le litige sera porte devant le tribunal competent du ressort du siege de l organisme.</p>');\n  h.push('<p>Fait en deux exemplaires, le ' + fr(new Date()) + '.</p>');\n\n  return {\n    json: {\n      ...l,\n      convention_html: h.join(''),\n      nom_fichier: l.reference_convention + '.pdf',\n      generee_le: new Date().toISOString(),\n    },\n  };\n});"
      },
      "typeVersion": 2
    },
    {
      "id": "d32e4a1b-2212-482d-aa3b-faa01643aa12",
      "name": "Convertir en PDF",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        900,
        -160
      ],
      "parameters": {
        "url": "https://votre-service-pdf.exemple/v1/convert",
        "method": "POST",
        "options": {},
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ html: $json.convention_html, filename: $json.nom_fichier }) }}"
      },
      "typeVersion": 4.2,
      "onError": "continueRegularOutput"
    },
    {
      "id": "8f1eb77e-d3bd-46d5-a388-c80662f60daf",
      "name": "Envoyer la convention pour signature",
      "type": "n8n-nodes-base.gmail",
      "position": [
        1120,
        -160
      ],
      "parameters": {
        "sendTo": "={{ $('Generer la convention').item.json.email_signataire }}",
        "subject": "=Convention de formation {{ $('Generer la convention').item.json.reference_convention }} - {{ $('Generer la convention').item.json.titre_formation }}",
        "message": "=<p>Bonjour {{ $('Generer la convention').item.json.nom_signataire }},</p><p>Veuillez trouver ci-dessous la convention de formation professionnelle relative a l'action <strong>{{ $('Generer la convention').item.json.titre_formation }}</strong>.</p><p>Merci de nous la retourner sign&eacute;e avant le d&eacute;but de la formation.</p><hr/>{{ $('Generer la convention').item.json.convention_html }}",
        "options": {
          "appendAttribution": false
        },
        "emailType": "html",
        "resource": "message",
        "operation": "send"
      },
      "typeVersion": 2.1,
      "webhookId": "72c6698d-7efb-448a-a252-09dfe9cb34bb"
    },
    {
      "id": "4cdf9851-bc7e-4bdf-a3a5-74f054b38d71",
      "name": "Mettre a jour le suivi des conventions",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1340,
        -160
      ],
      "parameters": {
        "columns": {
          "value": {
            "id_dossier": "={{ $('Generer la convention').item.json.id_dossier }}",
            "reference_convention": "={{ $('Generer la convention').item.json.reference_convention }}",
            "statut_convention": "envoyee_pour_signature",
            "convention_generee_le": "={{ $('Generer la convention').item.json.generee_le }}",
            "prix_ttc": "={{ $('Generer la convention').item.json.prix_ttc }}"
          },
          "schema": [
            {
              "id": "id_dossier",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "id_dossier",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "reference_convention",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "reference_convention",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "statut_convention",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "statut_convention",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "convention_generee_le",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "convention_generee_le",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "prix_ttc",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "prix_ttc",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "id_dossier"
          ]
        },
        "options": {},
        "operation": "appendOrUpdate",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Dossiers"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "REMPLACER_PAR_ID_DU_GOOGLE_SHEET"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "eab50be9-b813-4f71-a3f3-d7296f8e84ab",
      "name": "Convention signee",
      "type": "n8n-nodes-base.webhook",
      "position": [
        -420,
        420
      ],
      "parameters": {
        "path": "convention-signee",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "responseNode"
      },
      "typeVersion": 2,
      "webhookId": "086c9339-824c-4d99-a057-3cf73f50e4a7",
      "onError": "continueRegularOutput"
    },
    {
      "id": "2fd3b94e-72c0-4c87-a85c-1fe608075ec7",
      "name": "Normaliser le retour de signature",
      "type": "n8n-nodes-base.set",
      "position": [
        -200,
        420
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "719dde2d-fd97-4dbf-a211-6c430dbdb7f5",
              "name": "id_dossier",
              "type": "string",
              "value": "={{ $json.body.id_dossier }}"
            },
            {
              "id": "610f999f-df28-4d44-a8fe-3a1b50c58da7",
              "name": "signataire",
              "type": "string",
              "value": "={{ $json.body.signataire }}"
            },
            {
              "id": "cabd57b8-3fe5-4f0c-af29-0dc9185c0b7d",
              "name": "date_signature",
              "type": "string",
              "value": "={{ $json.body.date_signature || $now.toISO() }}"
            },
            {
              "id": "7f6ef354-4862-4e55-ae9e-b30b0fc054f7",
              "name": "url_document",
              "type": "string",
              "value": "={{ $json.body.url_document || '' }}"
            }
          ]
        },
        "includeOtherFields": false
      },
      "typeVersion": 3.4
    },
    {
      "id": "d7b2f02d-1aac-42de-ab9c-a87c3236b87c",
      "name": "Archiver la convention signee",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        20,
        420
      ],
      "parameters": {
        "columns": {
          "value": {
            "id_dossier": "={{ $json.id_dossier }}",
            "statut_convention": "signee",
            "convention_signee_le": "={{ $json.date_signature }}",
            "url_convention": "={{ $json.url_document }}"
          },
          "schema": [
            {
              "id": "id_dossier",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "id_dossier",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "statut_convention",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "statut_convention",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "convention_signee_le",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "convention_signee_le",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "url_convention",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "url_convention",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "id_dossier"
          ]
        },
        "options": {},
        "operation": "appendOrUpdate",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Dossiers"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "REMPLACER_PAR_ID_DU_GOOGLE_SHEET"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "8922cd49-a148-4863-aaa3-0e6f46336285",
      "name": "Accuser reception",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        240,
        420
      ],
      "parameters": {
        "options": {},
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify({ status: 'ok', id_dossier: $json.id_dossier }) }}"
      },
      "typeVersion": 1.1
    }
  ],
  "pinData": {},
  "connections": {
    "Chaque jour a 8h": {
      "main": [
        [
          {
            "node": "Lire les dossiers de formation",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Lire les dossiers de formation": {
      "main": [
        [
          {
            "node": "Reperer les conventions a generer",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Reperer les conventions a generer": {
      "main": [
        [
          {
            "node": "Convention a generer ?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Convention a generer ?": {
      "main": [
        [
          {
            "node": "Calculer duree prix et mentions legales",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Aucune convention a generer",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Calculer duree prix et mentions legales": {
      "main": [
        [
          {
            "node": "Generer la convention",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generer la convention": {
      "main": [
        [
          {
            "node": "Convertir en PDF",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Convertir en PDF": {
      "main": [
        [
          {
            "node": "Envoyer la convention pour signature",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Envoyer la convention pour signature": {
      "main": [
        [
          {
            "node": "Mettre a jour le suivi des conventions",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Convention signee": {
      "main": [
        [
          {
            "node": "Normaliser le retour de signature",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normaliser le retour de signature": {
      "main": [
        [
          {
            "node": "Archiver la convention signee",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Archiver la convention signee": {
      "main": [
        [
          {
            "node": "Accuser reception",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "605f99ff-3ebc-44a4-afff-1003415ede07",
  "meta": {
    "instanceId": "5f7c1a2b9d3e4f60a8b7c6d5e4f3021998af77bc12de34ff56aa7788bb99ccdd",
    "templateCredsSetupCompleted": true
  },
  "tags": []
}
