{
  "name": "Papierkram-Autopilot – 1 Belege-Eingang (Ordner)",
  "nodes": [
    {
      "name": "Notiz: Start",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -460,
        -420
      ],
      "parameters": {
        "content": "# 📥 Belege-Eingang (Ordner)\n**Papierkram-Autopilot v1.0.2** – Präsentiert von Nova Tessin (KI-Persona).\n\nLiest alle PDFs aus `Eingang/`, erkennt Datum, Lieferant und Betrag, legt sie umbenannt unter `Belege/JJJJ/MM/` ab und ergänzt die `Belegliste.csv`.\n\n### Einrichtung\n1. Ordnerstruktur anlegen (`vorlagen/ordner-anlegen.ps1` bzw. `.sh`) – **n8n legt keine Ordner selbst an.**\n2. Im Node **Einstellungen** den `basisOrdner` eintragen.\n3. Ein Test-PDF in `Eingang/` legen und **Workflow testen** klicken.\n4. Passt alles? Oben rechts **Aktiv** schalten → läuft alle 15 Minuten.\n\n⚠️ n8n darf nur auf freigegebene Ordner zugreifen (`N8N_RESTRICT_FILE_ACCESS_TO`, Standard `~/.n8n-files`). Details: ANLEITUNG.md.",
        "width": 520,
        "height": 520,
        "color": 4
      },
      "id": "2821810d-46f4-49c2-aac5-4d3ee9b2d7c4"
    },
    {
      "name": "Notiz: Erkennung",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        760,
        -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": "a0bffd9f-4631-4d00-add1-6dd6f579e72c"
    },
    {
      "name": "Notiz: Duplikate",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        1340,
        -420
      ],
      "parameters": {
        "content": "## Doppelte Belege & Originale\n- Jeder Beleg bekommt einen **Schlüssel** (Dateiname + Größe + Text-Fingerabdruck). Bereits erfasste Belege werden beim nächsten Lauf **übersprungen**.\n- Die Originale bleiben im Eingang als Sicherheitskopie (n8n hat ab Version 2 keinen Lösch-Node ohne „Execute Command“). Leere den Eingang gelegentlich von Hand – siehe ANLEITUNG.md.\n- Ergebnis: `Belege/JJJJ/MM/JJJJ-MM-TT_Lieferant_Betrag.pdf` + Zeile in `Belegliste.csv` (Semikolon, öffnet direkt in Excel/LibreOffice).\n- **Fehlt ein Zielordner**, wird der Beleg nicht eingetragen, bleibt im Eingang, und der Lauf endet mit der Meldung „Fehler melden“ (sichtbar in den Ausführungen). Nach dem Anlegen des Ordners klappt es beim nächsten Lauf.",
        "width": 520,
        "height": 300,
        "color": 7
      },
      "id": "827898a4-a263-41be-ac32-18217b139048"
    },
    {
      "name": "Manuell starten",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        -420,
        140
      ],
      "parameters": {},
      "id": "c0e2416d-a468-40b7-acb2-024ffcc2fea0"
    },
    {
      "name": "Alle 15 Minuten",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        -420,
        320
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "minutes",
              "minutesInterval": 15
            }
          ]
        }
      },
      "id": "6c06423d-8d53-4fc8-ad9c-8ec21ca92282"
    },
    {
      "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,
      "id": "414a291b-8833-49e6-a152-b5ad7e118600"
    },
    {
      "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,
      "onError": "continueRegularOutput",
      "id": "6f64b668-82e1-4e5d-a912-9c0507f23181"
    },
    {
      "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": "8e7273d3-7f84-4aee-a588-8e7cc9ddb3ee"
    },
    {
      "name": "Eingang lesen",
      "type": "n8n-nodes-base.readWriteFile",
      "typeVersion": 1.1,
      "position": [
        460,
        220
      ],
      "parameters": {
        "fileSelector": "={{ $('Einstellungen').first().json.basisOrdner.replace(/\\\\/g, '/').replace(/\\/+$/, '') + '/Eingang/*.{pdf,PDF,Pdf}' }}",
        "options": {}
      },
      "executeOnce": true,
      "onError": "continueRegularOutput",
      "id": "68b8edf2-d988-4537-a977-3475ae873e95"
    },
    {
      "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": "f4683871-3bf2-421c-a3f1-c961a1aef0e4"
    },
    {
      "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": "5a846d0a-c13a-49ed-a8c2-97ff864b19ad"
    },
    {
      "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": "2719d362-79a7-4f23-a09a-4d7792dcfa15"
    },
    {
      "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": "d5b7dab6-7c54-43ca-a83d-6d0c375d9de4"
    },
    {
      "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": "89877113-49e2-4283-a49e-afba2c63322e"
    },
    {
      "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": "18712f95-0255-4429-aa0f-defaf69681db"
    },
    {
      "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": "c5063c06-f2ea-4800-ab52-2bd2fb0c5c70"
    },
    {
      "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": "a3362252-9583-4b4a-afa3-caa57bd7ba52"
    }
  ],
  "connections": {
    "Manuell starten": {
      "main": [
        [
          {
            "node": "Einstellungen",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Alle 15 Minuten": {
      "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": "Eingang lesen",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Eingang lesen": {
      "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": []
}
