{
  "name": "Generate XRechnung from Google Sheets",
  "nodes": [
    {
      "parameters": {},
      "id": "d40c7611-fcda-49d4-9a68-dfd93d1b0a9d",
      "name": "Manual Trigger",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        -64,
        336
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "cfg-spreadsheet",
              "name": "spreadsheetId",
              "value": "",
              "type": "string"
            },
            {
              "id": "cfg-sheet-name",
              "name": "sheetName",
              "value": "Invoices",
              "type": "string"
            },
            {
              "id": "cfg-country",
              "name": "countryCode",
              "value": "DE",
              "type": "string"
            },
            {
              "id": "cfg-format",
              "name": "format",
              "value": "xrechnung",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "bfa02cf2-2202-4ab2-9f02-02a202a202a2",
      "name": "Set Config",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        112,
        336
      ]
    },
    {
      "parameters": {
        "documentId": {
          "__rl": true,
          "value": "={{ $('Set Config').first().json.spreadsheetId }}",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "={{ $('Set Config').first().json.sheetName }}",
          "mode": "name"
        },
        "options": {}
      },
      "id": "7b01a7e8-e3c5-42de-8f8a-730c67b3c6db",
      "name": "Google Sheets: Read Invoices",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        352,
        336
      ],
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "",
          "name": "Google Sheets account"
        }
      }
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "check-no-error",
              "leftValue": "={{ $json.error }}",
              "operator": {
                "type": "string",
                "operation": "notExists",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "c8d9e0f1-a2b3-44c5-d6e7-f8a9b0c1d2e3",
      "name": "Sheets Data Valid?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.3,
      "position": [
        560,
        336
      ]
    },
    {
      "parameters": {},
      "id": "d4e5f6a7-b8c9-40d1-e2f3-a4b5c6d7e8f9",
      "name": "Sheets Error",
      "type": "n8n-nodes-base.noOp",
      "typeVersion": 1,
      "position": [
        816,
        528
      ]
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const row = $json;\nconst quantity = parseFloat(row.itemQuantity || row.item_quantity || 1);\nconst unitPrice = parseFloat(row.itemUnitPrice || row.item_unit_price || 0);\nconst taxRate = parseFloat(row.itemVatRate || row.item_vat_rate || 19);\nconst netAmount = Math.round(quantity * unitPrice * 100) / 100;\nconst taxAmount = Math.round(netAmount * taxRate) / 100;\n\nconst countrySpecific = { countryCode: row.buyerCountryCode || 'DE' };\nif (row.leitwegId) countrySpecific.leitwegId = String(row.leitwegId);\nif (row.buyerReference) countrySpecific.buyerReference = String(row.buyerReference);\nif (!countrySpecific.leitwegId && !countrySpecific.buyerReference) countrySpecific.buyerReference = row.invoiceNumber || 'N/A';\n\nconst seller = {\n  name: row.sellerName || '',\n  vatId: row.sellerVatId || '',\n  email: row.sellerEmail || '',\n  phone: row.sellerPhone || '',\n  street: row.sellerStreet || '',\n  city: row.sellerCity || '',\n  postalCode: String(row.sellerPostalCode || ''),\n  countryCode: row.sellerCountryCode || 'DE'\n};\nconst paymentMethods = [];\nif (row.sellerIban) {\n  const iban = String(row.sellerIban).replace(/\\s+/g, '');\n  seller.bankAccount = {\n    iban,\n    bic: row.sellerBic || '',\n    bankName: row.sellerBankName || '',\n    accountHolder: row.sellerAccountHolder || row.sellerName || ''\n  };\n  paymentMethods.push({\n    type: 'bank_transfer',\n    details: `IBAN: ${iban}${row.sellerBic ? ', BIC: ' + row.sellerBic : ''}`\n  });\n}\n\nconst invoice = {\n  type: 'invoice',\n  invoiceNumber: row.invoiceNumber || '',\n  issueDate: row.issueDate || new Date().toISOString().split('T')[0],\n  dueDate: row.dueDate || '',\n  currency: 'EUR',\n  seller,\n  buyer: {\n    name: row.buyerName || '',\n    street: row.buyerStreet || '',\n    city: row.buyerCity || '',\n    postalCode: String(row.buyerPostalCode || ''),\n    countryCode: row.buyerCountryCode || 'DE'\n  },\n  items: [{\n    position: 1,\n    description: row.itemDescription || 'Service',\n    quantity,\n    unit: row.itemUnit || 'HUR',\n    unitPrice,\n    taxRate,\n    netAmount,\n    taxAmount,\n    grossAmount: Math.round((netAmount + taxAmount) * 100) / 100\n  }],\n  taxSummary: [{ taxRate, taxableAmount: netAmount, taxAmount, netAmount }],\n  paymentTerms: {\n    dueDays: 30,\n    description: row.paymentDescription || 'Zahlbar innerhalb von 30 Tagen ohne Abzug'\n  },\n  paymentMethods,\n  subtotal: netAmount,\n  total: Math.round((netAmount + taxAmount) * 100) / 100,\n  countrySpecific\n};\nreturn { json: { invoiceNumber: invoice.invoiceNumber, buyerEmail: row.buyerEmail || '', invoiceData: JSON.stringify(invoice) } };"
      },
      "id": "b2921c35-300a-45d2-9c1d-e88666ee8a45",
      "name": "Map to Invoice JSON",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        816,
        320
      ]
    },
    {
      "parameters": {
        "countryCode": {
          "__rl": true,
          "value": "={{ $('Set Config').first().json.countryCode }}",
          "mode": "expression"
        },
        "format": {
          "__rl": true,
          "value": "={{ $('Set Config').first().json.format }}",
          "mode": "expression"
        },
        "invoiceData": "={{ $json.invoiceData }}",
        "options": {}
      },
      "id": "31794fca-cdaf-4fd5-9520-511d2f901f1c",
      "name": "Generate XRechnung",
      "type": "n8n-nodes-invoice-api-xhub.invoiceXhub",
      "typeVersion": 1,
      "position": [
        1056,
        320
      ],
      "credentials": {
        "invoiceXhubApi": {
          "id": "",
          "name": "Invoice-api.xhub Account"
        }
      }
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "check-gen-ok",
              "leftValue": "={{ $json.success }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "9befe647-6036-40de-a537-332f0e180ae3",
      "name": "Generated OK?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.3,
      "position": [
        1264,
        320
      ]
    },
    {
      "parameters": {
        "sendTo": "={{ $('Map to Invoice JSON').item.json.buyerEmail }}",
        "subject": "=XRechnung {{ $('Map to Invoice JSON').item.json.invoiceNumber }}",
        "message": "=Sehr geehrte Damen und Herren,\n\nanbei erhalten Sie die XRechnung {{ $('Map to Invoice JSON').item.json.invoiceNumber }}.\n\nMit freundlichen Gruessen",
        "options": {
          "attachmentsUi": {
            "attachmentsBinary": [
              {}
            ]
          }
        }
      },
      "id": "ca2f29e2-d8ab-4768-b8ba-38448971ed89",
      "name": "Gmail: Send XRechnung",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.2,
      "webhookId": "cd43c6d4-45cc-4473-b924-bbfe9338ff6c",
      "position": [
        1536,
        224
      ],
      "credentials": {
        "gmailOAuth2": {
          "id": "",
          "name": "Gmail account"
        }
      }
    },
    {
      "parameters": {
        "operation": "update",
        "documentId": {
          "__rl": true,
          "value": "={{ $('Set Config').first().json.spreadsheetId }}",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "={{ $('Set Config').first().json.sheetName }}",
          "mode": "name"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "invoiceNumber": "={{ $('Map to Invoice JSON').item.json.invoiceNumber }}",
            "status": "sent",
            "sentAt": "={{ new Date().toISOString() }}"
          },
          "matchingColumns": [
            "invoiceNumber"
          ],
          "schema": [
            {
              "id": "invoiceNumber",
              "displayName": "invoiceNumber",
              "required": false,
              "defaultMatch": true,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "status",
              "displayName": "status",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": false
            },
            {
              "id": "sentAt",
              "displayName": "sentAt",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": false
            }
          ]
        },
        "options": {}
      },
      "id": "1a23be2d-88ea-4a0f-a71f-27721ce29cb7",
      "name": "Google Sheets: Update Status",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        1776,
        224
      ],
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "",
          "name": "Google Sheets account"
        }
      }
    },
    {
      "parameters": {},
      "id": "3d5cf2af-e290-4574-a9fc-25bd5c177351",
      "name": "Generation Error",
      "type": "n8n-nodes-base.noOp",
      "typeVersion": 1,
      "position": [
        1536,
        448
      ]
    },
    {
      "parameters": {
        "content": "## Google Sheet → XRechnung → Mail\n\nRows tagged `status = pending` in the `Invoices` tab become XRechnung XML, get emailed to the buyer, and flip to `status = sent`. Country and format are set in **Set Config** (defaults: `DE` / `xrechnung`; other EU formats work too).\n\n**Before you activate** — drop your Spreadsheet ID into **Set Config**, connect Gmail + Google Sheets + invoice-api.xhub, and have at least one row with `status = pending`. The example CSV in the repo has all required column names.",
        "height": 320,
        "width": 1740,
        "color": 1
      },
      "id": "72332d31-3c5b-4b95-b63c-664b331a82b3",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -128,
        -624
      ]
    },
    {
      "parameters": {
        "content": "## 1. Read & Validate\nRead pending rows from Google Sheets and check required fields.",
        "height": 420,
        "width": 860
      },
      "id": "84725d2b-9848-4473-bd8a-db172a55f7bb",
      "name": "Section: Read Data",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -128,
        240
      ]
    },
    {
      "parameters": {
        "content": "## 2. Map & Generate XRechnung\nConvert sheet rows to the XRechnung schema and generate EN 16931-compliant XML.",
        "height": 256,
        "width": 650
      },
      "id": "f7a7801f-edf4-4cc1-a6ef-95851ed03aeb",
      "name": "Section: Generate",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        768,
        240
      ]
    },
    {
      "parameters": {
        "content": "## 3. Send & Update Status\nEmail the XRechnung to the buyer and mark the row as sent.",
        "height": 468,
        "width": 524
      },
      "id": "dea6d70b-2f19-4462-875e-ce6a8465e707",
      "name": "Section: Send",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        1456,
        128
      ]
    },
    {
      "parameters": {
        "content": "⚠️ **Configure here:** Replace `your-spreadsheet-id` and set `sheetName`, `countryCode`, `format` to match your sheet and target e-invoice format before activating.",
        "height": 116,
        "width": 300,
        "color": 3
      },
      "id": "warn-setconfig-02",
      "name": "Warning: Configure Set Config",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -96,
        96
      ]
    }
  ],
  "connections": {
    "Manual Trigger": {
      "main": [
        [
          {
            "node": "Set Config",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Sheets: Read Invoices": {
      "main": [
        [
          {
            "node": "Sheets Data Valid?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sheets Data Valid?": {
      "main": [
        [
          {
            "node": "Map to Invoice JSON",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Sheets Error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Map to Invoice JSON": {
      "main": [
        [
          {
            "node": "Generate XRechnung",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate XRechnung": {
      "main": [
        [
          {
            "node": "Generated OK?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generated OK?": {
      "main": [
        [
          {
            "node": "Gmail: Send XRechnung",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Generation Error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Gmail: Send XRechnung": {
      "main": [
        [
          {
            "node": "Google Sheets: Update Status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Config": {
      "main": [
        [
          {
            "node": "Google Sheets: Read Invoices",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  }
}