{
  "name": "Papierkram-Autopilot – 4 Rechnungs-Erinnerung",
  "nodes": [
    {
      "name": "Notiz: Start",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -460,
        -460
      ],
      "parameters": {
        "content": "# ⏰ Rechnungs-Erinnerung\n**Papierkram-Autopilot v1.0.2**\n\nLiest deine **eigene** Liste offener Ausgangsrechnungen (`offene-rechnungen.csv`) und schreibt eine Liste aller überfälligen Rechnungen nach `Berichte/Ueberfaellige-Rechnungen_JJJJ-MM-TT.md` – mit Tagen überfällig und Vorschlag für die Mahnstufe.\n\n### CSV-Format (Semikolon, erste Zeile = Kopf)\n`Rechnungsnummer;Kunde;Betrag;Rechnungsdatum;Faellig_am;Bezahlt_am`\nDatum als `2026-10-15` oder `15.10.2026`. Bezahlt? → `Bezahlt_am` ausfüllen. Vorlage: `vorlagen/offene-rechnungen.csv`.\n\n### Einrichtung\n1. **Einstellungen** → `basisOrdner`; `stichtag` leer = heute.\n2. Aktiv schalten → läuft werktags um 8 Uhr.\n\n📧 E-Mail-Node ist **deaktiviert** (optional, SMTP nötig). Der Workflow verschickt **nie** selbst Mahnungen an Kunden.",
        "width": 540,
        "height": 560,
        "color": 4
      },
      "id": "ef8b6060-a500-47be-aa6f-7aa6b855f762"
    },
    {
      "name": "Manuell starten",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        -420,
        140
      ],
      "parameters": {},
      "id": "9bd0c03c-2212-47fa-a465-30f6a51b26a4"
    },
    {
      "name": "Werktags 8 Uhr",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        -420,
        320
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 8 * * 1-5"
            }
          ]
        }
      },
      "id": "980fd64f-fa79-4701-a2cb-1150e7406a61"
    },
    {
      "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"
            },
            {
              "id": "43a63a29-ce02-4a62-a667-be88d2471735",
              "name": "stichtag",
              "value": "",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "notes": "stichtag: leer = heute, sonst JJJJ-MM-TT",
      "notesInFlow": true,
      "id": "57075c05-9686-4e9c-a8d0-d3e987dc1f91"
    },
    {
      "name": "Rechnungsliste lesen",
      "type": "n8n-nodes-base.readWriteFile",
      "typeVersion": 1.1,
      "position": [
        20,
        220
      ],
      "parameters": {
        "fileSelector": "={{ $('Einstellungen').first().json.basisOrdner.replace(/\\\\/g, '/').replace(/\\/+$/, '') + '/offene-rechnungen.csv' }}",
        "options": {}
      },
      "alwaysOutputData": true,
      "onError": "continueRegularOutput",
      "id": "4154f0fa-73d5-4e14-a1b2-e27bd47f63be"
    },
    {
      "name": "Rechnungsliste 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": "5ecaca21-34da-4ef9-a4ae-2d28e740bc04"
    },
    {
      "name": "Überfällige finden",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        460,
        220
      ],
      "parameters": {
        "mode": "runOnceForAllItems",
        "language": "javaScript",
        "jsCode": "// ===== Code-Node \"Überfällige finden\" (Modus: Run Once for All Items) =====\n// Liest offene-rechnungen.csv (Semikolon-getrennt) und listet ueberfaellige Rechnungen.\n// Spalten: Rechnungsnummer;Kunde;Betrag;Rechnungsdatum;Faellig_am;Bezahlt_am\n// Datumsformat JJJJ-MM-TT oder TT.MM.JJJJ. Stichtag leer = heute.\n\nconst cfg = $('Einstellungen').first().json;\nconst roh = String($('Rechnungsliste als Text').first().json.data || '').replace(/^﻿/, '');\n\nfunction pad(n) { return String(n).padStart(2, '0'); }\nfunction zahl(s) { const t = String(s || '').replace(/\\s|€|EUR/gi, '').replace(/\\./g, '').replace(',', '.'); const n = parseFloat(t); return isNaN(n) ? 0 : n; }\nfunction eur(n) { return n.toFixed(2).replace('.', ',').replace(/\\B(?=(\\d{3})+(?!\\d))/g, '.') + ' €'; }\nfunction datum(s) {\n  s = String(s || '').trim();\n  let m = s.match(/^(\\d{4})-(\\d{2})-(\\d{2})$/); if (m) return `${m[1]}-${m[2]}-${m[3]}`;\n  m = s.match(/^(\\d{1,2})\\.(\\d{1,2})\\.(\\d{4})$/); if (m) return `${m[3]}-${pad(m[2])}-${pad(m[1])}`;\n  return null;\n}\nfunction tageZwischen(a, b) { return Math.round((Date.parse(b + 'T00:00:00Z') - Date.parse(a + 'T00:00:00Z')) / 86400000); }\nfunction zerlege(zeile) {\n  const out = []; let cur = ''; let q = false;\n  for (let i = 0; i < zeile.length; i++) {\n    const c = zeile[i];\n    if (q) { if (c === '\"' && zeile[i + 1] === '\"') { cur += '\"'; i++; } else if (c === '\"') q = false; else cur += c; }\n    else if (c === '\"') q = true; else if (c === ';') { out.push(cur); cur = ''; } else cur += c;\n  }\n  out.push(cur); return out;\n}\n\nlet stichtag = datum(cfg.stichtag);\nif (!stichtag) { const d = new Date(); stichtag = `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`; }\n\nconst zeilen = roh.split(/\\r?\\n/).filter((z) => z.trim().length);\nconst kopf = zeilen.length ? zerlege(zeilen.shift()).map((k) => k.trim()) : [];\nconst rows = zeilen.map((z) => { const f = zerlege(z); const o = {}; kopf.forEach((k, i) => { o[k] = (f[i] || '').trim(); }); return o; });\n\nconst fehlerhaft = [];\nconst ueberfaellig = [];\nfor (const r of rows) {\n  if (r.Bezahlt_am) continue; // bezahlt\n  const faellig = datum(r.Faellig_am);\n  if (!faellig) { fehlerhaft.push(r.Rechnungsnummer || '(ohne Nummer)'); continue; }\n  const tage = tageZwischen(faellig, stichtag);\n  if (tage > 0) ueberfaellig.push({ ...r, faellig, tage, betrag: zahl(r.Betrag) });\n}\nueberfaellig.sort((a, b) => b.tage - a.tage);\nconst summe = ueberfaellig.reduce((s, r) => s + r.betrag, 0);\nconst stufe = (t) => (t > 60 ? '3 – dringend' : t > 30 ? '2 – nachfassen' : '1 – freundlich erinnern');\n\nconst md = [];\nmd.push(`# Überfällige Rechnungen – Stand ${stichtag}`);\nmd.push('');\nmd.push(`- **Überfällig:** ${ueberfaellig.length} Rechnung(en)`);\nmd.push(`- **Offene Summe:** ${eur(summe)}`);\nmd.push('');\nif (ueberfaellig.length) {\n  md.push('| Rechnung | Kunde | Betrag | Fällig am | Tage überfällig | Vorschlag |');\n  md.push('|---|---|---:|---|---:|---|');\n  for (const r of ueberfaellig) md.push(`| ${r.Rechnungsnummer} | ${r.Kunde} | ${eur(r.betrag)} | ${r.faellig} | ${r.tage} | ${stufe(r.tage)} |`);\n} else {\n  md.push('Keine überfälligen Rechnungen.');\n}\nif (fehlerhaft.length) {\n  md.push('');\n  md.push(`**Hinweis:** Bei diesen Zeilen fehlt ein gültiges Fälligkeitsdatum: ${fehlerhaft.join(', ')}`);\n}\nmd.push('');\nmd.push('> Arbeitshilfe, keine Rechtsberatung. Prüfe vor jeder Zahlungserinnerung, ob die Zahlung nicht doch schon eingegangen ist.');\nmd.push('');\n\nconst basis = String(cfg.basisOrdner).replace(/\\\\/g, '/').replace(/\\/+$/, '');\nreturn [{\n  json: {\n    stichtag,\n    anzahl: ueberfaellig.length,\n    summe: Math.round(summe * 100) / 100,\n    rechnungen: ueberfaellig.map((r) => r.Rechnungsnummer),\n    markdown: md.join('\\n'),\n    pfad_md: `${basis}/Berichte/Ueberfaellige-Rechnungen_${stichtag}.md`,\n    betreff: `${ueberfaellig.length} überfällige Rechnung(en), ${eur(summe)}`,\n  },\n}];\n"
      },
      "id": "e237b5b7-11a2-4ed9-ad6c-8bb1bd6f557f"
    },
    {
      "name": "Liste als Datei",
      "type": "n8n-nodes-base.convertToFile",
      "typeVersion": 1.1,
      "position": [
        700,
        220
      ],
      "parameters": {
        "operation": "toText",
        "sourceProperty": "markdown",
        "binaryPropertyName": "data",
        "options": {
          "fileName": "={{ 'Ueberfaellige-Rechnungen_' + $json.stichtag + '.md' }}",
          "encoding": "utf8"
        }
      },
      "id": "7113ed3a-e2d4-4cad-a756-486814d64a6b"
    },
    {
      "name": "Liste speichern",
      "type": "n8n-nodes-base.readWriteFile",
      "typeVersion": 1.1,
      "position": [
        920,
        220
      ],
      "parameters": {
        "operation": "write",
        "fileName": "={{ $('Überfällige finden').first().json.pfad_md }}",
        "dataPropertyName": "data",
        "options": {}
      },
      "id": "fdc3d2ee-2900-466a-a17d-5cc33515996c"
    },
    {
      "name": "Nur wenn überfällig",
      "type": "n8n-nodes-base.filter",
      "typeVersion": 2.2,
      "position": [
        1140,
        220
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "261e0657-d6f1-4fe5-a948-5aba5ee44d15",
              "leftValue": "={{ $('Überfällige finden').first().json.anzahl }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "gt"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "4aa28fad-5af1-4f60-a100-10545a6bb733"
    },
    {
      "name": "Erinnerung an mich (optional)",
      "type": "n8n-nodes-base.emailSend",
      "typeVersion": 2.1,
      "position": [
        1360,
        220
      ],
      "disabled": true,
      "parameters": {
        "fromEmail": "absender@example.com",
        "toEmail": "du@example.com",
        "subject": "={{ $('Überfällige finden').first().json.betreff }}",
        "emailFormat": "text",
        "text": "={{ $('Überfällige finden').first().json.markdown }}",
        "options": {}
      },
      "id": "29bf46ed-78f3-4108-a41f-c8d9d8121c1a"
    }
  ],
  "connections": {
    "Manuell starten": {
      "main": [
        [
          {
            "node": "Einstellungen",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Werktags 8 Uhr": {
      "main": [
        [
          {
            "node": "Einstellungen",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Einstellungen": {
      "main": [
        [
          {
            "node": "Rechnungsliste lesen",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Rechnungsliste lesen": {
      "main": [
        [
          {
            "node": "Rechnungsliste als Text",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Rechnungsliste als Text": {
      "main": [
        [
          {
            "node": "Überfällige finden",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Überfällige finden": {
      "main": [
        [
          {
            "node": "Liste als Datei",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Liste als Datei": {
      "main": [
        [
          {
            "node": "Liste speichern",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Liste speichern": {
      "main": [
        [
          {
            "node": "Nur wenn überfällig",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Nur wenn überfällig": {
      "main": [
        [
          {
            "node": "Erinnerung an mich (optional)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1",
    "saveManualExecutions": true,
    "callerPolicy": "workflowsFromSameOwner"
  },
  "pinData": {},
  "meta": {
    "templateCredsSetupCompleted": false,
    "papierkramAutopilot": "1.0.2"
  },
  "tags": []
}
