Système de traitement de factures pour la soumission

Avancé

Ceci est unInvoice Processing, AI Summarizationworkflow d'automatisation du domainecontenant 24 nœuds.Utilise principalement des nœuds comme If, Code, Xero, Gmail, Slack. Automatisation du traitement des factures avec Gmail, OCR.space, Slack et Xero

Prérequis
  • Compte Google et informations d'identification Gmail API
  • Token Bot Slack ou URL Webhook
  • Peut nécessiter les informations d'identification d'authentification de l'API cible
  • Informations d'identification Google Sheets API
Aperçu du workflow
Visualisation des connexions entre les nœuds, avec support du zoom et du déplacement
Exporter le workflow
Copiez la configuration JSON suivante dans n8n pour importer et utiliser ce workflow
{
  "id": "QHTTTSIjepK3ipPO",
  "meta": {
    "instanceId": "a9990cea8ba499cd2caa870c8ec5e719a88b31e6ed62907b6084053e5dc1857a",
    "templateCredsSetupCompleted": true
  },
  "name": "Invoice Processing System for submission",
  "tags": [
    {
      "id": "CMJL1dF3mMlRu0Jb",
      "name": "Invoice Processing",
      "createdAt": "2025-10-11T20:13:04.997Z",
      "updatedAt": "2025-10-11T20:13:04.997Z"
    }
  ],
  "nodes": [
    {
      "id": "189e9199-b377-4acc-a0e7-d5769e69a762",
      "name": "Gmail Trigger",
      "type": "n8n-nodes-base.gmailTrigger",
      "position": [
        -96,
        16
      ],
      "parameters": {
        "simple": false,
        "filters": {
          "q": "has:attachment filename:pdf"
        },
        "options": {
          "downloadAttachments": true
        },
        "pollTimes": {
          "item": [
            {
              "mode": "everyMinute"
            }
          ]
        }
      },
      "credentials": {
        "gmailOAuth2": {
          "id": "d2kfFLUV2qlW2o43",
          "name": "Gmail account"
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "7c5278ea-c32f-4f55-9ba4-4448c06c21f0",
      "name": "Code en JavaScript",
      "type": "n8n-nodes-base.code",
      "position": [
        80,
        16
      ],
      "parameters": {
        "jsCode": "// Get email data\nconst email = $input.item.json;\nconst binary = $input.item.binary;\n\n// Check if we have binary attachments\nif (!binary || Object.keys(binary).length === 0) {\n  return { \n    json: { \n      error: 'No invoice attachment found',\n      emailId: email.id,\n      emailSubject: email.subject\n    } \n  };\n}\n\n// Find first PDF or image attachment\nlet invoiceFile = null;\nlet invoiceKey = null;\n\nfor (const key in binary) {\n  const file = binary[key];\n  if (file.mimeType?.includes('pdf') || \n      file.mimeType?.includes('image') ||\n      file.mimeType?.includes('png') ||\n      file.mimeType?.includes('jpg')) {\n    invoiceFile = file;\n    invoiceKey = key;\n    break;\n  }\n}\n\nif (!invoiceFile) {\n  return { \n    json: { \n      error: 'No PDF or image attachment found',\n      emailId: email.id,\n      emailSubject: email.subject\n    } \n  };\n}\n\n// The binary data in n8n is already base64 encoded\n// Just use it directly without re-encoding\nlet base64Data = invoiceFile.data;\n\n// If it's a Buffer, convert to base64 string\nif (Buffer.isBuffer(base64Data)) {\n  base64Data = base64Data.toString('base64');\n}\n\n// Build the data URL for OCR.space\nconst dataUrl = `data:${invoiceFile.mimeType};base64,${base64Data}`;\n\nreturn {\n  json: {\n    emailId: email.id,\n    emailSubject: email.subject,\n    emailFrom: email.from,\n    emailDate: email.date,\n    attachmentData: base64Data,\n    attachmentName: invoiceFile.fileName,\n    mimeType: invoiceFile.mimeType,\n    dataUrl: dataUrl\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "4ddb1479-1b7f-4f8d-ad41-1a92654c98c8",
      "name": "Ajouter ou mettre à jour une ligne dans la feuille",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1296,
        -48
      ],
      "parameters": {
        "columns": {
          "value": {
            "Amount": "={{ $json.amount }}",
            "Status": "Pending",
            "Vendor": "={{ $json.vendor }}",
            "Currency": "={{ $json.currency }}",
            "Due Date": "={{ $json.dueDate }}",
            "Email ID": "={{ $node[\"Code in JavaScript\"].json.emailId }}",
            "Timestamp": "={{ $now.format('yyyy-MM-dd HH:mm:ss') }}",
            "Description": "={{ $json.description }}",
            "Invoice Date": "={{ $json.invoiceDate }}",
            "Invoice Number": "={{ $json.invoiceNumber }}"
          },
          "schema": [
            {
              "id": "Invoice Number",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Invoice Number",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Vendor",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Vendor",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Amount",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Amount",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Currency",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Currency",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Invoice Date",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Invoice Date",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Due Date",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Due Date",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Description",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Description",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Email ID",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Email ID",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Status",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Status",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Timestamp",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Timestamp",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Processed By",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Processed By",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "Invoice Number"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "appendOrUpdate",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1TWE856dG0g0bSRr3M4SKPcpnJ32veWlq3m_DWJoSaTQ/edit#gid=0",
          "cachedResultName": "Sheet1"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "=\"YOUR_SPREADSHEET_ID\""
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "hVo2bSobLUbf5b3a",
          "name": "Google Sheets account"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "abea5730-c60f-47f4-83b3-50cb42c6c9e0",
      "name": "Slack Demande d'approbation",
      "type": "n8n-nodes-base.slack",
      "position": [
        1904,
        -48
      ],
      "webhookId": "4f4fa1f8-f072-4ceb-b802-68904622847d",
      "parameters": {
        "select": "channel",
        "message": "=🧾 *New Invoice Requires Approval*\n\n*Vendor:* {{ $json.vendor }}\n*Amount:* ${{ $json.amount }} {{ $json.currency }}\n*Invoice #:* {{ $json.invoiceNumber }}\n*Due Date:* {{ $json.dueDate }}\n*Description:* {{ $json.description }}",
        "options": {},
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "C09LDKZJGBB",
          "cachedResultName": "invoice-approvals"
        },
        "operation": "sendAndWait",
        "authentication": "oAuth2",
        "approvalOptions": {
          "values": {
            "approvalType": "double"
          }
        }
      },
      "credentials": {
        "slackOAuth2Api": {
          "id": "plTTzKNjrWG2CYjh",
          "name": "Slack account"
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "955f6439-8d79-4f9f-b35c-7bf3e39f6aad",
      "name": "Slack Message de rejet",
      "type": "n8n-nodes-base.slack",
      "position": [
        1296,
        144
      ],
      "webhookId": "e4b6a100-ab6c-4908-8909-a37c5c210aad",
      "parameters": {
        "text": "=Channel: #invoice-approvals\n\nText:\n❌ *Invoice Auto-Rejected*\n\nAn invoice was rejected by AI analysis:\n\n- From: {{ $node[\"Code in JavaScript\"].json.emailFrom }}\n- Subject: {{ $node[\"Code in JavaScript\"].json.emailSubject }}\n- Reason: Did not pass qualification checks\n- Confidence: {{ $json.confidence }}%\n- Red Flags: {{ $json.redFlags.join(', ') }}\n\nThe email has been labeled \"Rejected\" in Gmail.",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "C09LDKZJGBB",
          "cachedResultName": "invoice-approvals"
        },
        "otherOptions": {},
        "authentication": "oAuth2"
      },
      "credentials": {
        "slackOAuth2Api": {
          "id": "plTTzKNjrWG2CYjh",
          "name": "Slack account"
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "424382f8-ff99-457e-9714-569c83e29157",
      "name": "Vérifier la qualification",
      "type": "n8n-nodes-base.if",
      "position": [
        1040,
        0
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "1237bc0c-6f1a-4a3b-98ff-45369c8ebc06",
              "operator": {
                "type": "boolean",
                "operation": "equals"
              },
              "leftValue": "={{ $json.qualified }}",
              "rightValue": true
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "be9d4c45-2bb3-4ca4-944e-65045fa75a60",
      "name": "Analyser les données de la facture",
      "type": "n8n-nodes-base.code",
      "position": [
        720,
        0
      ],
      "parameters": {
        "jsCode": "// Get OCR result\nconst ocrResult = $json;\n\n// Extract the text\nlet extractedText = '';\n\nif (ocrResult.ParsedResults && ocrResult.ParsedResults[0]) {\n  extractedText = ocrResult.ParsedResults[0].ParsedText;\n} else {\n  return {\n    json: {\n      error: 'OCR failed to extract text',\n      qualified: false,\n      redFlags: ['Could not read invoice text']\n    }\n  };\n}\n\n// Helper function to parse dates correctly without timezone issues\nfunction parseDate(dateString) {\n  // Try to parse dates like \"October 1, 2025\"\n  const date = new Date(dateString + ' 12:00:00 UTC'); // Add noon UTC to avoid timezone shifts\n  \n  if (!isNaN(date)) {\n    const year = date.getUTCFullYear();\n    const month = String(date.getUTCMonth() + 1).padStart(2, '0');\n    const day = String(date.getUTCDate()).padStart(2, '0');\n    return `${year}-${month}-${day}`;\n  }\n  \n  return '';\n}\n\n// Parse invoice fields using regex\nconst invoiceData = {\n  qualified: true,\n  vendor: '',\n  invoiceNumber: '',\n  amount: 0,\n  currency: 'USD',\n  invoiceDate: '',\n  dueDate: '',\n  description: '',\n  confidence: 100,\n  redFlags: []\n};\n\n// Extract Vendor (line after \"From:\")\nconst vendorMatch = extractedText.match(/From:\\s*([^\\n]+)/i);\nif (vendorMatch) {\n  invoiceData.vendor = vendorMatch[1].trim();\n}\n\n// Extract Invoice Number\nconst invoiceNumMatch = extractedText.match(/Invoice Number:\\s*([^\\n]+)/i);\nif (invoiceNumMatch) {\n  invoiceData.invoiceNumber = invoiceNumMatch[1].trim();\n}\n\n// Extract Amount (from \"Total:\" line) - CRITICAL!\nconst totalMatch = extractedText.match(/Total:\\s*\\$\\s*([0-9,]+\\.?[0-9]*)/i);\nif (totalMatch) {\n  invoiceData.amount = parseFloat(totalMatch[1].replace(/,/g, ''));\n}\n\n// If Total not found, try Amount line\nif (invoiceData.amount === 0) {\n  const amountMatch = extractedText.match(/Amount:\\s*\\$\\s*([0-9,]+\\.?[0-9]*)/i);\n  if (amountMatch) {\n    invoiceData.amount = parseFloat(amountMatch[1].replace(/,/g, ''));\n  }\n}\n\n// Extract Currency\nconst currencyMatch = extractedText.match(/(USD|EUR|GBP|CAD)/i);\nif (currencyMatch) {\n  invoiceData.currency = currencyMatch[1].toUpperCase();\n}\n\n// Extract Invoice Date - FIXED!\nconst dateMatch = extractedText.match(/Date:\\s*([^\\n]+)/i);\nif (dateMatch) {\n  const dateStr = dateMatch[1].trim();\n  invoiceData.invoiceDate = parseDate(dateStr);\n}\n\n// Extract Due Date - FIXED!\nconst dueMatch = extractedText.match(/Due Date:\\s*([^\\n]+)/i);\nif (dueMatch) {\n  const dateStr = dueMatch[1].trim();\n  invoiceData.dueDate = parseDate(dateStr);\n}\n\n// Extract Description\nconst descMatch = extractedText.match(/Description:\\s*([^\\n]+)/i);\nif (descMatch) {\n  invoiceData.description = descMatch[1].trim();\n}\n\n// Validation checks\nif (!invoiceData.vendor) {\n  invoiceData.qualified = false;\n  invoiceData.redFlags.push('Missing vendor name');\n}\n\nif (!invoiceData.invoiceNumber) {\n  invoiceData.qualified = false;\n  invoiceData.redFlags.push('Missing invoice number');\n}\n\nif (invoiceData.amount === 0) {\n  invoiceData.qualified = false;\n  invoiceData.redFlags.push('Missing or invalid amount');\n}\n\nif (invoiceData.amount > 10000) {\n  invoiceData.qualified = false;\n  invoiceData.redFlags.push('Amount exceeds $10,000');\n}\n\nreturn {\n  json: invoiceData\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "36afc95e-af94-4c98-a458-744e9bfd1139",
      "name": "Formater pour Slack",
      "type": "n8n-nodes-base.code",
      "position": [
        1504,
        -48
      ],
      "parameters": {
        "jsCode": "const sheetData = $node[\"Append or update row in sheet\"].json;\n\nreturn {\n  json: {\n    vendor: sheetData.Vendor,\n    amount: sheetData.Amount,\n    currency: sheetData.Currency,\n    invoiceNumber: sheetData[\"Invoice Number\"],\n    invoiceDate: sheetData[\"Invoice Date\"],  // ← ADD THIS LINE!\n    dueDate: sheetData[\"Due Date\"],\n    description: sheetData.Description\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "b524a093-e827-4049-b0c9-809dc839465e",
      "name": "Message de rejet",
      "type": "n8n-nodes-base.slack",
      "position": [
        2896,
        16
      ],
      "webhookId": "019dcb52-375d-4fff-9e0d-d4d5b4ac0282",
      "parameters": {
        "text": "❌ Invoice rejected",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "C09LDKZJGBB",
          "cachedResultName": "invoice-approvals"
        },
        "otherOptions": {},
        "authentication": "oAuth2"
      },
      "credentials": {
        "slackOAuth2Api": {
          "id": "plTTzKNjrWG2CYjh",
          "name": "Slack account"
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "afcf2f08-41ec-4ce5-bb32-98733317379f",
      "name": "Message de succès",
      "type": "n8n-nodes-base.slack",
      "position": [
        3248,
        -176
      ],
      "webhookId": "ae7fc4b9-99a3-41ad-ba4e-4e4a15a52303",
      "parameters": {
        "text": "✅ Invoice posted to Xero",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "C09LDKZJGBB",
          "cachedResultName": "invoice-approvals"
        },
        "otherOptions": {},
        "authentication": "oAuth2"
      },
      "credentials": {
        "slackOAuth2Api": {
          "id": "plTTzKNjrWG2CYjh",
          "name": "Slack account"
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "2a70f5c0-db5f-44eb-8eb8-24c978892049",
      "name": "Mettre à jour le statut rejeté",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        2704,
        16
      ],
      "parameters": {
        "columns": {
          "value": {
            "Status": "Rejected",
            "Invoice Number": "={{ $('Clean Invoice Payload').item.json.invoiceNumber }}"
          },
          "schema": [
            {
              "id": "Invoice Number",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Invoice Number",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Vendor",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Vendor",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Amount",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Amount",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Currency",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Currency",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Invoice Date",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Invoice Date",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Due Date",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Due Date",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Description",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Description",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Email ID",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Email ID",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Status",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Status",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Timestamp",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Timestamp",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Processed By",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Processed By",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "row_number",
              "type": "number",
              "display": true,
              "removed": true,
              "readOnly": true,
              "required": false,
              "displayName": "row_number",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "Invoice Number"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "update",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1TWE856dG0g0bSRr3M4SKPcpnJ32veWlq3m_DWJoSaTQ/edit#gid=0",
          "cachedResultName": "Sheet1"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "=\"YOUR_SPREADSHEET_ID\""
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "hVo2bSobLUbf5b3a",
          "name": "Google Sheets account"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "f775d6e5-a97b-4aea-be73-f6a3030cbfc3",
      "name": "Mettre à jour le statut approuvé",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        3024,
        -176
      ],
      "parameters": {
        "columns": {
          "value": {
            "Status": "Approved",
            "Invoice Number": "={{ $('Clean Invoice Payload').item.json.invoiceNumber }}"
          },
          "schema": [
            {
              "id": "Invoice Number",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Invoice Number",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Vendor",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Vendor",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Amount",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Amount",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Currency",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Currency",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Invoice Date",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Invoice Date",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Due Date",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Due Date",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Description",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Description",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Email ID",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Email ID",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Status",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Status",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Timestamp",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Timestamp",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Processed By",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Processed By",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "row_number",
              "type": "number",
              "display": true,
              "removed": true,
              "readOnly": true,
              "required": false,
              "displayName": "row_number",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "Invoice Number"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "update",
        "sheetName": {
          "__rl": true,
          "mode": "id",
          "value": "=Sheet1"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "=\"YOUR_SPREADSHEET_ID\""
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "hVo2bSobLUbf5b3a",
          "name": "Google Sheets account"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "b062f234-61a1-4913-8135-be9d9be322a8",
      "name": "Ajouter l'étiquette Traité",
      "type": "n8n-nodes-base.gmail",
      "position": [
        2832,
        -176
      ],
      "webhookId": "a5cbed4c-0c43-4bf0-8114-e8fbbf987e9e",
      "parameters": {
        "labelIds": [
          "Label_3710233975993850496"
        ],
        "messageId": "={{$node[\"Code in JavaScript\"].json.emailId}}",
        "operation": "addLabels"
      },
      "credentials": {
        "gmailOAuth2": {
          "id": "d2kfFLUV2qlW2o43",
          "name": "Gmail account"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "854afcd4-6559-4753-b75d-2a65766c492d",
      "name": "Ajouter l'étiquette Rejeté",
      "type": "n8n-nodes-base.gmail",
      "position": [
        2432,
        16
      ],
      "webhookId": "2e5f020d-ed42-438c-8744-4fdd602145c7",
      "parameters": {
        "labelIds": [
          "Label_8899302689673155790"
        ],
        "messageId": "={{$node[\"Code in JavaScript\"].json.emailId}}",
        "operation": "addLabels"
      },
      "credentials": {
        "gmailOAuth2": {
          "id": "d2kfFLUV2qlW2o43",
          "name": "Gmail account"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "025efb16-32dd-4a7e-8349-baef18b32040",
      "name": "Obtenir plusieurs contacts",
      "type": "n8n-nodes-base.xero",
      "position": [
        2416,
        -176
      ],
      "parameters": {
        "limit": 1,
        "options": {},
        "resource": "contact",
        "operation": "getAll",
        "organizationId": "=9a46b4a9-feac-446e-a04d-4e7421565898"
      },
      "credentials": {
        "xeroOAuth2Api": {
          "id": "XfSNoqDEDyivPRYX",
          "name": "Xero account"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "b43a1017-8ecb-4c39-b92f-8865349ccec2",
      "name": "Nettoyer le payload de la facture",
      "type": "n8n-nodes-base.code",
      "position": [
        1696,
        -48
      ],
      "parameters": {
        "jsCode": "const invoiceData = { ...$node[\"Format for Slack\"].json };\n\n// Strip off any vendor or itemCode field that might be passed\ndelete invoiceData.vendor;\ndelete invoiceData.itemCode;\ndelete invoiceData[\"Item Code Name or ID\"]; // Just in case\n\nreturn [{ json: invoiceData }];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "a77a3860-e69a-428f-b74e-d926859c83e3",
      "name": "Créer une facture",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        2624,
        -176
      ],
      "parameters": {
        "url": "https://api.xero.com/api.xro/2.0/Invoices",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"Type\": \"ACCPAY\",\n  \"Contact\": {\n    \"ContactID\": \"{{$node[\"Get many contacts\"].json.ContactID}}\"\n  },\n  \"Date\": \"{{$node[\"Format for Slack\"].json.invoiceDate}}\",\n  \"DueDate\": \"{{$node[\"Format for Slack\"].json.dueDate}}\",\n  \"InvoiceNumber\": \"{{$node[\"Format for Slack\"].json.invoiceNumber}}\",\n  \"Reference\": \"{{$node[\"Format for Slack\"].json.invoiceNumber}}\",\n  \"Status\": \"DRAFT\",\n  \"LineAmountTypes\": \"Exclusive\",\n  \"LineItems\": [\n    {\n      \"Description\": \"{{$node[\"Format for Slack\"].json.description}}\",\n      \"Quantity\": 1,\n      \"UnitAmount\": {{$node[\"Format for Slack\"].json.amount}},\n      \"AccountCode\": \"463\",\n      \"TaxType\": \"INPUT2\"\n    }\n  ]\n}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "authentication": "predefinedCredentialType",
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            },
            {
              "name": "Accept",
              "value": "application/json"
            },
            {
              "name": "xero-tenant-id",
              "value": "=\"{{$credentials.xero.apiKey}}\""
            }
          ]
        },
        "nodeCredentialType": "xeroOAuth2Api"
      },
      "credentials": {
        "xeroOAuth2Api": {
          "id": "XfSNoqDEDyivPRYX",
          "name": "Xero account"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "270ea877-b081-4704-8137-3cbcf62606fb",
      "name": "A une pièce jointe valide ?",
      "type": "n8n-nodes-base.if",
      "position": [
        288,
        16
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "5b78e677-d068-411a-8c43-e41a9725f370",
              "operator": {
                "type": "number",
                "operation": "notExists",
                "singleValue": true
              },
              "leftValue": "={{$json.error}}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "4128986d-09d7-44b5-a9f5-5f8a532c1712",
      "name": "A été approuvé ?",
      "type": "n8n-nodes-base.if",
      "position": [
        2128,
        -48
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "b58f3729-809c-4b21-9210-6c581dbeed50",
              "operator": {
                "type": "boolean",
                "operation": "equals"
              },
              "leftValue": "={{ $json.data.approved }}",
              "rightValue": true
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "8400ed04-51ba-4cc6-92cc-abb03a09e258",
      "name": "Extraire le texte",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        544,
        0
      ],
      "parameters": {
        "url": "https://api.ocr.space/parse/image",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"name\": \"apikey\",\n  \"value\": \"{{$credentials.ocrspaceApi.apiKey}}\"\n}",
        "sendBody": true,
        "specifyBody": "json"
      },
      "typeVersion": 4.2
    },
    {
      "id": "d132fdec-f017-49f6-a4bc-8a938df37d98",
      "name": "Note adhésive",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -912,
        -464
      ],
      "parameters": {
        "width": 608,
        "height": 1696,
        "content": "### Automate Invoice Processing with Gmail, OCR.space, Slack & Xero\n\nThis n8n template demonstrates how to automatically extract, validate, approve, and sync invoices received via email using AI and automation — reducing manual processing time and minimizing errors.\n\n### Use Cases\n\n- Finance teams looking to eliminate invoice approval bottlenecks  \n- Businesses that receive PDF/image invoices via email  \n- Companies currently logging invoices manually into spreadsheets or accounting tools  \n\n### How It Works\n\n1. **Trigger**: Watches for new emails in Gmail with PDF/image attachments.  \n2. **OCR**: Sends the attachment to OCR.space API (https://ocr.space/OCRAPI) to extract invoice text.  \n3. **Parsing**: Extracts key fields:  \n   - Vendor  \n   - Invoice number  \n   - Amount  \n   - Currency  \n   - Invoice date  \n   - Due date  \n   - Description  \n4. **Validation Logic**:  \n   - Checks if amount is valid  \n   - Ensures vendor and invoice number are present  \n   - Flags high-value invoices (e.g., over $10,000)  \n5. **Routing**:  \n   - If invalid: \n   - Sends a Slack message highlighting issues\n   - Labels email as **Rejected**  \n   - If valid:  \n     - Logs the invoice into Google Sheets  \n     - Sends a Slack message to the finance team for approval  \n     - After approval, creates a draft invoice in Xero  \n     - Labels the email as **Processed** in Gmail  \n\n### Customisation Options\n\n- Swap Gmail trigger with a file uploader or webhook  \n- Adjust the Slack notification channels or approval thresholds  \n- Replace Xero with QuickBooks, Zoho, or another tool  \n- Adapt log storage from Google Sheets to Airtable or Notion  \n- Add a reminder flow if no approval is received within 24h  \n\n### Prerequisites/Credential Setup\n\nTo use this workflow securely, you'll need the following credentials set up in n8n:\n\n- **Gmail OAuth2** – to watch for incoming invoice emails and apply labels\n- **OCR.space API** – create an HTTP Credential in n8n named `ocrspaceApi`, with your OCR.space key\n- **Slack OAuth2 or Webhook (with connected bot)** – to send approval/rejection messages to your team\n- **Google Sheets OAuth2 credentials** – to log processed invoices for audit and tracking\n- **Xero OAuth2 credentials (via n8n’s native integration)** – connect your Xero account to push approved invoices in Draft status\n \n### Secure Configuration\n- All credential fields use **n8n Credential Types**  \n- No sensitive data (API keys, spreadsheet IDs) is hardcoded  \n\n### Why This Helps\n\n- Reduces time spent manually reviewing and copying invoices\n- Maintains structured logs with clean data\n- Automates Slack approvals and reduces email back-and-forth\n- Improves audit trails and AP visibility across tools\n\n---\nWith this template, finance teams can streamline invoice approvals, reduce processing time, and maintain a clean audit trail, all fully automated."
      },
      "typeVersion": 1
    },
    {
      "id": "b16d6e58-0125-4735-b939-040945fcc89b",
      "name": "Note adhésive 1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1008,
        -336
      ],
      "parameters": {
        "width": 1248,
        "height": 640,
        "content": "## 2. Approve or Reject Invoice in Slack\n[Read more about Slack Approval workflows in n8n](https://docs.n8n.io/integrations/slack/)\n\nAfter parsing, the workflow evaluates whether the invoice meets preset criteria (e.g., approved vendors or amount limits). It logs the result in a Google Sheet and sends a Slack message for manual approval. Based on the response, it either proceeds to Xero or marks it as rejected.\n\nSetup Tip: Replace YOUR_SLACK_CLIENT_ID with the Client ID from your Slack apps page - https://api.slack.com/apps.\n"
      },
      "typeVersion": 1
    },
    {
      "id": "af22b982-18c5-4adc-9e88-d7ecf53f357c",
      "name": "Note adhésive 2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -144,
        -336
      ],
      "parameters": {
        "width": 1120,
        "height": 640,
        "content": "## 1. Extract & Parse Invoice from Gmail\n[Read more about the Gmail and HTTP Request nodes](https://docs.n8n.io/integrations/builtin/email-read-imap/)\n  \nThis section listens for new emails via Gmail, checks if an attachment exists and is valid, then extracts and parses invoice data using OCR (via OCR.Space API). The data is cleaned and prepared for downstream steps.\n\nSetup Tip: Replace YOUR_GOOGLE_CLIENT_ID with the Client ID from your Google Console - https://console.cloud.google.com/ for your Google Sheets, Google Docs, Gmail."
      },
      "typeVersion": 1
    },
    {
      "id": "422c5b66-d77a-4224-b689-e4b57152373e",
      "name": "Note adhésive 3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2288,
        -336
      ],
      "parameters": {
        "width": 1168,
        "height": 640,
        "content": "## 3. Sync Outcome to Xero & Notify\n[Read more about Xero and Gmail automation](https://docs.n8n.io/integrations/xero/)\n\nIf the invoice is approved, the workflow fetches the contact from Xero, creates the invoice, updates Gmail labels, logs final status in Google Sheets, and sends a success message in Slack. If rejected, it applies a different Gmail label, updates status, and notifies the team.\n"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "a1d9da45-06e7-460c-9c56-bc430ee568b6",
  "connections": {
    "8400ed04-51ba-4cc6-92cc-abb03a09e258": {
      "main": [
        [
          {
            "node": "be9d4c45-2bb3-4ca4-944e-65045fa75a60",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "189e9199-b377-4acc-a0e7-d5769e69a762": {
      "main": [
        [
          {
            "node": "7c5278ea-c32f-4f55-9ba4-4448c06c21f0",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "4128986d-09d7-44b5-a9f5-5f8a532c1712": {
      "main": [
        [
          {
            "node": "025efb16-32dd-4a7e-8349-baef18b32040",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "854afcd4-6559-4753-b75d-2a65766c492d",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "a77a3860-e69a-428f-b74e-d926859c83e3": {
      "main": [
        [
          {
            "node": "b062f234-61a1-4913-8135-be9d9be322a8",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "36afc95e-af94-4c98-a458-744e9bfd1139": {
      "main": [
        [
          {
            "node": "b43a1017-8ecb-4c39-b92f-8865349ccec2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "025efb16-32dd-4a7e-8349-baef18b32040": {
      "main": [
        [
          {
            "node": "a77a3860-e69a-428f-b74e-d926859c83e3",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "854afcd4-6559-4753-b75d-2a65766c492d": {
      "main": [
        [
          {
            "node": "2a70f5c0-db5f-44eb-8eb8-24c978892049",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "7c5278ea-c32f-4f55-9ba4-4448c06c21f0": {
      "main": [
        [
          {
            "node": "270ea877-b081-4704-8137-3cbcf62606fb",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "be9d4c45-2bb3-4ca4-944e-65045fa75a60": {
      "main": [
        [
          {
            "node": "424382f8-ff99-457e-9714-569c83e29157",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "b062f234-61a1-4913-8135-be9d9be322a8": {
      "main": [
        [
          {
            "node": "f775d6e5-a97b-4aea-be73-f6a3030cbfc3",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "424382f8-ff99-457e-9714-569c83e29157": {
      "main": [
        [
          {
            "node": "4ddb1479-1b7f-4f8d-ad41-1a92654c98c8",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "955f6439-8d79-4f9f-b35c-7bf3e39f6aad",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "b43a1017-8ecb-4c39-b92f-8865349ccec2": {
      "main": [
        [
          {
            "node": "abea5730-c60f-47f4-83b3-50cb42c6c9e0",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "270ea877-b081-4704-8137-3cbcf62606fb": {
      "main": [
        [
          {
            "node": "8400ed04-51ba-4cc6-92cc-abb03a09e258",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "abea5730-c60f-47f4-83b3-50cb42c6c9e0": {
      "main": [
        [
          {
            "node": "4128986d-09d7-44b5-a9f5-5f8a532c1712",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "f775d6e5-a97b-4aea-be73-f6a3030cbfc3": {
      "main": [
        [
          {
            "node": "afcf2f08-41ec-4ce5-bb32-98733317379f",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "2a70f5c0-db5f-44eb-8eb8-24c978892049": {
      "main": [
        [
          {
            "node": "b524a093-e827-4049-b0c9-809dc839465e",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "4ddb1479-1b7f-4f8d-ad41-1a92654c98c8": {
      "main": [
        [
          {
            "node": "36afc95e-af94-4c98-a458-744e9bfd1139",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Foire aux questions

Comment utiliser ce workflow ?

Copiez le code de configuration JSON ci-dessus, créez un nouveau workflow dans votre instance n8n et sélectionnez "Importer depuis le JSON", collez la configuration et modifiez les paramètres d'authentification selon vos besoins.

Dans quelles scénarios ce workflow est-il adapté ?

Avancé - Traitement des factures, Résumé IA

Est-ce payant ?

Ce workflow est entièrement gratuit et peut être utilisé directement. Veuillez noter que les services tiers utilisés dans le workflow (comme l'API OpenAI) peuvent nécessiter un paiement de votre part.

Informations sur le workflow
Niveau de difficulté
Avancé
Nombre de nœuds24
Catégorie2
Types de nœuds9
Description de la difficulté

Adapté aux utilisateurs avancés, avec des workflows complexes contenant 16+ nœuds

Auteur
Abi Odedeyi

Abi Odedeyi

@abiodedeyi

Automation Builder with extensive experience helping businesses streamline processes and generate revenue.

Liens externes
Voir sur n8n.io

Partager ce workflow

Catégories

Catégories: 34