{
  "nodes": [
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 9 * * 1"
            }
          ]
        }
      },
      "id": "e092f10a-1ecc-4292-aee4-270ac5075b04",
      "name": "Weekly Monday 9am",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        192,
        1824
      ]
    },
    {
      "parameters": {
        "resource": "worksheet",
        "operation": "readRows",
        "workbook": {
          "__rl": true,
          "value": "016ZC2UENNX3FINPJ7TBFJT6K37BFNTZJD",
          "mode": "list",
          "cachedResultName": "NEG_Rechnungseingangsbuch_Template 1"
        },
        "worksheet": {
          "__rl": true,
          "value": "{00000000-0001-0000-0000-000000000000}",
          "mode": "list",
          "cachedResultName": "Rechnungseingangsbuch"
        },
        "options": {}
      },
      "id": "3a7c8ed4-ee46-4091-b108-167b9e6e086c",
      "name": "Read All Invoices",
      "type": "n8n-nodes-base.microsoftExcel",
      "typeVersion": 2.2,
      "position": [
        416,
        1824
      ],
      "credentials": {
        "microsoftExcelOAuth2Api": {
          "id": "gn9O9iJYh6osoSAu",
          "name": "Microsoft Excel account"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "/**\n * Filter & Tag Invoices\n * Single output — adds _route tag to each item:\n *   'auto'  = auto-approve (< EUR100, Freigabestufe = Auto)\n *   'heike' = needs Heike (2-Stufen or 3-Stufen, still Offen)\n *   'mgmt'  = needs Management (3-Stufen, Teilfreigabe, Heike done)\n * Items without a route (already processed, no Lfd. Nr.) are dropped.\n */\nconst items = $input.all();\nconst results = [];\n\nfor (const item of items) {\n  const d = item.json;\n  const status = (d['Zahlungsstatus'] || '').trim();\n  const stufe = (d['Freigabestufe'] || '').trim();\n  const lfdNr = (d['Lfd. Nr.'] || '').toString().trim();\n\n  // Skip if no invoice number or already processed\n  if (!lfdNr) continue;\n  if (['Genehmigt', 'Bezahlt', 'Abgelehnt', 'Storniert'].includes(status)) continue;\n\n  if (stufe === 'Auto' && status === 'Offen') {\n    results.push({ json: { ...d, _route: 'auto' } });\n\n  } else if ((stufe === '2-Stufen' || stufe === '3-Stufen') && status === 'Offen') {\n    results.push({ json: { ...d, _route: 'heike' } });\n\n  } else if (stufe === '3-Stufen' && status === 'Teilfreigabe') {\n    const approvedBy = (d['Freigegeben von'] || '').toLowerCase();\n    const hasHeike = approvedBy.includes('heike');\n    const hasJuergen = approvedBy.includes('juergen') || approvedBy.includes('j\\u00fcrgen');\n    const hasPetra = approvedBy.includes('petra');\n\n    if (hasHeike) {\n      if (!hasJuergen) {\n        results.push({ json: { ...d, _route: 'mgmt', _approverName: 'Juergen', _approverEmail: 'va@saits.ai' } });\n      }\n      if (!hasPetra) {\n        results.push({ json: { ...d, _route: 'mgmt', _approverName: 'Petra', _approverEmail: 'va@saits.ai' } });\n      }\n    } else {\n      // Heike hasn't approved yet — unusual state, re-send to Heike\n      results.push({ json: { ...d, _route: 'heike' } });\n    }\n  }\n}\n\nif (results.length === 0) {\n  return [{ json: { _route: 'none', _empty: true, _message: 'No pending invoices found' } }];\n}\n\nreturn results;\n"
      },
      "id": "c84e5771-ef6a-400c-991e-612fae9bb667",
      "name": "Filter & Tag Invoices",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        640,
        1824
      ]
    },
    {
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "leftValue": "={{ $json._route }}",
                    "rightValue": "auto",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "id": "9cf2876c-44da-4071-92f0-d79ce6cfad51"
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "Auto-Approve"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "leftValue": "={{ $json._route }}",
                    "rightValue": "heike",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "id": "b79b3e42-c5da-402e-93dc-0695c75d678a"
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "Needs Heike"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "leftValue": "={{ $json._route }}",
                    "rightValue": "mgmt",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "id": "bf8c73b1-8076-4ddd-a273-294eab5fabf4"
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "Needs Mgmt"
            }
          ]
        },
        "options": {}
      },
      "id": "26c42394-6a64-4b3b-8f28-500ec81550f7",
      "name": "Route Invoices",
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3.2,
      "position": [
        864,
        1808
      ]
    },
    {
      "parameters": {
        "jsCode": "/**\n * Auto-Approve: Build update data for each invoice\n */\nconst items = $input.all();\nconst today = new Date().toLocaleDateString('de-DE');\n\nreturn items.map(item => ({\n  json: {\n    'Lfd. Nr.': item.json['Lfd. Nr.'],\n    Zahlungsstatus: 'Genehmigt',\n    'Freigegeben von': 'System (Auto)',\n    Freigabedatum: today\n  }\n}));\n"
      },
      "id": "b29607cc-b44e-41ea-bab3-5f935dbceee3",
      "name": "Prep Auto-Approve",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1088,
        1632
      ]
    },
    {
      "parameters": {
        "resource": "worksheet",
        "operation": "update",
        "workbook": {
          "__rl": true,
          "value": "016ZC2UENNX3FINPJ7TBFJT6K37BFNTZJD",
          "mode": "list",
          "cachedResultName": "NEG_Rechnungseingangsbuch_Template 1"
        },
        "worksheet": {
          "__rl": true,
          "value": "{00000000-0001-0000-0000-000000000000}",
          "mode": "list",
          "cachedResultName": "Rechnungseingangsbuch"
        },
        "columnToMatchOn": "Lfd. Nr.",
        "valueToMatchOn": "={{ $json[\"Lfd. Nr.\"] }}",
        "fieldsUi": {
          "values": [
            {
              "column": "Zahlungsstatus",
              "fieldValue": "={{ $json.Zahlungsstatus }}"
            },
            {
              "column": "Freigegeben von",
              "fieldValue": "={{ $json['Freigegeben von'] }}"
            },
            {
              "column": "Freigabedatum",
              "fieldValue": "={{ $json.Freigabedatum }}"
            }
          ]
        },
        "options": {}
      },
      "id": "c3e22e57-e80a-4adf-82cf-71c8037fe468",
      "name": "Auto-Approve in Excel",
      "type": "n8n-nodes-base.microsoftExcel",
      "typeVersion": 2.2,
      "position": [
        1312,
        1632
      ],
      "credentials": {
        "microsoftExcelOAuth2Api": {
          "id": "gn9O9iJYh6osoSAu",
          "name": "Microsoft Excel account"
        }
      }
    },
    {
      "parameters": {
        "toRecipients": "va@saits.ai",
        "subject": "={{ $json.subject }}",
        "bodyContent": "={{ $json.textBody }}",
        "additionalFields": {}
      },
      "id": "a1fd6e62-52bc-4a94-bb72-6b07dd823db7",
      "name": "Send Email (Heike)",
      "type": "n8n-nodes-base.microsoftOutlook",
      "typeVersion": 2,
      "position": [
        1312,
        1824
      ],
      "webhookId": "d3062800-4fbe-4fdf-986c-13d2c9095f7e",
      "credentials": {
        "microsoftOutlookOAuth2Api": {
          "id": "lpkp2ZKiapfXmFTp",
          "name": "Rechnung - test"
        }
      }
    },
    {
      "parameters": {
        "path": "neg-invoice-approval",
        "responseMode": "responseNode",
        "options": {}
      },
      "id": "305c093b-b091-4765-9e2f-775f11cb4d29",
      "name": "Approval Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2.1,
      "position": [
        336,
        2528
      ],
      "webhookId": "neg-invoice-approval"
    },
    {
      "parameters": {
        "jsCode": "const query = $input.first().json.query || $input.first().json;\n\nreturn {\n  json: {\n    valid: true,\n    action: query.action,\n    invoiceId: query.invoiceId,\n    approver: query.approver,\n    approverDisplayName: query.approver.charAt(0).toUpperCase() + query.approver.slice(1)\n  }\n};"
      },
      "id": "416c3769-4ae9-4c22-b2bd-c83c33e4e493",
      "name": "Validate Token",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        576,
        2528
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 1
          },
          "conditions": [
            {
              "leftValue": "={{ $json.valid }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "a76ac66f-deca-4c6b-b7fd-182f412003f1",
      "name": "Token Valid?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        816,
        2528
      ]
    },
    {
      "parameters": {
        "respondWith": "text",
        "responseBody": "<html><body style=\"font-family:Arial;text-align:center;padding:40px\"><h1>Fehler</h1><p>Ungueltiger Link.</p></body></html>",
        "options": {
          "responseCode": 403
        }
      },
      "id": "af1e908f-aa0d-4752-8ac8-313740fa1116",
      "name": "Error Response",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        1056,
        2640
      ]
    },
    {
      "parameters": {
        "resource": "worksheet",
        "operation": "readRows",
        "workbook": {
          "__rl": true,
          "value": "016ZC2UENNX3FINPJ7TBFJT6K37BFNTZJD",
          "mode": "list",
          "cachedResultName": "NEG_Rechnungseingangsbuch_Template 1"
        },
        "worksheet": {
          "__rl": true,
          "value": "{00000000-0001-0000-0000-000000000000}",
          "mode": "list",
          "cachedResultName": "Rechnungseingangsbuch"
        },
        "options": {}
      },
      "id": "a680a4db-7429-4944-a20d-2893db9c7d2b",
      "name": "Lookup Invoice",
      "type": "n8n-nodes-base.microsoftExcel",
      "typeVersion": 2.2,
      "position": [
        1056,
        2448
      ],
      "credentials": {
        "microsoftExcelOAuth2Api": {
          "id": "gn9O9iJYh6osoSAu",
          "name": "Microsoft Excel account"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const approval = $('Validate Token').first().json;\nconst allRows = $input.all();\nconst today = new Date().toLocaleDateString('de-DE');\n\n// Find the invoice row\nconst invoiceRow = allRows.find(item =>\n  (item.json['Lfd. Nr.'] || '').toString().trim() === approval.invoiceId\n);\n\nif (!invoiceRow) {\n  return [{\n    json: {\n      error: true,\n      alreadyDone: false,\n      htmlResponse: '<html><body style=\"font-family:Arial;text-align:center;padding:40px\"><h1>Fehler</h1><p>Rechnung nicht gefunden: ' + approval.invoiceId + '</p></body></html>'\n    }\n  }];\n}\n\nconst d = invoiceRow.json;\nconst currentStatus = (d['Zahlungsstatus'] || '').trim();\nconst stufe = (d['Freigabestufe'] || '').trim();\nconst currentApprovers = (d['Freigegeben von'] || '').trim();\n\n// Already processed?\nif (['Genehmigt', 'Bezahlt', 'Abgelehnt', 'Storniert'].includes(currentStatus)) {\n  return [{\n    json: {\n      error: false,\n      alreadyDone: true,\n      htmlResponse: `<html><body style=\"font-family:Arial;text-align:center;padding:40px\"><h1>Bereits verarbeitet</h1><p>Rechnung ${approval.invoiceId} wurde bereits verarbeitet (Status: ${currentStatus}).</p></body></html>`\n    }\n  }];\n}\n\n// REJECTION\nif (approval.action === 'reject') {\n  return [{\n    json: {\n      error: false,\n      alreadyDone: false,\n      invoiceId: approval.invoiceId,\n      newStatus: 'Abgelehnt',\n      newApprovers: currentApprovers\n        ? currentApprovers + ', ' + approval.approverDisplayName + ' (ABGELEHNT)'\n        : approval.approverDisplayName + ' (ABGELEHNT)',\n      newDate: today,\n      needsNextEmail: false,\n      nextApprovers: [],\n      htmlResponse: `<html><body style=\"font-family:Arial;text-align:center;padding:40px\"><div style=\"background:#dc3545;color:white;padding:20px;border-radius:8px;max-width:400px;margin:0 auto\"><h1>&#10008; Abgelehnt</h1><p>Rechnung ${approval.invoiceId} wurde von ${approval.approverDisplayName} abgelehnt.</p></div></body></html>`\n    }\n  }];\n}\n\n// APPROVAL\nconst approverList = currentApprovers\n  ? currentApprovers.split(',').map(s => s.trim().toLowerCase())\n  : [];\napproverList.push(approval.approverDisplayName.toLowerCase());\nconst newApproversStr = currentApprovers\n  ? currentApprovers + ', ' + approval.approverDisplayName\n  : approval.approverDisplayName;\n\nlet newStatus;\nlet needsNextEmail = false;\nlet nextApprovers = [];\n\nif (stufe === '2-Stufen') {\n  newStatus = 'Genehmigt';\n} else if (stufe === '3-Stufen') {\n  if (approval.approver === 'heike') {\n    newStatus = 'Teilfreigabe';\n    needsNextEmail = true;\n    nextApprovers = [\n      { ...d, 'Freigegeben von': newApproversStr, _approverName: 'Juergen', _approverEmail: 'va@saits.ai' },\n      { ...d, 'Freigegeben von': newApproversStr, _approverName: 'Petra', _approverEmail: 'va@saits.ai' }\n    ];\n  } else {\n    const hasJuergen = approverList.some(a => a.includes('juergen') || a.includes('j\\u00fcrgen'));\n    const hasPetra = approverList.some(a => a.includes('petra'));\n    newStatus = (hasJuergen && hasPetra) ? 'Genehmigt' : 'Teilfreigabe';\n  }\n} else {\n  newStatus = 'Genehmigt';\n}\n\nconst actionWord = newStatus === 'Genehmigt' ? 'vollstaendig genehmigt' : 'teilweise genehmigt';\nconst color = newStatus === 'Genehmigt' ? '#28a745' : '#ffc107';\n\nreturn [{\n  json: {\n    error: false,\n    alreadyDone: false,\n    invoiceId: approval.invoiceId,\n    newStatus,\n    newApprovers: newApproversStr,\n    newDate: today,\n    needsNextEmail,\n    nextApprovers,\n    isFullyApproved: newStatus === 'Genehmigt',\n    htmlResponse: `<html><body style=\"font-family:Arial;text-align:center;padding:40px\"><div style=\"background:${color};color:white;padding:20px;border-radius:8px;max-width:400px;margin:0 auto\"><h1>&#10004; ${actionWord}</h1><p>Rechnung ${approval.invoiceId} wurde von ${approval.approverDisplayName} genehmigt.</p><p>Status: ${newStatus}</p></div></body></html>`\n  }\n}];"
      },
      "id": "cea7b565-0c6e-4d15-a4e3-b257bb5dc468",
      "name": "Process Approval",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1296,
        2448
      ]
    },
    {
      "parameters": {
        "resource": "worksheet",
        "operation": "update",
        "workbook": {
          "__rl": true,
          "value": "016ZC2UENNX3FINPJ7TBFJT6K37BFNTZJD",
          "mode": "list",
          "cachedResultName": "NEG_Rechnungseingangsbuch_Template 1"
        },
        "worksheet": {
          "__rl": true,
          "value": "{00000000-0001-0000-0000-000000000000}",
          "mode": "list",
          "cachedResultName": "Rechnungseingangsbuch"
        },
        "columnToMatchOn": "Lfd. Nr.",
        "valueToMatchOn": "={{ $json.invoiceId }}",
        "fieldsUi": {
          "values": [
            {
              "column": "Zahlungsstatus",
              "fieldValue": "={{ $('Process Approval').first().json.newStatus }}"
            },
            {
              "column": "Freigegeben von",
              "fieldValue": "={{ $('Process Approval').first().json.newApprovers }}"
            },
            {
              "column": "Freigabedatum",
              "fieldValue": "={{ $('Process Approval').first().json.newDate }}"
            }
          ]
        },
        "options": {}
      },
      "id": "3a873baa-3afc-4776-b1cb-6aaa84e14ff3",
      "name": "Update Excel Status",
      "type": "n8n-nodes-base.microsoftExcel",
      "typeVersion": 2.2,
      "position": [
        1776,
        2368
      ],
      "credentials": {
        "microsoftExcelOAuth2Api": {
          "id": "gn9O9iJYh6osoSAu",
          "name": "Microsoft Excel account"
        }
      }
    },
    {
      "parameters": {
        "respondWith": "text",
        "responseBody": "={{ $('Process Approval').first().json.htmlResponse }}",
        "options": {
          "responseCode": 200
        }
      },
      "id": "838a401f-2bc7-465f-9f6d-386c71833a1c",
      "name": "Success Response",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        2016,
        2368
      ]
    },
    {
      "parameters": {
        "respondWith": "text",
        "responseBody": "={{ $('Process Approval').first().json.htmlResponse || '<html><body><h1>Bereits verarbeitet</h1></body></html>' }}",
        "options": {
          "responseCode": 200
        }
      },
      "id": "e7e16e42-a035-4e9f-a393-f14611de0da3",
      "name": "Already Done Response",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        1776,
        2544
      ]
    },
    {
      "parameters": {
        "jsCode": "const crypto = require('crypto');\nconst processData = $('Process Approval').first().json;\n\nif (!processData.needsNextEmail || !processData.nextApprovers || processData.nextApprovers.length === 0) {\n  return [];\n}\n\nconst secret = 'NEG-INVOICE-SECRET-2026';\nconst WEBHOOK_BASE = 'https://n8n.saits.ai/webhook/neg-invoice-approval';\n\nfunction makeToken(invoiceId, approver) {\n  return crypto.createHmac('sha256', secret)\n    .update(invoiceId + '|' + approver)\n    .digest('hex')\n    .substring(0, 16);\n}\n\nfunction buildUrl(action, invoiceId, approver) {\n  const token = makeToken(invoiceId, approver);\n  return `${WEBHOOK_BASE}?action=${action}&invoiceId=${encodeURIComponent(invoiceId)}&approver=${encodeURIComponent(approver)}&token=${token}`;\n}\n\nconst results = [];\n\nfor (const approverData of processData.nextApprovers) {\n  const d = approverData;\n  if (!d._approverName) continue;\n\n  const invoiceId = d['Lfd. Nr.'];\n  const approverName = d._approverName;\n  const approverKey = approverName.toLowerCase();\n  const approveUrl = buildUrl('approve', invoiceId, approverKey);\n  const rejectUrl = buildUrl('reject', invoiceId, approverKey);\n  const amount = Number(d['Bruttobetrag'] || 0).toLocaleString('de-DE', { minimumFractionDigits: 2 });\n\n  const textBody = `Guten Tag ${approverName},\n\nfolgende Rechnung wurde von Heike geprueft und wartet auf Ihre Freigabe:\n\nInterne Nr.:       ${invoiceId}\nLieferant:         ${d['Lieferant'] || '-'}\nBetrag (brutto):   EUR ${amount}\nProjekt:           ${d['Projekt'] || '-'}\nBereits genehmigt: ${d['Freigegeben von'] || '-'}\n\nGENEHMIGEN: ${approveUrl}\n\nABLEHNEN:   ${rejectUrl}\n\nMit freundlichen Gruessen,\nNEG Rechnungssystem`;\n\n  results.push({\n    json: {\n      to: d._approverEmail || 'rs@saits.ai',\n      subject: `[Freigabe Mgmt] ${invoiceId} - EUR ${amount} - ${d['Lieferant'] || 'Unbekannt'}`,\n      textBody\n    }\n  });\n}\n\nreturn results;"
      },
      "id": "a96ece24-9281-44b4-af35-82d259e09f25",
      "name": "Build Next Email",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2256,
        2368
      ]
    },
    {
      "parameters": {
        "toRecipients": "={{ $json.to }}",
        "subject": "={{ $json.subject }}",
        "bodyContent": "={{ $json.textBody }}",
        "additionalFields": {}
      },
      "id": "f4c068a4-a75b-4294-8898-25a4202a1bba",
      "name": "Send Next Email",
      "type": "n8n-nodes-base.microsoftOutlook",
      "typeVersion": 2,
      "position": [
        2496,
        2368
      ],
      "webhookId": "6b6ab61b-0f55-4bc2-82bb-7e99f1d5510a",
      "credentials": {
        "microsoftOutlookOAuth2Api": {
          "id": "bLEDW2m8bjLKhT9l",
          "name": "Microsoft Outlook account"
        }
      }
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 3
          },
          "conditions": [
            {
              "id": "468eca3d-a309-4803-b358-83cbb3917edc",
              "leftValue": "={{ $json.error }}",
              "rightValue": false,
              "operator": {
                "type": "boolean",
                "operation": "equals"
              }
            },
            {
              "id": "7036db13-f128-4825-859f-9e7d75d5495e",
              "leftValue": "={{ $json.alreadyDone }}",
              "rightValue": false,
              "operator": {
                "type": "boolean",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.3,
      "position": [
        1504,
        2448
      ],
      "id": "3f487709-b7c1-48d2-b19d-a16a58e14d69",
      "name": "If"
    },
    {
      "parameters": {
        "jsCode": "/**\n * Build Approval Email for Heike\n * Generates HTML email with Approve/Reject buttons per invoice.\n */\nconst crypto = require('crypto');\nconst items = $input.all();\n\nlet statics = $getWorkflowStaticData('global');\nif (!statics.approvalSecret) {\n  statics.approvalSecret = crypto.randomBytes(32).toString('hex');\n}\nconst secret = statics.approvalSecret;\nconst WEBHOOK_BASE = 'https://n8n.saits.click/webhook/neg-invoice-approval';\n\nfunction makeToken(invoiceId, approver) {\n  return crypto.createHmac('sha256', secret)\n    .update(invoiceId + '|' + approver)\n    .digest('hex')\n    .substring(0, 16);\n}\n\nfunction buildUrl(action, invoiceId, approver) {\n  const token = makeToken(invoiceId, approver);\n  return `${WEBHOOK_BASE}?action=${action}&invoiceId=${encodeURIComponent(invoiceId)}&approver=${encodeURIComponent(approver)}&token=${token}`;\n}\n\nconst results = [];\n\nfor (const item of items) {\n  const d = item.json;\n  const invoiceId = d['Lfd. Nr.'];\n  const approver = 'heike';\n  const approveUrl = buildUrl('approve', invoiceId, approver);\n  const rejectUrl = buildUrl('reject', invoiceId, approver);\n  const amount = Number(d['Bruttobetrag'] || 0).toLocaleString('de-DE', { minimumFractionDigits: 2 });\n\n  const textBody = `Guten Tag Mrs. Heinke,\n\nfolgende Rechnung wartet auf Ihre Freigabe:\n\nInterne Nr.:     ${invoiceId}\nRechnungs-Nr.:   ${d['RE-Nr.'] || '-'}\nLieferant:       ${d['Lieferant'] || '-'}\nBetrag (brutto): ${d['Waehrung'] || 'EUR'} ${amount}\nProjekt:         ${d['Projekt'] || '-'}\nGesellschaft:    ${d['Gesellschaft'] || '-'}\nRE-Datum:        ${d['RE-Datum'] || '-'}\nFaellig am:      ${d['Faelligkeitsdatum'] || '-'}\nFreigabestufe:   ${d['Freigabestufe'] || '-'}\n\nGENEHMIGEN: ${approveUrl}\n\nABLEHNEN:   ${rejectUrl}\n\nMit freundlichen Grüssen,\nNEG Rechnungssystem`;\n\n  results.push({\n    json: {\n      to: 'va@saits.ai',\n      subject: `[Freigabe] ${invoiceId} - EUR ${amount} - ${d['Lieferant'] || 'Unbekannt'}`,\n      textBody,\n      invoiceId,\n      approver\n    }\n  });\n}\n\nreturn results;\n"
      },
      "id": "6dbc910f-2364-4075-bd7d-8ad3ceccedeb",
      "name": "Build Email (Mrs. Heinke)",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1136,
        1824
      ]
    },
    {
      "parameters": {
        "jsCode": "\nconst crypto = require('crypto');\nconst items = $input.all();\n\nlet statics = $getWorkflowStaticData('global');\nif (!statics.approvalSecret) {\n  statics.approvalSecret = crypto.randomBytes(32).toString('hex');\n}\nconst secret = statics.approvalSecret;\nconst WEBHOOK_BASE = 'https://n8n.saits.click/webhook/neg-invoice-approval';\n\nfunction makeToken(invoiceId, approver) {\n  return crypto.createHmac('sha256', secret)\n    .update(invoiceId + '|' + approver)\n    .digest('hex')\n    .substring(0, 16);\n}\n\nfunction buildUrl(action, invoiceId, approver) {\n  const token = makeToken(invoiceId, approver);\n  return `${WEBHOOK_BASE}?action=${action}&invoiceId=${encodeURIComponent(invoiceId)}&approver=${encodeURIComponent(approver)}&token=${token}`;\n}\n\nconst results = [];\n\nfor (const item of items) {\n  const d = item.json;\n  const invoiceId = d['Lfd. Nr.'];\n\n  if (!d._approverName) {\n    throw new Error(`Item ${invoiceId} is missing _approverName — check the routing node upstream.`);\n  }\n\n  const approverName = d._approverName;\n  const approverKey = approverName.toLowerCase();\n  const approveUrl = buildUrl('approve', invoiceId, approverKey);\n  const rejectUrl = buildUrl('reject', invoiceId, approverKey);\n  const amount = Number(d['Bruttobetrag'] || 0).toLocaleString('de-DE', { minimumFractionDigits: 2 });\n\n  const textBody = `Guten Tag ${approverName},\n\nfolgende Rechnung wurde von Heike geprüft und wartet auf Ihre Management-Freigabe:\n\nInterne Nr.:          ${invoiceId}\nRechnungs-Nr.:        ${d['RE-Nr.'] || '-'}\nLieferant:            ${d['Lieferant'] || '-'}\nBetrag (brutto):      ${d['Waehrung'] || 'EUR'} ${amount}\nProjekt:              ${d['Projekt'] || '-'}\nGesellschaft:         ${d['Gesellschaft'] || '-'}\nBereits genehmigt:    ${d['Freigegeben von'] || '-'}\nFreigabestufe:        3-Stufen (Management erforderlich)\n\nGENEHMIGEN: ${approveUrl}\n\nABLEHNEN:   ${rejectUrl}\n\nMit freundlichen Grüssen,\nNEG Rechnungssystem`;\n\n  results.push({\n    json: {\n      to: 'va@saits.ai',\n      subject: `[Freigabe Mgmt] ${invoiceId} - EUR ${amount} - ${d['Lieferant'] || 'Unbekannt'}`,\n      textBody,\n      invoiceId,\n      approver: approverKey\n    }\n  });\n}\n\nreturn results;"
      },
      "id": "2c830fa6-2b07-462a-a851-a5924902a589",
      "name": "Build Email (Mr. Wilms)",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1088,
        2016
      ]
    },
    {
      "parameters": {
        "toRecipients": "va@saits.ai",
        "subject": "={{ $json.subject }}",
        "bodyContent": "={{ $json.textBody }}",
        "additionalFields": {}
      },
      "id": "5d19b51a-dce1-470c-83dd-1e3c49ca5aec",
      "name": "Send Email (Mr. Wilms)",
      "type": "n8n-nodes-base.microsoftOutlook",
      "typeVersion": 2,
      "position": [
        1312,
        2016
      ],
      "webhookId": "beb5e62f-44e1-4d98-95de-ca880304bf6d",
      "credentials": {
        "microsoftOutlookOAuth2Api": {
          "id": "lpkp2ZKiapfXmFTp",
          "name": "Rechnung - test"
        }
      }
    }
  ],
  "connections": {
    "Weekly Monday 9am": {
      "main": [
        [
          {
            "node": "Read All Invoices",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read All Invoices": {
      "main": [
        [
          {
            "node": "Filter & Tag Invoices",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter & Tag Invoices": {
      "main": [
        [
          {
            "node": "Route Invoices",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Route Invoices": {
      "main": [
        [
          {
            "node": "Prep Auto-Approve",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Build Email (Mr. Wilms)",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Build Email (Mr. Wilms)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prep Auto-Approve": {
      "main": [
        [
          {
            "node": "Auto-Approve in Excel",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Approval Webhook": {
      "main": [
        [
          {
            "node": "Validate Token",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate Token": {
      "main": [
        [
          {
            "node": "Token Valid?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Token Valid?": {
      "main": [
        [
          {
            "node": "Lookup Invoice",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Error Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Lookup Invoice": {
      "main": [
        [
          {
            "node": "Process Approval",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Process Approval": {
      "main": [
        [
          {
            "node": "If",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update Excel Status": {
      "main": [
        [
          {
            "node": "Success Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Success Response": {
      "main": [
        [
          {
            "node": "Build Next Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Next Email": {
      "main": [
        [
          {
            "node": "Send Next Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If": {
      "main": [
        [
          {
            "node": "Update Excel Status",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Already Done Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Email (Mrs. Heinke)": {
      "main": [
        [
          {
            "node": "Send Email (Heike)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Email (Mr. Wilms)": {
      "main": [
        [
          {
            "node": "Send Email (Mr. Wilms)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "pinData": {
    "Approval Webhook": [
      {
        "headers": {
          "host": "n8n.saits.click",
          "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.3 Safari/605.1.15",
          "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
          "accept-encoding": "gzip, deflate, br, zstd",
          "accept-language": "en-IN,en-GB;q=0.9,en;q=0.8",
          "cookie": "ph_phc_4URIAm1uYfJO7j8kWSe0J8lc8IqnstRLS7Jx8NcakHo_posthog=%7B%22%24device_id%22%3A%22019c4912-5eb1-70e8-b090-388b43995990%22%2C%22distinct_id%22%3A%220ffb98e39bce821a632ca9b75e41251a5c908577011f31555a1af0c2da210111%236b67bba3-0492-4fe3-b105-2e9073e2c56b%22%2C%22%24sesid%22%3A%5B1771297386881%2C%22019c698b-e13d-727c-8a25-58405cbead65%22%2C1771297300785%5D%2C%22%24epp%22%3Atrue%2C%22%24initial_person_info%22%3A%7B%22r%22%3A%22%24direct%22%2C%22u%22%3A%22https%3A%2F%2Fn8ntutario.saits.click%2F%22%7D%7D; rl_anonymous_id=RudderEncrypt%3AU2FsdGVkX19flOc6lDtYN8Sc%2FsQl6%2Fiv5f31ZM%2F%2FtfM0jcRggD9P7XofXFJa3P1w6bLd0jHBO4Hc%2FZix%2FCZiPw%3D%3D; rl_session=RudderEncrypt%3AU2FsdGVkX19oqsTIZQowZ7i0Ne2Qhj3EAvjuETXIl8tmhXmZRgm81aNLunXLosUp%2Furwu6VO1iRZrxG%2FFqA6zOxnwQ%2BynuwEK1s9rJ0vqGeEe3BSphlrVgZ4ntiWnsbvG2oJO3Q1qN60YCMybZNnsA%3D%3D; rl_trait=RudderEncrypt%3AU2FsdGVkX19c0NUyQ22JwX3B%2FdFY4qQKSP2TRWbMv32BQa45%2BPp30ZxxIgihi21C6inDrPc2xLGumMMZrmz3PI3n1KtRGpCnGYBU1yoVz72wLhU0KPvJpEZkj3S%2BgWtdBadGGL8jA6Oq2J0dnYnHrbSZ0wXj0XDlfMQABWLywwc%3D; rl_user_id=RudderEncrypt%3AU2FsdGVkX1%2FAv0sdUSc3zDDjh6wnmV5nUhlJY%2FqLwTqsb9B8a9pTG8y8pnAjeyS%2FtXdhQ4C%2FMZ7rCcQ%2BlQNRTfB7KFbaWoMA7F%2BhfxS0lPxulIFx3aES7k1j0bJgTzQo4nX96DTqfL4BpAvI3s2%2Bgy%2Br%2BPA70TToShFyNSo7NUA%3D; rl_page_init_referrer=RudderEncrypt%3AU2FsdGVkX19brtipuj271A2dAqsHflsgo7MDO6SfjSA%3D; rl_page_init_referring_domain=RudderEncrypt%3AU2FsdGVkX19kKXqbLDwW%2BL4zrmJKjjgoGnaSKhxjgp4%3D",
          "priority": "u=0, i",
          "sec-fetch-dest": "document",
          "sec-fetch-mode": "navigate",
          "sec-fetch-site": "cross-site",
          "via": "2.0 Caddy",
          "x-forwarded-for": "49.156.117.229",
          "x-forwarded-host": "n8n.saits.click",
          "x-forwarded-proto": "https"
        },
        "params": {},
        "query": {
          "action": "approve",
          "invoiceId": "INV-2026-0001",
          "approver": "heike",
          "token": "4a64ec9e29e1471a"
        },
        "body": {},
        "webhookUrl": "https://n8n.saits.click/webhook/neg-invoice-approval",
        "executionMode": "production"
      }
    ]
  },
  "meta": {
    "templateCredsSetupCompleted": true,
    "instanceId": "9c1de76795e8803a2ffa944dee021ff74564866a2c2ffdc6044fed6d9556cfc1"
  }
}