{
  "name": "Papierkram-Autopilot – 2 Belege-Eingang (E-Mail, Vorlage)",
  "nodes": [
    {
      "name": "Notiz: Start",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -520,
        -460
      ],
      "parameters": {
        "content": "# 📧 Belege-Eingang (E-Mail / IMAP)\n**Papierkram-Autopilot v1.0.2** – ⚠️ **Vorlage: gegen ein echtes Postfach ungetestet.** Die Verarbeitung ab „PDF-Anhänge sammeln“ ist identisch mit Workflow 1 und wurde mit simulierten Anhängen getestet (siehe Testbericht).\n\nHolt neue E-Mails per IMAP, nimmt PDF-Anhänge und legt sie wie Workflow 1 ab.\n\n### Einrichtung\n1. Am besten ein **eigenes Postfach/Ordner** nur für Belege nutzen (z. B. Weiterleitungsregel „Rechnung“ → Ordner `Belege`).\n2. Im Node **E-Mail-Eingang (IMAP)** neue IMAP-Zugangsdaten anlegen (bleiben verschlüsselt in **deiner** n8n-Instanz). Bei vielen Anbietern ist ein **App-Passwort** nötig.\n3. Mailbox-Name eintragen (Standard `INBOX`).\n4. Ordnerstruktur anlegen (`vorlagen/ordner-anlegen`, wie bei Workflow 1) und in **Einstellungen** den `basisOrdner` setzen.\n5. Aktiv schalten. Der Trigger prüft dann laufend auf ungelesene Mails.",
        "width": 560,
        "height": 560,
        "color": 4
      },
      "id": "cf2f1cb5-75f0-4ee7-af91-47c0e1483d60"
    },
    {
      "name": "Notiz: Erkennung",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        920,
        -420
      ],
      "parameters": {
        "content": "## Wie werden Belege erkannt?\n1. **Dateiname** im Format `JJJJ-MM-TT_Lieferant_Betrag.pdf` (z. B. `2026-09-03_Hetzner_5,83.pdf`) wird direkt übernommen.\n2. Sonst wird der **PDF-Text** gelesen: Datum (z. B. „Rechnungsdatum: 15.09.2026“), Betrag (Gesamtbetrag / Rechnungsbetrag / Summe …) und Lieferant (Regeln oder erste Zeile).\n3. Fehlt etwas → Ablage in `Belege/_Pruefen/`, Status „pruefen“.\n\n**Anpassen:** Im Node „Belege erkennen“ oben die Listen `LIEFERANTEN_REGELN` und `KATEGORIE_REGELN` ergänzen.\n\n**Grenzen:** Gescannte Bilder ohne Textebene (Fotos, reine Scans) werden **nicht** gelesen (keine Texterkennung/OCR). Prüfe Ergebnisse stichprobenartig.",
        "width": 560,
        "height": 360,
        "color": 6
      },
      "id": "3e42f521-642e-4158-a540-a27051b22ce7"
    },
    {
      "name": "E-Mail-Eingang (IMAP)",
      "type": "n8n-nodes-base.emailReadImap",
      "typeVersion": 2.2,
      "position": [
        -420,
        220
      ],
      "parameters": {
        "mailbox": "INBOX",
        "postProcessAction": "read",
        "downloadAttachments": true,
        "format": "resolved",
        "dataPropertyAttachmentsPrefixName": "attachment_",
        "options": {
          "customEmailConfig": "[\"UNSEEN\"]"
        }
      },
      "id": "2f5c6cb7-513c-4cb9-a3db-5dac07dc8825"
    },
    {
      "name": "Einstellungen",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        -200,
        220
      ],
      "parameters": {
        "mode": "manual",
        "includeOtherFields": false,
        "assignments": {
          "assignments": [
            {
              "id": "2df0d44e-8e6b-495a-a679-a086045d7970",
              "name": "basisOrdner",
              "value": "/home/node/.n8n-files/Papierkram",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "notes": "Pfad zu deinem Papierkram-Ordner. Docker-Standard: /home/node/.n8n-files/Papierkram – Windows z. B. C:/Users/DU/.n8n-files/Papierkram (Schrägstriche / verwenden).",
      "notesInFlow": true,
      "executeOnce": true,
      "id": "c3fd36f6-a77f-4591-aff4-fd00da371aff"
    },
    {
      "name": "Belegliste lesen",
      "type": "n8n-nodes-base.readWriteFile",
      "typeVersion": 1.1,
      "position": [
        20,
        220
      ],
      "parameters": {
        "fileSelector": "={{ $('Einstellungen').first().json.basisOrdner.replace(/\\\\/g, '/').replace(/\\/+$/, '') + '/Belegliste.csv' }}",
        "options": {}
      },
      "alwaysOutputData": true,
      "executeOnce": true,
      "onError": "continueRegularOutput",
      "id": "50afa6d2-3b53-4f66-ae7a-baa5ad4dc662"
    },
    {
      "name": "Belegliste als Text",
      "type": "n8n-nodes-base.extractFromFile",
      "typeVersion": 1.1,
      "position": [
        240,
        220
      ],
      "parameters": {
        "operation": "text",
        "binaryPropertyName": "data",
        "destinationKey": "data",
        "options": {
          "stripBOM": true
        }
      },
      "onError": "continueRegularOutput",
      "id": "fb3260f7-5654-4262-a57e-404fc562ef20"
    },
    {
      "name": "PDF-Anhänge sammeln",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        460,
        220
      ],
      "parameters": {
        "mode": "runOnceForAllItems",
        "language": "javaScript",
        "jsCode": "// ===== Code-Node \"PDF-Anhänge sammeln\" (Modus: Run Once for All Items) =====\n// Macht aus jeder E-Mail mit Anhaengen (attachment_0, attachment_1, ...) einzelne Items,\n// je ein PDF-Anhang pro Item im Binaerfeld \"data\". Andere Anhaenge werden ignoriert.\n\nconst ergebnis = [];\nfor (const mail of $('E-Mail-Eingang (IMAP)').all()) {\n  const anhaenge = mail.binary || {};\n  for (const [schluessel, datei] of Object.entries(anhaenge)) {\n    const name = String(datei.fileName || '');\n    const istPdf = /\\.pdf$/i.test(name) || datei.mimeType === 'application/pdf';\n    if (!istPdf) continue;\n    ergebnis.push({\n      json: {\n        email_betreff: mail.json.subject || '',\n        email_von: (mail.json.from && (mail.json.from.text || mail.json.from)) || '',\n        email_datum: mail.json.date || '',\n        anhang_feld: schluessel,\n      },\n      binary: { data: datei },\n    });\n  }\n}\nreturn ergebnis;\n"
      },
      "id": "a3fa6546-07a3-4dbb-a154-cb1f8e7db90c"
    },
    {
      "name": "PDF-Text auslesen",
      "type": "n8n-nodes-base.extractFromFile",
      "typeVersion": 1.1,
      "position": [
        680,
        220
      ],
      "parameters": {
        "operation": "pdf",
        "binaryPropertyName": "data",
        "options": {
          "joinPages": true,
          "keepSource": "binary"
        }
      },
      "onError": "continueRegularOutput",
      "id": "ae7eb651-abbd-40a4-a0ff-cd8fbf7eb8df"
    },
    {
      "name": "Belege erkennen",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        900,
        220
      ],
      "parameters": {
        "mode": "runOnceForAllItems",
        "language": "javaScript",
        "jsCode": "// ===== Code-Node \"Belege erkennen\" (Modus: Run Once for All Items) =====\n// Erkennt Datum, Lieferant und Betrag aus Dateiname und/oder PDF-Text,\n// vergibt Kategorie und Zielpfad und ueberspringt bereits erfasste Belege.\n// Anpassen: LIEFERANTEN-REGELN und KATEGORIE-REGELN weiter unten.\n\n\n// Ablage_Datei wird relativ zum basisOrdner gespeichert (Ordner bleibt verschiebbar)\n\n// --- Lieferanten-Regeln: Suchwort (im Text oder Dateinamen) -> sauberer Name ---\nconst LIEFERANTEN_REGELN = [\n  { suchwort: 'hetzner', name: 'Hetzner' },\n  { suchwort: 'telekom', name: 'Telekom' },\n  { suchwort: 'vodafone', name: 'Vodafone' },\n  { suchwort: 'amazon', name: 'Amazon' },\n  { suchwort: 'deutsche bahn', name: 'Deutsche-Bahn' },\n];\n\n// --- Kategorie-Regeln: erstes passendes Suchwort gewinnt ---\nconst KATEGORIE_REGELN = [\n  { suchwoerter: ['hosting', 'server', 'hetzner', 'domain', 'software', 'lizenz'], kategorie: 'Software & Hosting' },\n  { suchwoerter: ['telekom', 'vodafone', 'o2', 'mobilfunk', 'internet', 'telefon'], kategorie: 'Telefon & Internet' },\n  { suchwoerter: ['büro', 'buero', 'papier', 'druckerpapier', 'ordner'], kategorie: 'Bürobedarf' },\n  { suchwoerter: ['bahn', 'ticket', 'hotel', 'tankstelle'], kategorie: 'Reise & Fahrt' },\n];\n\nconst STOPP_ZEILEN = /^(rechnung|quittung|beleg|kassenbon|invoice|seite|datum|kundennummer)\\b/i;\n\nfunction pad(n) { return String(n).padStart(2, '0'); }\n\nfunction gueltigesDatum(j, m, t) {\n  const d = new Date(Date.UTC(j, m - 1, t));\n  return d.getUTCFullYear() === j && d.getUTCMonth() === m - 1 && d.getUTCDate() === t && j >= 2000 && j <= 2100;\n}\n\nfunction findeDatum(text) {\n  const muster = [\n    { re: /\\b(\\d{1,2})\\.(\\d{1,2})\\.(\\d{4})\\b/g, f: (m) => [+m[3], +m[2], +m[1]] },\n    { re: /\\b(\\d{4})-(\\d{2})-(\\d{2})\\b/g, f: (m) => [+m[1], +m[2], +m[3]] },\n  ];\n  const zeilen = text.split(/\\r?\\n/);\n  // 1. Zeilen mit Schluesselwort bevorzugen\n  const schluessel = /(rechnungsdatum|belegdatum|leistungsdatum|datum|date)/i;\n  const kandidaten = [];\n  zeilen.forEach((zeile, idx) => {\n    for (const mu of muster) {\n      mu.re.lastIndex = 0;\n      let m;\n      while ((m = mu.re.exec(zeile)) !== null) {\n        const [j, mo, t] = mu.f(m);\n        if (gueltigesDatum(j, mo, t)) kandidaten.push({ datum: `${j}-${pad(mo)}-${pad(t)}`, prio: schluessel.test(zeile) ? 0 : 1, idx });\n      }\n    }\n  });\n  kandidaten.sort((a, b) => a.prio - b.prio || a.idx - b.idx);\n  return kandidaten.length ? kandidaten[0].datum : null;\n}\n\n// \"1.234,50\" | \"119,00\" | \"1234.50\" | \"5,83\" -> Zahl\nfunction parseBetrag(s) {\n  s = String(s).trim();\n  if (/^\\d{1,3}(\\.\\d{3})+,\\d{2}$/.test(s)) return parseFloat(s.replace(/\\./g, '').replace(',', '.'));\n  if (/^\\d+,\\d{2}$/.test(s)) return parseFloat(s.replace(',', '.'));\n  if (/^\\d{1,3}(,\\d{3})+\\.\\d{2}$/.test(s)) return parseFloat(s.replace(/,/g, ''));\n  if (/^\\d+\\.\\d{2}$/.test(s)) return parseFloat(s);\n  if (/^\\d+$/.test(s)) return parseFloat(s);\n  return null;\n}\n\nconst BETRAG_RE = /(\\d{1,3}(?:\\.\\d{3})+,\\d{2}|\\d{1,3}(?:,\\d{3})+\\.\\d{2}|\\d+[.,]\\d{2})(?!\\d)/g;\n// Reihenfolge = Prioritaet\nconst BETRAG_SCHLUESSEL = ['gesamtbetrag', 'rechnungsbetrag', 'endbetrag', 'zahlbetrag', 'gesamtsumme', 'bruttobetrag', 'zu zahlen', 'summe', 'total', 'gesamt', 'betrag'];\n\nfunction findeBetrag(text) {\n  const zeilen = text.split(/\\r?\\n/);\n  for (const schluessel of BETRAG_SCHLUESSEL) {\n    const re = new RegExp('(^|[^a-zäöüß])' + schluessel + '(?![a-zäöüß])', 'i');\n    for (const zeile of zeilen) {\n      const pos = zeile.search(re);\n      if (pos === -1) continue;\n      const rest = zeile.slice(pos);\n      const treffer = rest.match(BETRAG_RE);\n      if (treffer) {\n        const wert = parseBetrag(treffer[treffer.length - 1]);\n        if (wert !== null) return wert;\n      }\n    }\n  }\n  return null;\n}\n\nfunction ersetzeUmlaute(s) {\n  return s.replace(/ä/g, 'ae').replace(/ö/g, 'oe').replace(/ü/g, 'ue').replace(/Ä/g, 'Ae').replace(/Ö/g, 'Oe').replace(/Ü/g, 'Ue').replace(/ß/g, 'ss');\n}\n\nfunction dateinamenTeil(s) {\n  return ersetzeUmlaute(String(s))\n    .replace(/&/g, 'und')\n    .replace(/[^A-Za-z0-9-]+/g, '-')\n    .replace(/-+/g, '-')\n    .replace(/^-|-$/g, '')\n    .slice(0, 40) || 'Unbekannt';\n}\n\nfunction findeLieferant(text, dateiname) {\n  const heuhaufen = (text + ' ' + dateiname).toLowerCase();\n  for (const r of LIEFERANTEN_REGELN) if (heuhaufen.includes(r.suchwort)) return r.name;\n  const zeilen = text.split(/\\r?\\n/).map((z) => z.trim()).filter((z) => z.length >= 3);\n  for (const z of zeilen) {\n    if (STOPP_ZEILEN.test(z)) continue;\n    if (/\\d{2}[.-]\\d{2}[.-]\\d{2,4}/.test(z)) continue;\n    return z.replace(/\\s+(GmbH|AG|UG|KG|e\\.K\\.|GbR|OHG|Ltd\\.?|Inc\\.?)(\\s.*)?$/i, ' $1').trim();\n  }\n  return null;\n}\n\nfunction findeKategorie(text, lieferant) {\n  const h = (text + ' ' + (lieferant || '')).toLowerCase();\n  // Kurze Suchwoerter (<= 3 Zeichen) nur als ganzes Wort, laengere auch als Wortteil (z. B. \"Webhosting\")\n  const passt = (w) => (w.length <= 3\n    ? new RegExp('(^|[^a-z0-9äöüß])' + w + '(?![a-z0-9äöüß])', 'i').test(h)\n    : h.includes(w));\n  for (const r of KATEGORIE_REGELN) if (r.suchwoerter.some(passt)) return r.kategorie;\n  return 'Sonstiges';\n}\n\nfunction betragText(n) { return n.toFixed(2).replace('.', ','); }\n\n// Einfacher Text-Fingerabdruck (FNV-1a), um Duplikate zu erkennen\nfunction fingerabdruck(s) {\n  let h = 0x811c9dc5;\n  for (let i = 0; i < s.length; i++) { h ^= s.charCodeAt(i); h = Math.imul(h, 0x01000193) >>> 0; }\n  return h.toString(16).padStart(8, '0');\n}\n\n// --- Bereits erfasste Belege aus der Belegliste lesen ---\nlet bekannt = new Set();\nlet belegteZiele = new Set();\ntry {\n  const roh = $('Belegliste als Text').first().json.data;\n  if (typeof roh === 'string' && roh.length) {\n    const zeilen = roh.replace(/^﻿/, '').split(/\\r?\\n/).filter(Boolean);\n    const kopf = zeilen.shift().split(';');\n    const iSchluessel = kopf.indexOf('Schluessel');\n    const iDatei = kopf.indexOf('Ablage_Datei');\n    for (const z of zeilen) {\n      const f = z.split(';');\n      if (iSchluessel >= 0 && f[iSchluessel]) bekannt.add(f[iSchluessel]);\n      if (iDatei >= 0 && f[iDatei]) belegteZiele.add(f[iDatei].toLowerCase());\n    }\n  }\n} catch (e) { /* Noch keine Belegliste vorhanden */ }\n\nconst ergebnis = [];\nconst jetzt = new Date();\nconst verarbeitetAm = `${jetzt.getFullYear()}-${pad(jetzt.getMonth() + 1)}-${pad(jetzt.getDate())} ${pad(jetzt.getHours())}:${pad(jetzt.getMinutes())}`;\n\nfor (const item of $input.all()) {\n  const bin = item.binary && (item.binary.data || Object.values(item.binary)[0]);\n  if (!bin) continue;\n  const original = bin.fileName || 'unbenannt.pdf';\n  const text = String(item.json.text || '');\n  const schluessel = `${original}|${bin.fileSize || ''}|${fingerabdruck(text)}`;\n  if (bekannt.has(schluessel)) continue; // schon erfasst\n  bekannt.add(schluessel);\n\n  let datum = null, lieferant = null, betrag = null, quelle = 'PDF-Text';\n  // 1. Dateinamen-Konvention JJJJ-MM-TT_Lieferant_Betrag.pdf\n  const m = original.match(/^(\\d{4})-(\\d{2})-(\\d{2})[_ ]+(.+?)[_ ]+(\\d{1,3}(?:\\.\\d{3})+,\\d{2}|\\d+(?:[.,]\\d{2})?)\\s*(?:eur|€)?\\.pdf$/i);\n  if (m && gueltigesDatum(+m[1], +m[2], +m[3])) {\n    datum = `${m[1]}-${m[2]}-${m[3]}`;\n    lieferant = m[4].replace(/_/g, ' ');\n    betrag = parseBetrag(m[5]);\n    quelle = 'Dateiname';\n  }\n  // 2. PDF-Text als Ergaenzung\n  if (!datum) datum = findeDatum(text);\n  if (betrag === null) betrag = findeBetrag(text);\n  if (!lieferant) lieferant = findeLieferant(text, original);\n\n  const vollstaendig = Boolean(datum && betrag !== null && lieferant);\n  const kategorie = findeKategorie(text + ' ' + original, lieferant);\n  let ordner, dateiname;\n  if (vollstaendig) {\n    ordner = `Belege/${datum.slice(0, 4)}/${datum.slice(5, 7)}`;\n    dateiname = `${datum}_${dateinamenTeil(lieferant)}_${betragText(betrag)}.pdf`;\n  } else {\n    ordner = `Belege/_Pruefen`;\n    dateiname = dateinamenTeil(original.replace(/\\.pdf$/i, '')) + '.pdf';\n  }\n  // Namenskollision vermeiden (_2, _3, ...)\n  let ziel = `${ordner}/${dateiname}`;\n  let n = 2;\n  while (belegteZiele.has(ziel.toLowerCase())) {\n    ziel = `${ordner}/${dateiname.replace(/\\.pdf$/i, '')}_${n}.pdf`;\n    n++;\n  }\n  belegteZiele.add(ziel.toLowerCase());\n\n  ergebnis.push({\n    json: {\n      Datum: datum || '',\n      Lieferant: lieferant || '',\n      Betrag: betrag !== null ? betragText(betrag) : '',\n      Kategorie: kategorie,\n      Status: vollstaendig ? 'erkannt' : 'pruefen',\n      Quelle: quelle,\n      Original_Datei: original,\n      Ablage_Datei: ziel,\n      Verarbeitet_am: verarbeitetAm,\n      Schluessel: schluessel,\n    },\n    binary: { data: bin },\n  });\n}\n\nreturn ergebnis;\n"
      },
      "id": "6d485f00-47b5-4dd5-a1e7-d2c7ac3050e9"
    },
    {
      "name": "Beleg ablegen",
      "type": "n8n-nodes-base.readWriteFile",
      "typeVersion": 1.1,
      "position": [
        1120,
        220
      ],
      "parameters": {
        "operation": "write",
        "fileName": "={{ $('Einstellungen').first().json.basisOrdner.replace(/\\\\/g, '/').replace(/\\/+$/, '') + '/' + $json.Ablage_Datei }}",
        "dataPropertyName": "data",
        "options": {}
      },
      "onError": "continueRegularOutput",
      "id": "f73bd793-18ef-4e14-a000-4c0751cfb3a2"
    },
    {
      "name": "Belegliste aktualisieren",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1340,
        220
      ],
      "parameters": {
        "mode": "runOnceForAllItems",
        "language": "javaScript",
        "jsCode": "// ===== Code-Node \"Belegliste aktualisieren\" (Modus: Run Once for All Items) =====\n// Haengt die erfolgreich abgelegten Belege an die bestehende Belegliste an und\n// erzeugt den kompletten CSV-Inhalt (Semikolon-getrennt, UTF-8 mit BOM fuer Excel).\n// Belege, die nicht abgelegt werden konnten (z. B. Zielordner fehlt), werden NICHT\n// eingetragen und beim naechsten Lauf erneut versucht.\n\nconst SPALTEN = ['Datum', 'Lieferant', 'Betrag', 'Kategorie', 'Status', 'Quelle', 'Original_Datei', 'Ablage_Datei', 'Verarbeitet_am', 'Schluessel'];\n\nfunction csvFeld(v) {\n  const s = String(v === undefined || v === null ? '' : v);\n  return /[;\"\\r\\n]/.test(s) ? '\"' + s.replace(/\"/g, '\"\"') + '\"' : s;\n}\n\n// Bestehende Zeilen unveraendert uebernehmen\nlet alteZeilen = [];\ntry {\n  const roh = $('Belegliste als Text').first().json.data;\n  if (typeof roh === 'string' && roh.trim().length) {\n    alteZeilen = roh.replace(/^﻿/, '').split(/\\r?\\n/).filter((z) => z.trim().length);\n    alteZeilen.shift(); // Kopfzeile\n  }\n} catch (e) { /* noch keine Belegliste */ }\n\nconst erkannt = $('Belege erkennen').all().map((i) => i.json);\nconst abgelegt = $input.all(); // Ausgabe von \"Beleg ablegen\" (gleiche Reihenfolge)\nconst neu = [];\nconst fehler = [];\nerkannt.forEach((r, i) => {\n  const out = abgelegt[i] && abgelegt[i].json;\n  if (!out || out.error) {\n    const ordner = String(r.Ablage_Datei).split('/').slice(0, -1).join('/');\n    fehler.push(`${r.Original_Datei} -> ${r.Ablage_Datei} (${out && out.error ? out.error : 'unbekannter Fehler'}; existiert der Ordner ${ordner}?)`);\n  } else {\n    neu.push(r);\n  }\n});\nconst neueZeilen = neu.map((r) => SPALTEN.map((s) => csvFeld(r[s])).join(';'));\n\nconst csv = '﻿' + [SPALTEN.join(';'), ...alteZeilen, ...neueZeilen].join('\\r\\n') + '\\r\\n';\n\nreturn [{\n  json: {\n    csv,\n    neu_erfasst: neu.length,\n    davon_pruefen: neu.filter((r) => r.Status === 'pruefen').length,\n    zeilen_gesamt: alteZeilen.length + neueZeilen.length,\n    fehler_anzahl: fehler.length,\n    fehler_text: fehler.join('\\n'),\n  },\n}];\n"
      },
      "id": "94757f85-6027-47ee-a739-f91cf95ce34e"
    },
    {
      "name": "Belegliste als Datei",
      "type": "n8n-nodes-base.convertToFile",
      "typeVersion": 1.1,
      "position": [
        1560,
        220
      ],
      "parameters": {
        "operation": "toText",
        "sourceProperty": "csv",
        "binaryPropertyName": "data",
        "options": {
          "fileName": "={{ 'Belegliste.csv' }}",
          "encoding": "utf8"
        }
      },
      "id": "64407d13-355a-4b8e-a7fd-e2dc58f65c8f"
    },
    {
      "name": "Belegliste speichern",
      "type": "n8n-nodes-base.readWriteFile",
      "typeVersion": 1.1,
      "position": [
        1780,
        220
      ],
      "parameters": {
        "operation": "write",
        "fileName": "={{ $('Einstellungen').first().json.basisOrdner.replace(/\\\\/g, '/').replace(/\\/+$/, '') + '/Belegliste.csv' }}",
        "dataPropertyName": "data",
        "options": {}
      },
      "id": "ec78c18a-c9ee-4bfa-a323-c8165843e675"
    },
    {
      "name": "Ablagefehler?",
      "type": "n8n-nodes-base.filter",
      "typeVersion": 2.2,
      "position": [
        2000,
        220
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "c74bce49-fb7e-4374-a7b3-5c572d1aba95",
              "leftValue": "={{ $('Belegliste aktualisieren').first().json.fehler_anzahl }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "gt"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "39ccf684-699a-4173-a66b-e26b2910dd5f"
    },
    {
      "name": "Fehler melden",
      "type": "n8n-nodes-base.stopAndError",
      "typeVersion": 1,
      "position": [
        2220,
        220
      ],
      "parameters": {
        "errorType": "errorMessage",
        "errorMessage": "={{ 'Papierkram-Autopilot: ' + $('Belegliste aktualisieren').first().json.fehler_anzahl + ' Beleg(e) konnten nicht abgelegt werden. Meist fehlt der Zielordner (Belege/JJJJ/MM) – bitte mit vorlagen/ordner-anlegen anlegen. Die Belege bleiben im Eingang und werden beim nächsten Lauf erneut verarbeitet. Details: ' + $('Belegliste aktualisieren').first().json.fehler_text }}"
      },
      "id": "01d56156-aea3-4f7b-a256-6f3241a8c920"
    }
  ],
  "connections": {
    "E-Mail-Eingang (IMAP)": {
      "main": [
        [
          {
            "node": "Einstellungen",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Einstellungen": {
      "main": [
        [
          {
            "node": "Belegliste lesen",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Belegliste lesen": {
      "main": [
        [
          {
            "node": "Belegliste als Text",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Belegliste als Text": {
      "main": [
        [
          {
            "node": "PDF-Anhänge sammeln",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "PDF-Anhänge sammeln": {
      "main": [
        [
          {
            "node": "PDF-Text auslesen",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "PDF-Text auslesen": {
      "main": [
        [
          {
            "node": "Belege erkennen",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Belege erkennen": {
      "main": [
        [
          {
            "node": "Beleg ablegen",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Beleg ablegen": {
      "main": [
        [
          {
            "node": "Belegliste aktualisieren",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Belegliste aktualisieren": {
      "main": [
        [
          {
            "node": "Belegliste als Datei",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Belegliste als Datei": {
      "main": [
        [
          {
            "node": "Belegliste speichern",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Belegliste speichern": {
      "main": [
        [
          {
            "node": "Ablagefehler?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Ablagefehler?": {
      "main": [
        [
          {
            "node": "Fehler melden",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1",
    "saveManualExecutions": true,
    "callerPolicy": "workflowsFromSameOwner"
  },
  "pinData": {},
  "meta": {
    "templateCredsSetupCompleted": false,
    "papierkramAutopilot": "1.0.2"
  },
  "tags": []
}
