{
  "name": "Eingangsrechnungen aus E-Mail auslesen und ablegen",
  "meta": {
    "beschreibung": "Holt PDF-Rechnungen aus einem IMAP-Postfach, liest Rechnungsnummer, Datum, Betrag, USt und Lieferant per KI aus, legt sie strukturiert in Google Sheets ab und meldet Unstimmigkeiten. DSGVO-Hinweis: Bei personenbezogenen Daten in Rechnungen einen AV-Vertrag mit dem KI-Anbieter schliessen oder ein lokales Modell (Ollama) verwenden.",
    "kategorie": "Rechnungen & Buchhaltung",
    "schwierigkeit": "mittel",
    "benoetigt": ["IMAP-Postfach", "OpenAI-API (oder Ollama)", "Google Sheets"],
    "quelle": "https://die-ki-agentur.de/n8n-agentur/"
  },
  "nodes": [
    {
      "parameters": {
        "content": "## Eingangsrechnungen automatisch erfassen\n\n**Ablauf:** IMAP-Postfach alle 15 Min pruefen → PDF-Anhaenge filtern → Text extrahieren → KI liest Rechnungsdaten → Plausibilitaetspruefung → Google Sheets.\n\n**Vor dem Start:**\n1. IMAP-Zugangsdaten hinterlegen (eigener Ordner `Rechnungen` empfohlen)\n2. Google-Sheet mit Spalten: Datum, Lieferant, Rechnungsnummer, Netto, USt, Brutto, Datei\n3. Bei personenbezogenen Daten: AV-Vertrag mit dem KI-Anbieter oder lokales Modell\n\n**Betreuung:** https://die-ki-agentur.de/n8n-agentur/",
        "height": 420,
        "width": 460
      },
      "id": "note-doku",
      "name": "Kurzanleitung",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [-140, -60]
    },
    {
      "parameters": {
        "pollTimes": {"item": [{"mode": "everyX", "value": 15, "unit": "minutes"}]},
        "format": "resolved",
        "options": {"customEmailConfig": "[\"UNSEEN\"]", "downloadAttachments": true}
      },
      "id": "trigger-imap",
      "name": "Posteingang pruefen",
      "type": "n8n-nodes-base.emailReadImap",
      "typeVersion": 2,
      "position": [380, 200]
    },
    {
      "parameters": {
        "conditions": {
          "options": {"caseSensitive": false, "version": 2},
          "conditions": [{
            "id": "hat-pdf",
            "leftValue": "={{ Object.keys($binary || {}).length }}",
            "rightValue": 0,
            "operator": {"type": "number", "operation": "gt"}
          }],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "filter-anhang",
      "name": "Hat PDF-Anhang?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [600, 200]
    },
    {
      "parameters": {
        "operation": "pdf",
        "binaryPropertyName": "attachment_0",
        "options": {}
      },
      "id": "pdf-text",
      "name": "PDF-Text extrahieren",
      "type": "n8n-nodes-base.extractFromFile",
      "typeVersion": 1,
      "position": [820, 120]
    },
    {
      "parameters": {
        "modelId": {"__rl": true, "value": "gpt-4.1-mini", "mode": "list"},
        "messages": {"values": [
          {"role": "system", "content": "Du liest deutsche Eingangsrechnungen aus. Antworte AUSSCHLIESSLICH mit einem JSON-Objekt, keine Erklaerung. Felder: lieferant (string), rechnungsnummer (string), rechnungsdatum (YYYY-MM-DD), netto (number), ust_satz (number), ust_betrag (number), brutto (number), waehrung (string), leistungszeitraum (string oder null). Unbekannte Werte: null. Deutsche Zahlenformate (1.234,56) in Punktnotation umrechnen."},
          {"role": "user", "content": "={{ $json.text }}"}
        ]},
        "jsonOutput": true,
        "options": {"temperature": 0}
      },
      "id": "ki-auslesen",
      "name": "Rechnungsdaten auslesen",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "typeVersion": 1.8,
      "position": [1040, 120]
    },
    {
      "parameters": {
        "jsCode": "// Plausibilitaetspruefung: Netto + USt muss Brutto ergeben (1 Cent Toleranz\n// wegen kaufmaennischer Rundung). Faengt die typischen KI-Lesefehler ab,\n// bevor falsche Zahlen in der Buchhaltung landen.\nconst raus = [];\nfor (const eintrag of $input.all()) {\n  const r = eintrag.json.message?.content ?? eintrag.json;\n  const netto = Number(r.netto ?? 0);\n  const ust = Number(r.ust_betrag ?? 0);\n  const brutto = Number(r.brutto ?? 0);\n  const abweichung = Math.abs(netto + ust - brutto);\n  raus.push({\n    json: {\n      ...r,\n      betrag_plausibel: abweichung <= 0.01,\n      abweichung_eur: Number(abweichung.toFixed(2)),\n      pflichtfelder_vollstaendig: Boolean(r.lieferant && r.rechnungsnummer && r.rechnungsdatum),\n      erfasst_am: new Date().toISOString().slice(0, 10),\n    },\n  });\n}\nreturn raus;"
      },
      "id": "pruefen",
      "name": "Betraege pruefen",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [1260, 120]
    },
    {
      "parameters": {
        "conditions": {
          "options": {"caseSensitive": true, "version": 2},
          "conditions": [
            {"id": "plausibel", "leftValue": "={{ $json.betrag_plausibel }}", "rightValue": true, "operator": {"type": "boolean", "operation": "true", "singleValue": true}},
            {"id": "vollstaendig", "leftValue": "={{ $json.pflichtfelder_vollstaendig }}", "rightValue": true, "operator": {"type": "boolean", "operation": "true", "singleValue": true}}
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "weiche",
      "name": "Sauber ausgelesen?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [1480, 120]
    },
    {
      "parameters": {
        "operation": "append",
        "documentId": {"__rl": true, "value": "HIER_SHEET_ID_EINTRAGEN", "mode": "id"},
        "sheetName": {"__rl": true, "value": "Eingangsrechnungen", "mode": "name"},
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "Erfasst am": "={{ $json.erfasst_am }}",
            "Lieferant": "={{ $json.lieferant }}",
            "Rechnungsnummer": "={{ $json.rechnungsnummer }}",
            "Rechnungsdatum": "={{ $json.rechnungsdatum }}",
            "Netto": "={{ $json.netto }}",
            "USt": "={{ $json.ust_betrag }}",
            "Brutto": "={{ $json.brutto }}",
            "Waehrung": "={{ $json.waehrung }}"
          }
        },
        "options": {}
      },
      "id": "sheets",
      "name": "In Buchhaltung eintragen",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [1700, 20]
    },
    {
      "parameters": {
        "assignments": {"assignments": [
          {"id": "grund", "name": "pruefgrund", "value": "={{ $json.betrag_plausibel ? 'Pflichtfelder unvollstaendig' : 'Netto + USt weicht um ' + $json.abweichung_eur + ' EUR vom Brutto ab' }}", "type": "string"},
          {"id": "aktion", "name": "naechster_schritt", "value": "Rechnung manuell pruefen und nachtragen", "type": "string"}
        ]},
        "options": {}
      },
      "id": "manuell",
      "name": "Zur manuellen Pruefung",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [1700, 220]
    }
  ],
  "connections": {
    "Posteingang pruefen": {"main": [[{"node": "Hat PDF-Anhang?", "type": "main", "index": 0}]]},
    "Hat PDF-Anhang?": {"main": [[{"node": "PDF-Text extrahieren", "type": "main", "index": 0}], []]},
    "PDF-Text extrahieren": {"main": [[{"node": "Rechnungsdaten auslesen", "type": "main", "index": 0}]]},
    "Rechnungsdaten auslesen": {"main": [[{"node": "Betraege pruefen", "type": "main", "index": 0}]]},
    "Betraege pruefen": {"main": [[{"node": "Sauber ausgelesen?", "type": "main", "index": 0}]]},
    "Sauber ausgelesen?": {"main": [[{"node": "In Buchhaltung eintragen", "type": "main", "index": 0}], [{"node": "Zur manuellen Pruefung", "type": "main", "index": 0}]]}
  },
  "settings": {"executionOrder": "v1"},
  "pinData": {}
}
