{
  "name": "Rechnungsbelege automatisch ablegen",
  "nodes": [
    {
      "id": "baa8e4a4-3955-40c1-a988-05245cf0a163",
      "name": "Notiz 1",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -60,
        -520
      ],
      "parameters": {
        "content": "## So richtest du den Ablauf ein\n1. Im Knoten **Neue E-Mail mit Anhang** deine IMAP-Zugangsdaten eintragen. Am besten ein eigenes Postfach nur für Rechnungen.\n2. Den Ordner für Belege einmal anlegen und in **Rechnungsdaten erkennen** oben eintragen. n8n darf standardmäßig nur in ~/.n8n-files schreiben. Lege den Ordner vorher an.\n3. Mit **Test: Start per Klick** ausprobieren: Drei Beispiel-Rechnungen als PDF laufen durch.\n4. Dauerhaft auf der Festplatte speichern geht nur, wenn du n8n selbst betreibst; in der n8n Cloud bleiben Dateien laut n8n-Doku nicht dauerhaft erhalten. Dort nimmst du einen Cloud-Speicher-Knoten (z. B. Google Drive).\n5. Optional (Lexware Office, Tarif XL): API-Schlüssel als \"Bearer Auth\" anlegen und **Beleg an Lexware Office senden** einschalten.",
        "height": 320,
        "width": 560,
        "color": 4
      }
    },
    {
      "id": "937bd727-ddf0-4b84-a03b-c14207768dfb",
      "name": "Test: Start per Klick",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        0,
        160
      ],
      "parameters": {},
      "notes": "Startet den Ablauf von Hand, zum Testen mit den Beispieldaten."
    },
    {
      "id": "be2497d3-970f-47f8-a6be-07c22fc0e13f",
      "name": "Neue E-Mail mit Anhang",
      "type": "n8n-nodes-base.emailReadImap",
      "typeVersion": 2,
      "position": [
        240,
        0
      ],
      "parameters": {
        "mailbox": "INBOX",
        "postProcessAction": "read",
        "format": "simple",
        "downloadAttachments": true,
        "options": {}
      },
      "notes": "Holt neue Mails samt Anhängen per IMAP."
    },
    {
      "id": "c0aaf791-0d00-4d0e-acf8-ed0ce64884ef",
      "name": "Testdaten: 3 Rechnungen als PDF",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        240,
        160
      ],
      "parameters": {
        "jsCode": "// Baut drei kleine, erfundene Rechnungen als echte PDF-Dateien, dazu ein Bild, das aussortiert werden soll.\nfunction pdf(zeilen) {\n  const maske = (z) => z.replace(/[\\\\()]/g, (m) => `\\\\${m}`);\n  const inhalt = `BT /F1 11 Tf 50 780 Td 16 TL ${zeilen.map((z) => `(${maske(z)}) Tj T*`).join(' ')} ET`;\n  const objekte = [\n    '<< /Type /Catalog /Pages 2 0 R >>',\n    '<< /Type /Pages /Kids [3 0 R] /Count 1 >>',\n    '<< /Type /Page /Parent 2 0 R /MediaBox [0 0 595 842] /Resources << /Font << /F1 4 0 R >> >> /Contents 5 0 R >>',\n    '<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding /WinAnsiEncoding >>',\n    `<< /Length ${Buffer.byteLength(inhalt, 'latin1')} >>\\nstream\\n${inhalt}\\nendstream`,\n  ];\n  let aus = '%PDF-1.4\\n';\n  const stellen = [];\n  objekte.forEach((o, i) => {\n    stellen.push(Buffer.byteLength(aus, 'latin1'));\n    aus += `${i + 1} 0 obj\\n${o}\\nendobj\\n`;\n  });\n  const xref = Buffer.byteLength(aus, 'latin1');\n  aus += `xref\\n0 ${objekte.length + 1}\\n0000000000 65535 f \\n${stellen.map((s) => `${String(s).padStart(10, '0')} 00000 n \\n`).join('')}`;\n  aus += `trailer\\n<< /Size ${objekte.length + 1} /Root 1 0 R >>\\nstartxref\\n${xref}\\n%%EOF`;\n  return Buffer.from(aus, 'latin1').toString('base64');\n}\nconst anhang = (zeilen, name) => ({ data: pdf(zeilen), mimeType: 'application/pdf', fileName: name, fileExtension: 'pdf' });\nconst bild = {\n  data: 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==',\n  mimeType: 'image/png',\n  fileName: 'logo.png',\n  fileExtension: 'png',\n};\nreturn [\n  {\n    json: { from: 'Muster Hosting GmbH <rechnung@example.net>', subject: 'Ihre Rechnung RE-2026-1042' },\n    binary: {\n      attachment_0: anhang(['Muster Hosting GmbH', 'Beispielweg 1, 12345 Musterstadt', 'Rechnungsnummer: RE-2026-1042', 'Rechnungsdatum: 03.11.2026', 'Webhosting November', 'Gesamtbetrag: 23,80 EUR'], 'rechnung.pdf'),\n      attachment_1: bild,\n    },\n  },\n  {\n    json: { from: 'Bürobedarf Beispiel KG <info@example.com>', subject: 'Rechnung zu Ihrer Bestellung' },\n    binary: {\n      attachment_0: anhang(['Bürobedarf Beispiel KG', 'Rechnung Nr. 88123', 'Datum: 28.10.2026', 'Druckerpapier und Toner', 'Rechnungsbetrag: 1.234,56 EUR'], 'scan_0042.pdf'),\n    },\n  },\n  {\n    json: { from: 'Spedition Beispiel <versand@example.org>', subject: 'Lieferschein' },\n    binary: {\n      attachment_0: anhang(['Spedition Beispiel', 'Lieferschein 5531', 'Datum: 30.10.2026', 'Ohne Betrag'], 'dokument.pdf'),\n    },\n  },\n];\n"
      },
      "notes": "Drei erfundene Rechnungen als echte PDF-Dateien. Eine davon ist ein Lieferschein ohne Betrag."
    },
    {
      "id": "1e7dd308-fcfb-4c83-a75f-e99e1b987d1f",
      "name": "Nur PDF-Anhänge, einzeln",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        480,
        80
      ],
      "parameters": {
        "jsCode": "// Jede Mail kann mehrere Anhänge haben: hier wird pro PDF ein eigener Eintrag daraus.\nconst ergebnis = [];\nfor (const item of $input.all()) {\n  for (const datei of Object.values(item.binary ?? {})) {\n    const istPdf = datei.mimeType === 'application/pdf' || /\\.pdf$/i.test(datei.fileName ?? '');\n    if (!istPdf) continue;\n    ergebnis.push({\n      json: { absender: String(item.json.from ?? ''), betreff: String(item.json.subject ?? ''), originalname: datei.fileName ?? 'beleg.pdf' },\n      binary: { data: datei },\n    });\n  }\n}\nreturn ergebnis;\n"
      },
      "notes": "Sortiert Bilder und andere Anhänge aus. Jedes PDF wird ein eigener Eintrag."
    },
    {
      "id": "31028dc6-6dd2-487b-a106-1c9eb9e31775",
      "name": "Text aus PDF lesen",
      "type": "n8n-nodes-base.extractFromFile",
      "typeVersion": 1,
      "position": [
        720,
        80
      ],
      "parameters": {
        "operation": "pdf",
        "binaryPropertyName": "data",
        "options": {
          "keepSource": "both"
        }
      },
      "notes": "Liest den Text aus dem PDF. Eingescannte Belege ohne Textebene haben wir nicht getestet; die Doku des Bausteins nennt keine Texterkennung (OCR)."
    },
    {
      "id": "e0366ae5-e02c-47a6-aa6f-a1e8f37e3617",
      "name": "Rechnungsdaten erkennen",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        960,
        80
      ],
      "parameters": {
        "jsCode": "// ===== Deine Angaben =====\nconst ORDNER = '/home/node/.n8n-files/belege'; // Zielordner, einmal von Hand anlegen\n// ==========================\nconst sauber = (s) =>\n  String(s)\n    .replace(/ä/g, 'ae').replace(/ö/g, 'oe').replace(/ü/g, 'ue')\n    .replace(/Ä/g, 'Ae').replace(/Ö/g, 'Oe').replace(/Ü/g, 'Ue').replace(/ß/g, 'ss')\n    .replace(/[^A-Za-z0-9.,_-]+/g, '-')\n    .replace(/-+/g, '-')\n    .replace(/^-|-$/g, '')\n    .slice(0, 40);\nreturn $input.all().map((item) => {\n  const text = String(item.json.text ?? '');\n  const zeilen = text.split('\\n').map((z) => z.trim()).filter(Boolean);\n  const nummer = text.match(/(?:Rechnungs(?:nummer|-?nr\\.?)|Rechnung\\s+Nr\\.?|Invoice\\s+(?:No\\.?|Number))\\s*[:#]?\\s*([A-Z0-9][A-Z0-9\\-\\/]{2,})/i)?.[1];\n  const d = text.match(/(?:Rechnungsdatum|Datum)\\s*:?\\s*(\\d{1,2})\\.(\\d{1,2})\\.(\\d{4})/i);\n  const datum = d ? `${d[3]}-${d[2].padStart(2, '0')}-${d[1].padStart(2, '0')}` : undefined;\n  const betrag = text.match(/(?:Gesamtbetrag|Rechnungsbetrag|Endbetrag|zu zahlen|Summe)\\s*:?\\s*(\\d{1,3}(?:\\.\\d{3})*,\\d{2})/i)?.[1];\n  const lieferant = zeilen[0] ?? item.json.absender;\n  const erkannt = Boolean(nummer && datum && betrag);\n  const dateiname = erkannt\n    ? `${datum}_${sauber(lieferant)}_${sauber(nummer)}_${betrag.replace(/\\./g, '')}EUR.pdf`\n    : `PRUEFEN_${new Date().toISOString().slice(0, 10)}_${sauber(item.json.originalname)}`;\n  // Alles in einem Ordner: Erkannte Belege sortieren sich nach Datum, unklare stehen dank PRUEFEN_ ganz unten.\n  const zielpfad = `${ORDNER}/${dateiname}`;\n  return {\n    json: { absender: item.json.absender, originalname: item.json.originalname, lieferant, nummer, datum, betrag, erkannt, dateiname, zielpfad },\n    binary: item.binary,\n  };\n});\n"
      },
      "notes": "Sucht Rechnungsnummer, Datum und Betrag und baut daraus den Dateinamen: Datum_Lieferant_Nummer_Betrag."
    },
    {
      "id": "c54045fe-a454-4793-a8bd-1cf90a853bb0",
      "name": "Alles erkannt?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1200,
        80
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": false,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "2e1c76c8-7372-4987-a8d4-7bb8adf01b75",
              "leftValue": "={{ $json.erkannt }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "looseTypeValidation": true,
        "options": {}
      },
      "notes": "Nur vollständig erkannte Belege werden umbenannt abgelegt."
    },
    {
      "id": "949d9b21-5df5-4d0a-a7a9-7c6892011e2b",
      "name": "Umbenannt speichern",
      "type": "n8n-nodes-base.readWriteFile",
      "typeVersion": 1,
      "position": [
        1440,
        -20
      ],
      "parameters": {
        "operation": "write",
        "fileName": "={{ $json.zielpfad }}",
        "dataPropertyName": "data",
        "options": {}
      },
      "notes": "Legt den Beleg unter dem neuen Namen im Belege-Ordner ab."
    },
    {
      "id": "ada016c7-bc55-4351-aa47-7cda8bb9e464",
      "name": "Als PRUEFEN_ speichern",
      "type": "n8n-nodes-base.readWriteFile",
      "typeVersion": 1,
      "position": [
        1440,
        180
      ],
      "parameters": {
        "operation": "write",
        "fileName": "={{ $json.zielpfad }}",
        "dataPropertyName": "data",
        "options": {}
      },
      "notes": "Unklare Belege bekommen den Vorsatz PRUEFEN_, damit nichts verloren geht."
    },
    {
      "id": "98da1fdd-0721-4903-a947-f6ce6ca88abe",
      "name": "Beleg an Lexware Office senden",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1680,
        -20
      ],
      "disabled": true,
      "parameters": {
        "method": "POST",
        "url": "https://api.lexware.io/v1/files",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpBearerAuth",
        "sendBody": true,
        "contentType": "multipart-form-data",
        "bodyParameters": {
          "parameters": [
            {
              "parameterType": "formBinaryData",
              "name": "file",
              "inputDataFieldName": "data"
            },
            {
              "name": "type",
              "value": "voucher"
            }
          ]
        },
        "options": {}
      },
      "notes": "Optional, beim Import ausgeschaltet. Lädt das PDF in Lexware Office hoch (landet dort unter \"Zu prüfen\"). Schnittstelle laut Lexware nur im Tarif XL. Nicht von uns ausgeführt."
    },
    {
      "id": "5a23e796-1af0-451e-a90a-03fb173b53c4",
      "name": "Hinweis an dich: Beleg prüfen",
      "type": "n8n-nodes-base.emailSend",
      "typeVersion": 2.1,
      "position": [
        1680,
        180
      ],
      "disabled": true,
      "parameters": {
        "fromEmail": "Ablauf <deine-adresse@example.com>",
        "toEmail": "deine-adresse@example.com",
        "subject": "={{ 'Beleg bitte prüfen: ' + $json.originalname }}",
        "emailFormat": "text",
        "text": "=Diesen Beleg konnte der Ablauf nicht vollständig lesen. Er liegt hier: {{ $json.zielpfad }}",
        "options": {
          "appendAttribution": false
        }
      },
      "notes": "Sagt dir Bescheid, wenn ein Beleg unklar war. Beim Import ausgeschaltet."
    }
  ],
  "connections": {
    "Test: Start per Klick": {
      "main": [
        [
          {
            "node": "Testdaten: 3 Rechnungen als PDF",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Neue E-Mail mit Anhang": {
      "main": [
        [
          {
            "node": "Nur PDF-Anhänge, einzeln",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Testdaten: 3 Rechnungen als PDF": {
      "main": [
        [
          {
            "node": "Nur PDF-Anhänge, einzeln",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Nur PDF-Anhänge, einzeln": {
      "main": [
        [
          {
            "node": "Text aus PDF lesen",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Text aus PDF lesen": {
      "main": [
        [
          {
            "node": "Rechnungsdaten erkennen",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Rechnungsdaten erkennen": {
      "main": [
        [
          {
            "node": "Alles erkannt?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Alles erkannt?": {
      "main": [
        [
          {
            "node": "Umbenannt speichern",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Als PRUEFEN_ speichern",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Umbenannt speichern": {
      "main": [
        [
          {
            "node": "Beleg an Lexware Office senden",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Als PRUEFEN_ speichern": {
      "main": [
        [
          {
            "node": "Hinweis an dich: Beleg prüfen",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1",
    "timezone": "Europe/Berlin"
  },
  "pinData": {}
}
