{
  "nodes": [
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "triggerAtHour": 8
            }
          ]
        }
      },
      "id": "bdc247e7-71f8-48bf-9d60-54288669741d",
      "name": "1. Monthly Schedule",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.1,
      "position": [
        352,
        -752
      ],
      "notes": "Runs on 1st of each month at 8:00 AM"
    },
    {
      "parameters": {
        "jsCode": "/**\n * Calculate previous month date range\n */\nconst now = new Date();\nconst year = now.getMonth() === 0 ? now.getFullYear() - 1 : now.getFullYear();\nconst month = now.getMonth() === 0 ? 12 : now.getMonth();\nconst monthPadded = String(month).padStart(2, '0');\n\nreturn {\n  json: {\n    year,\n    month,\n    monthPadded,\n    periodName: `${year}-${monthPadded}`,\n    exportFilename: `DATEV_Export_${year}-${monthPadded}.csv`\n  }\n};"
      },
      "id": "0b00dc4a-0de2-4e3d-a511-deca4bd1a04a",
      "name": "2. Calculate Period",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        560,
        -752
      ]
    },
    {
      "parameters": {
        "resource": "table",
        "operation": "getRows",
        "workbook": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $env.APPROVAL_TRACKING_WORKBOOK_ID }}"
        },
        "worksheet": {
          "__rl": true,
          "mode": "name",
          "value": "Approvals"
        },
        "table": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "filters": {}
      },
      "id": "4872d98b-8856-46e7-89ce-aa2c7e1dd032",
      "name": "3. Get Approved Invoices",
      "type": "n8n-nodes-base.microsoftExcel",
      "typeVersion": 2,
      "position": [
        752,
        -752
      ],
      "credentials": {
        "microsoftExcelOAuth2Api": {
          "id": "gn9O9iJYh6osoSAu",
          "name": "Microsoft Excel account"
        }
      },
      "notes": "⚠️ SETUP: Set APPROVAL_TRACKING_WORKBOOK_ID env variable"
    },
    {
      "parameters": {
        "jsCode": "/**\n * DATEV EXTF Format Generator (SKR03)\n * Generates DATEV-compatible CSV export\n */\n\nconst period = $('2. Calculate Period').first().json;\nconst invoices = $input.all().map(item => item.json);\n\n// Filter for the export period\nconst targetYear = period.year;\nconst targetMonth = period.month;\n\nconst filteredInvoices = invoices.filter(inv => {\n  if (!inv.CreatedAt) return false;\n  const date = new Date(inv.CreatedAt);\n  return date.getFullYear() === targetYear && (date.getMonth() + 1) === targetMonth;\n});\n\n// DATEV Header (EXTF Format)\nconst header = [\n  'EXTF', '700', '21', 'Buchungsstapel', '7', // Format version\n  '', '', '', '', '',  // Reserved\n  period.year, period.monthPadded + '01', period.monthPadded + String(new Date(targetYear, targetMonth, 0).getDate()).padStart(2, '0'),\n  'NEG AG Export', 'n8n Automation', '', '', '', '', '', '', '', '', ''\n].join(';');\n\n// Column headers\nconst columnHeaders = [\n  'Umsatz (ohne Soll/Haben-Kz)', 'Soll/Haben-Kennzeichen', 'WKZ Umsatz', 'Kurs', 'Basis-Umsatz',\n  'WKZ Basis-Umsatz', 'Konto', 'Gegenkonto (ohne BU-Schlüssel)', 'BU-Schlüssel', 'Belegdatum',\n  'Belegfeld 1', 'Belegfeld 2', 'Skonto', 'Buchungstext', 'Postensperre', 'Diverse Adressnummer',\n  'Geschäftspartnerbank', 'Sachverhalt', 'Zinssperre', 'Beleglink'\n].join(';');\n\n// Data rows\nconst dataRows = filteredInvoices.map(inv => {\n  const amount = parseFloat(inv.Amount) || 0;\n  const accountCode = inv.AccountCode || '70000';\n  const contraAccount = inv.VendorAccountCode || '1200';\n  const invoiceDate = inv.InvoiceDate ? new Date(inv.InvoiceDate).toLocaleDateString('de-DE', {day: '2-digit', month: '2-digit'}).replace(/\\./g, '') : '';\n  \n  return [\n    amount.toFixed(2).replace('.', ','), // Umsatz\n    'S', // Soll (Debit)\n    'EUR', // Currency\n    '', // Exchange rate\n    '', // Base amount\n    '', // Base currency\n    contraAccount, // Konto (Expense account)\n    accountCode, // Gegenkonto (Vendor/Creditor)\n    '', // BU-Schlüssel\n    invoiceDate, // Belegdatum (DDMM)\n    inv.InvoiceNumber || '', // Belegfeld 1\n    inv.InternalNumber || '', // Belegfeld 2\n    '', // Skonto\n    (inv.VendorName || '').substring(0, 60), // Buchungstext (max 60 chars)\n    '', '', '', '', '', '' // Remaining fields\n  ].join(';');\n});\n\n// Combine all\nconst csvContent = [header, columnHeaders, ...dataRows].join('\\n');\n\nreturn {\n  json: {\n    ...period,\n    invoiceCount: filteredInvoices.length,\n    totalAmount: filteredInvoices.reduce((sum, inv) => sum + (parseFloat(inv.Amount) || 0), 0),\n    csvContent,\n    csvBase64: Buffer.from(csvContent, 'utf-8').toString('base64')\n  }\n};"
      },
      "id": "c2d27366-8f03-4b7b-a23e-218d6cfa121e",
      "name": "4. Generate DATEV CSV",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        960,
        -752
      ],
      "notes": "EXTF 7.0 format, SKR03 chart of accounts"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "leftValue": "={{ $json.invoiceCount }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "gt"
              }
            }
          ]
        },
        "options": {}
      },
      "id": "8762f92a-da25-46ee-8762-cd37df079dbe",
      "name": "Has Invoices?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        1152,
        -752
      ]
    },
    {
      "parameters": {
        "fileName": "={{ $json.exportFilename }}"
      },
      "id": "506927b6-c209-4bf2-be02-8b55703eeb95",
      "name": "5. Upload to OneDrive",
      "type": "n8n-nodes-base.microsoftOneDrive",
      "typeVersion": 1,
      "position": [
        1360,
        -864
      ],
      "disabled": true,
      "notes": "Saves to /50_Buchhaltung/DATEV_Export/"
    },
    {
      "parameters": {
        "subject": "=📊 DATEV Export {{ $json.periodName }} - {{ $json.invoiceCount }} Rechnungen",
        "additionalFields": {}
      },
      "id": "0103283e-d296-4f41-946e-2fe950176721",
      "name": "6. Notify Heike",
      "type": "n8n-nodes-base.microsoftOutlook",
      "typeVersion": 2,
      "position": [
        1568,
        -864
      ],
      "webhookId": "2104a75a-9aa9-4f47-b877-4e2b23553a31",
      "credentials": {
        "microsoftOutlookOAuth2Api": {
          "id": "bLEDW2m8bjLKhT9l",
          "name": "Microsoft Outlook account"
        }
      },
      "disabled": true
    },
    {
      "parameters": {
        "subject": "=ℹ️ DATEV Export {{ $json.periodName }} - Keine Rechnungen",
        "additionalFields": {}
      },
      "id": "c91ab573-9c63-48b1-8814-b688e605af00",
      "name": "No Data Notification",
      "type": "n8n-nodes-base.microsoftOutlook",
      "typeVersion": 2,
      "position": [
        1360,
        -656
      ],
      "webhookId": "55283498-5837-4734-9950-b11e8bc5e4e9",
      "credentials": {
        "microsoftOutlookOAuth2Api": {
          "id": "bLEDW2m8bjLKhT9l",
          "name": "Microsoft Outlook account"
        }
      },
      "disabled": true
    },
    {
      "parameters": {
        "options": {}
      },
      "id": "67a658c6-ce83-4da0-8e81-19509e451502",
      "name": "Complete",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3,
      "position": [
        1760,
        -864
      ],
      "disabled": true
    },
    {
      "parameters": {
        "resource": "accountingSequence"
      },
      "type": "@klardaten/n8n-nodes-datevconnect.accounting",
      "typeVersion": 1,
      "position": [
        544,
        1056
      ],
      "id": "6c1e8008-98f2-4b0e-a7e3-ffbe9831c325",
      "name": "Get many accounting sequences",
      "disabled": true
    },
    {
      "parameters": {
        "content": "## Understand The extent of DATEV Use & Automation Before Implementation  (community Node Installed)",
        "height": 384,
        "width": 400
      },
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        496,
        896
      ],
      "typeVersion": 1,
      "id": "badf0f56-eafc-49b7-bc08-feaa55ad92ac",
      "name": "Sticky Note"
    },
    {
      "parameters": {
        "content": "## I'm a note \n**Double click** to edit me. [Guide](https://docs.n8n.io/workflows/components/sticky-notes/)\n\nAll the data every month\n"
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        1808,
        -400
      ],
      "id": "3dde5b40-7aac-4820-9c9f-60007ab2c36a",
      "name": "Sticky Note1"
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 8 1 * *"
            }
          ]
        }
      },
      "id": "3ab9c633-5634-4ed0-92f2-e00e3edce957",
      "name": "1. Monthly Schedule1",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.1,
      "position": [
        -704,
        800
      ]
    },
    {
      "parameters": {
        "jsCode": "const now = new Date();\nconst year = now.getMonth() === 0 ? now.getFullYear() - 1 : now.getFullYear();\nconst month = now.getMonth() === 0 ? 12 : now.getMonth();\nconst monthPadded = String(month).padStart(2, '0');\n\nreturn {\n  json: {\n    year,\n    month,\n    monthPadded,\n    periodName: `${year}-${monthPadded}`,\n    exportFilename: `DATEV_Export_${year}-${monthPadded}.csv`\n  }\n};"
      },
      "id": "f5a16adb-cfe4-440f-b1a9-ad792e0c9b5d",
      "name": "2. Calculate Period1",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -496,
        800
      ]
    },
    {
      "parameters": {
        "resource": "worksheet",
        "operation": "readRows",
        "workbook": {
          "__rl": true,
          "value": "016ZC2UENNX3FINPJ7TBFJT6K37BFNTZJD",
          "mode": "list",
          "cachedResultName": "NEG_Rechnungseingangsbuch_Template 1"
        },
        "worksheet": {
          "__rl": true,
          "value": "{00000000-0001-0000-0000-000000000000}",
          "mode": "list",
          "cachedResultName": "Rechnungseingangsbuch"
        },
        "options": {}
      },
      "id": "4f1a0041-eb18-4835-84b3-13b605f2f0d4",
      "name": "3. Get Approved Invoices1",
      "type": "n8n-nodes-base.microsoftExcel",
      "typeVersion": 2.2,
      "position": [
        -304,
        800
      ],
      "credentials": {
        "microsoftExcelOAuth2Api": {
          "id": "gn9O9iJYh6osoSAu",
          "name": "Microsoft Excel account"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const period = $('2. Calculate Period1').first().json;\nconst invoices = $input.all().map(item => item.json);\n\n// Helper: convert Excel serial date to JS Date\nfunction excelDateToJS(serial) {\n  if (!serial || isNaN(serial)) return null;\n  return new Date(Math.round((serial - 25569) * 86400 * 1000));\n}\n\n// Filter: only Genehmigt invoices for the target period\nconst targetYear = period.year;\nconst targetMonth = period.month;\n\nconst filteredInvoices = invoices.filter(inv => {\n  const status = (inv['Zahlungsstatus'] || '').trim();\n  if (status !== 'Genehmigt') return false;\n  const dateJS = excelDateToJS(inv['RE-Datum']);\n  if (!dateJS) return false;\n  return dateJS.getFullYear() === targetYear && (dateJS.getMonth() + 1) === targetMonth;\n});\n\n// DATEV EXTF Header\nconst lastDay = new Date(targetYear, targetMonth, 0).getDate();\nconst header = [\n  '\"EXTF\"', '700', '21', '\"Buchungsstapel\"', '7',\n  '', '', '', '', '',\n  `${targetYear}${period.monthPadded}01`,\n  `${targetYear}${period.monthPadded}${String(lastDay).padStart(2, '0')}`,\n  '', '', '', '', '',\n  '', '', '', '', '', ''\n].join(';');\n\n// Column headers\nconst columnHeaders = [\n  'Umsatz (ohne Soll/Haben-Kz)',\n  'Soll/Haben-Kennzeichen',\n  'WKZ Umsatz',\n  'Kurs',\n  'Basis-Umsatz',\n  'WKZ Basis-Umsatz',\n  'Konto',\n  'Gegenkonto (ohne BU-Schluessel)',\n  'BU-Schluessel',\n  'Belegdatum',\n  'Belegfeld 1',\n  'Belegfeld 2',\n  'Skonto',\n  'Buchungstext',\n  'Postensperre',\n  'Diverse Adressnummer',\n  'Geschaeftspartnerbank',\n  'Sachverhalt',\n  'Zinssperre',\n  'Beleglink'\n].join(';');\n\n// Data rows\nconst dataRows = filteredInvoices.map(inv => {\n  const amount = parseFloat(inv['Bruttobetrag']) || 0;\n  const accountCode = inv['Buchungskonto'] ? String(inv['Buchungskonto']) : '70000';\n  const contraAccount = inv['Kred.-Konto'] ? String(inv['Kred.-Konto']) : '1600';\n  const currency = inv['Waehrung'] || 'EUR';\n  const vendorName = (inv['Lieferant'] || '').substring(0, 60);\n  const invoiceNumber = String(inv['RE-Nr.'] || '');\n  const internalNumber = String(inv['Lfd. Nr.'] || '');\n\n  const dateJS = excelDateToJS(inv['RE-Datum']);\n  const invoiceDate = dateJS\n    ? String(dateJS.getDate()).padStart(2, '0') + String(dateJS.getMonth() + 1).padStart(2, '0')\n    : '';\n\n  return [\n    amount.toFixed(2).replace('.', ','),\n    'S',\n    currency,\n    '',\n    '',\n    '',\n    contraAccount,\n    accountCode,\n    '',\n    invoiceDate,\n    invoiceNumber,\n    internalNumber,\n    '',\n    vendorName,\n    '', '', '', '', '', ''\n  ].join(';');\n});\n\nconst csvContent = [header, columnHeaders, ...dataRows].join('\\n');\nconst totalAmount = filteredInvoices.reduce((sum, inv) => sum + (parseFloat(inv['Bruttobetrag']) || 0), 0);\n\nreturn {\n  json: {\n    ...period,\n    invoiceCount: filteredInvoices.length,\n    totalAmount: totalAmount.toFixed(2),\n    csvContent,\n    csvBase64: Buffer.from('\\uFEFF' + csvContent, 'utf-8').toString('base64')\n  }\n};"
      },
      "id": "40f7cbd1-33c4-43e9-b9a1-172a73ac2a4b",
      "name": "4. Generate DATEV CSV1",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -96,
        800
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "leftValue": "={{ $json.invoiceCount }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "gt"
              }
            }
          ]
        },
        "options": {}
      },
      "id": "af61ecb1-a402-4607-be9a-d8994e4bb417",
      "name": "Has Invoices?1",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        96,
        800
      ]
    },
    {
      "parameters": {
        "fileName": "={{ $json.exportFilename }}",
        "fileContent": "={{ $json.csvBase64 }}"
      },
      "id": "851e0207-8fdc-4219-b066-9c0be839fdb4",
      "name": "5. Upload to OneDrive1",
      "type": "n8n-nodes-base.microsoftOneDrive",
      "typeVersion": 1,
      "position": [
        304,
        688
      ],
      "credentials": {
        "microsoftOneDriveOAuth2Api": {
          "id": "gn9O9iJYh6osoSAu",
          "name": "Microsoft OneDrive account"
        }
      }
    },
    {
      "parameters": {
        "toRecipients": "heike@neg-ag.com",
        "subject": "={{ '📊 DATEV Export ' + $('4. Generate DATEV CSV1').first().json.periodName + ' - ' + $('4. Generate DATEV CSV1').first().json.invoiceCount + ' Rechnungen' }}",
        "bodyContent": "={{ 'Guten Tag Heike,\\n\\nder DATEV-Export fuer den Monat ' + $('4. Generate DATEV CSV1').first().json.periodName + ' wurde erfolgreich erstellt.\\n\\nAnzahl Rechnungen: ' + $('4. Generate DATEV CSV1').first().json.invoiceCount + '\\nGesamtbetrag: EUR ' + $('4. Generate DATEV CSV1').first().json.totalAmount + '\\nDateiname: ' + $('4. Generate DATEV CSV1').first().json.exportFilename + '\\n\\nDie Datei wurde in OneDrive unter /50_Buchhaltung/DATEV_Export/ gespeichert.\\n\\nMit freundlichen Gruessen,\\nNEG Rechnungssystem' }}",
        "additionalFields": {}
      },
      "id": "4ed29136-8536-44cd-b17b-85c02cf13847",
      "name": "6. Notify Heike1",
      "type": "n8n-nodes-base.microsoftOutlook",
      "typeVersion": 2,
      "position": [
        512,
        688
      ],
      "webhookId": "f39ffab7-f107-4a91-98a7-089a150397ef",
      "credentials": {
        "microsoftOutlookOAuth2Api": {
          "id": "JkENlhnvoCwv5aDV",
          "name": "Saits - Vansh"
        }
      }
    },
    {
      "parameters": {
        "toRecipients": "heike@neg-ag.com",
        "subject": "={{ 'ℹ️ DATEV Export ' + $json.periodName + ' - Keine Rechnungen' }}",
        "bodyContent": "={{ 'Guten Tag Heike,\\n\\nfuer den Monat ' + $json.periodName + ' wurden keine genehmigten Rechnungen gefunden.\\nEs wurde kein DATEV-Export erstellt.\\n\\nMit freundlichen Gruessen,\\nNEG Rechnungssystem' }}",
        "additionalFields": {}
      },
      "id": "5e58c68a-865a-4365-83af-e979ef37d726",
      "name": "No Data Notification1",
      "type": "n8n-nodes-base.microsoftOutlook",
      "typeVersion": 2,
      "position": [
        304,
        896
      ],
      "webhookId": "c587b3d0-1bdd-4a79-9bf9-7ce7aec8eaf5",
      "credentials": {
        "microsoftOutlookOAuth2Api": {
          "id": "JkENlhnvoCwv5aDV",
          "name": "Saits - Vansh"
        }
      }
    },
    {
      "parameters": {
        "options": {}
      },
      "id": "97a2c19d-c43c-4f2f-8fdf-1d3036795bb6",
      "name": "Complete1",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3,
      "position": [
        704,
        688
      ]
    }
  ],
  "connections": {
    "1. Monthly Schedule": {
      "main": [
        [
          {
            "node": "2. Calculate Period",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "2. Calculate Period": {
      "main": [
        [
          {
            "node": "3. Get Approved Invoices",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "3. Get Approved Invoices": {
      "main": [
        [
          {
            "node": "4. Generate DATEV CSV",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "4. Generate DATEV CSV": {
      "main": [
        [
          {
            "node": "Has Invoices?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Has Invoices?": {
      "main": [
        [
          {
            "node": "5. Upload to OneDrive",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "No Data Notification",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "5. Upload to OneDrive": {
      "main": [
        [
          {
            "node": "6. Notify Heike",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "6. Notify Heike": {
      "main": [
        [
          {
            "node": "Complete",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "1. Monthly Schedule1": {
      "main": [
        [
          {
            "node": "2. Calculate Period1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "2. Calculate Period1": {
      "main": [
        [
          {
            "node": "3. Get Approved Invoices1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "3. Get Approved Invoices1": {
      "main": [
        [
          {
            "node": "4. Generate DATEV CSV1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "4. Generate DATEV CSV1": {
      "main": [
        [
          {
            "node": "Has Invoices?1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Has Invoices?1": {
      "main": [
        [
          {
            "node": "5. Upload to OneDrive1",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "No Data Notification1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "5. Upload to OneDrive1": {
      "main": [
        [
          {
            "node": "6. Notify Heike1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "6. Notify Heike1": {
      "main": [
        [
          {
            "node": "Complete1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "pinData": {},
  "meta": {
    "instanceId": "9c1de76795e8803a2ffa944dee021ff74564866a2c2ffdc6044fed6d9556cfc1"
  }
}