npm package: @pixelfarben/n8n-nodes-lexware-office
Description: n8n Community Node for Lexware Office API - Manage contacts, invoices, articles, vouchers and more.
Author: Andreas Henkel
Latest version: 0.3.0

# n8n-nodes-lexware-office

> n8n Community Node fuer die [Lexware Office API](https://developers.lexoffice.io/docs/) - Verwalte Kontakte, Rechnungen, Artikel, Belege und mehr direkt aus n8n-Workflows heraus.

**Package:** `@pixelfarben/n8n-nodes-lexware-office`
**Version:** 0.2.2
**Lizenz:** MIT
**Autor:** Andreas Henkel

---

## Inhaltsverzeichnis

- [Projektstruktur](#projektstruktur)
- [Architektur](#architektur)
- [Ressourcen und Operationen](#ressourcen-und-operationen)
- [API-Routing](#api-routing)
- [Credentials und Lizenzierung](#credentials-und-lizenzierung)
- [Workflows](#workflows)
- [Technische Patterns und Konventionen](#technische-patterns-und-konventionen)
- [Bekannte Besonderheiten der Lexware API](#bekannte-besonderheiten-der-lexware-api)
- [Entwicklung](#entwicklung)
- [Weiterentwicklung - Offene Aufgaben](#weiterentwicklung---offene-aufgaben)

---

## Projektstruktur

```
lexware n8n node/
├── .npmrc                          # npm Registry-Konfiguration
├── .prettierrc.js                  # Code-Formatierung (Tabs, Single Quotes)
├── package.json                    # n8n-Node-Package-Konfiguration
├── tsconfig.json                   # TypeScript-Konfiguration (ES2019, CommonJS)
├── LICENSE.md                      # MIT-Lizenz
├── README.md                       # Diese Datei
│
├── credentials/
│   └── LexwareOfficeApi.credentials.ts   # Credential-Definition (License Key + API Key)
│
├── icons/
│   └── lexware.svg                 # Node-Icon fuer n8n UI
│
├── nodes/
│   └── LexwareOffice/
│       ├── LexwareOffice.node.ts   # HAUPTDATEI: Node-Logik, Routing, Execute
│       └── resources/              # Eine Datei pro API-Ressource
│           ├── article/index.ts
│           ├── contact/index.ts
│           ├── country/index.ts
│           ├── creditNote/index.ts
│           ├── deliveryNote/index.ts
│           ├── eventSubscription/index.ts
│           ├── invoice/index.ts
│           ├── orderConfirmation/index.ts
│           ├── postingCategory/index.ts
│           ├── profile/index.ts
│           ├── quotation/index.ts
│           ├── voucher/index.ts
│           └── voucherlist/index.ts
│
├── workflows/                      # 24 importierbare n8n-Workflow-JSON-Dateien
│   ├── 01-belegmanagement/         # 8 Workflows (inkl. KI-Varianten)
│   ├── 02-rechnungswesen/          # 4 Workflows
│   ├── 03-mahnwesen/               # 3 Workflows
│   ├── 04-kontakte/                # 3 Workflows
│   ├── 05-reporting/               # 4 Workflows
│   └── 06-automatisierung/         # 2 Workflows
│
└── dist/                           # Build-Output (nicht im Git)
```

---

## Architektur

### Node-Aufbau (Programmatic Style)

Die Node nutzt den **programmatic style** von n8n (nicht declarative). Die gesamte Logik befindet sich in `LexwareOffice.node.ts`:

```
LexwareOffice.node.ts
│
├── verifyLicense()        # Lizenspruefung gegen license.webugo.de (1h Cache)
├── getApiRoute()          # Mappt resource + operation → HTTP method + URL
├── buildQueryParams()     # Baut Query-Parameter aus Node-Parametern
├── buildBody()            # Baut Request-Body aus Node-Parametern
│
└── class LexwareOffice
    ├── description        # Node-Metadata, Properties (alle Resource-Descriptions importiert)
    └── execute()          # Hauptlogik: License Check → Pro Item → Route → Request → Response
```

### Resource-Dateien

Jede Resource-Datei (`resources/*/index.ts`) exportiert ein `INodeProperties[]`-Array mit:
- Operation-Auswahl (z.B. Create, Get, Get Many, Update, Delete)
- Parameter-Definitionen (IDs, Filter, Body-JSON, Paginierung)
- `displayOptions` mit `{ show: { resource: ['resourceName'] } }` zur bedingten Anzeige

### Ablauf einer Execution

```
1. execute() wird aufgerufen
2. Lizenz wird geprueft (Cache: 1h TTL, Fallback bei Server-Ausfall)
3. Fuer jedes Input-Item:
   a. resource + operation auslesen
   b. getApiRoute() → { method, url, isFile? }
   c. buildQueryParams() → Query-String
   d. buildBody() → JSON-Body (nur bei create/update)
   e. httpRequestWithAuthentication() → Lexware API
   f. Response verarbeiten:
      - Array → einzelne Items
      - Objekt → ein Item
      - File → Binary-Data (PDF)
```

---

## Ressourcen und Operationen

| Ressource | Operationen | API-Endpunkt | Besonderheiten |
|-----------|-------------|--------------|----------------|
| **Contact** | create, get, getAll, update | `/v1/contacts` | Filter: email, name, number, customer, vendor. Paginierung (page, size). **getAll gibt `content`-Array zurueck** |
| **Invoice** | create, get, downloadFile | `/v1/invoices` | `?finalize=true` moeglich. `precedingSalesVoucherId` fuer Folgbelege |
| **Credit Note** | create, get, downloadFile | `/v1/credit-notes` | Wie Invoice |
| **Quotation** | create, get, downloadFile | `/v1/quotations` | Wie Invoice |
| **Order Confirmation** | create, get, downloadFile | `/v1/order-confirmations` | Wie Invoice |
| **Delivery Note** | create, get, downloadFile | `/v1/delivery-notes` | Wie Invoice |
| **Article** | create, get, getAll, update, delete | `/v1/articles` | Filter: articleNumber, gtin, type. Paginierung |
| **Voucher** | create, get, update | `/v1/vouchers` | Body als JSON-String (`voucherBody`). `categoryId` referenziert Posting Categories |
| **Voucherlist** | getAll | `/v1/voucherlist` | Umfangreiche Filter: voucherType, voucherStatus, contactId, Datumsbereich. **Gibt `content`-Array zurueck** |
| **Event Subscription** | create, get, getAll, delete | `/v1/event-subscriptions` | Body: `eventType` + `callbackUrl` |
| **Posting Category** | getAll | `/v1/posting-categories` | Gibt Array von Buchungskategorien zurueck (id, name, type, groupName, contactRequired, splitAllowed) |
| **Profile** | get | `/v1/profile` | Gibt Organisationsprofil zurueck (taxType, smallBusiness, features) |
| **Country** | getAll | `/v1/countries` | Statische Laenderliste |

---

## API-Routing

Die Funktion `getApiRoute()` in `LexwareOffice.node.ts` (ab Zeile 70) mappt jede Kombination aus Resource und Operation auf die korrekte API-URL:

```
contact/create       → POST   /v1/contacts
contact/get          → GET    /v1/contacts/{contactId}
contact/getAll       → GET    /v1/contacts
contact/update       → PUT    /v1/contacts/{contactId}
invoice/create       → POST   /v1/invoices
invoice/get          → GET    /v1/invoices/{invoiceId}
invoice/downloadFile → GET    /v1/invoices/{invoiceId}/file  (isFile: true)
article/delete       → DELETE /v1/articles/{articleId}
voucherlist/getAll   → GET    /v1/voucherlist
postingCategory/getAll → GET  /v1/posting-categories
profile/get          → GET    /v1/profile
country/getAll       → GET    /v1/countries
```

**Neue Resource hinzufuegen:**
1. `resources/newResource/index.ts` erstellen (exportiert `newResourceDescription`)
2. In `LexwareOffice.node.ts`:
   - Import hinzufuegen
   - Resource-Option zum `options`-Array hinzufuegen (alphabetisch!)
   - `...newResourceDescription` zu `properties` hinzufuegen
   - `case 'newResource':` in `getApiRoute()` hinzufuegen
   - Optional: `buildQueryParams()` und `buildBody()` erweitern

---

## Credentials und Lizenzierung

### Credential-Felder (`LexwareOfficeApi.credentials.ts`)

| Feld | Typ | Beschreibung |
|------|-----|-------------|
| `licenseKey` | password | Webugo-Lizenzschluessel (Pflicht) |
| `apiKey` | password | Lexware Office API Bearer Token (Pflicht) |

### Authentifizierung

- **API-Auth:** Bearer Token im `Authorization`-Header
- **Lizenz-Check:** GET gegen `https://license.webugo.de/api/n8n/verify/{licenseKey}`
  - In-Memory-Cache mit 1h TTL
  - Bei Server-Ausfall: Fallback auf abgelaufenen Cache
  - Ohne gueltige Lizenz: `NodeApiError` wird geworfen
- **Credential-Test:** GET `/v1/profile` (prueft ob API-Key gueltig ist)

### API-Basis-URL

Aktuell: `https://api.lexoffice.io` (hardcoded in `requestDefaults` und `execute()`)

**ACHTUNG:** Lexware hat auf `https://api.lexware.io` umgestellt (Mai 2025). Die alte URL funktioniert noch bis Dezember 2025. Migration auf neue URL steht noch aus.

---

## Workflows

### Uebersicht (24 Workflows)

#### 01 - Belegmanagement

| Datei | Name | KI | Beschreibung |
|-------|------|:--:|-------------|
| `wf01a-*-openai.json` | KI-Belegkategorisierung (OpenAI) | GPT-4o-mini | Belege automatisch kategorisieren mit echten Lexware-Buchungskategorien |
| `wf01b-*-gemini.json` | KI-Belegkategorisierung (Gemini) | Gemini 2.0 Flash | Identisch, nur anderer KI-Provider |
| `wf01c-*-anthropic.json` | KI-Belegkategorisierung (Anthropic) | Claude Sonnet | Identisch, nur anderer KI-Provider |
| `wf02a-*-openai.json` | Eingangsrechnungen aus E-Mail (OpenAI) | GPT-4o-mini | Rechnungsdaten aus E-Mails extrahieren, Kontakt suchen/erstellen |
| `wf02b-*-gemini.json` | Eingangsrechnungen aus E-Mail (Gemini) | Gemini 2.0 Flash | Identisch, nur anderer KI-Provider |
| `wf02c-*-anthropic.json` | Eingangsrechnungen aus E-Mail (Anthropic) | Claude Sonnet | Identisch, nur anderer KI-Provider |
| `wf03-*.json` | Belegarchivierung in Google Drive | - | Belege automatisch in Google Drive archivieren |
| `wf04-*.json` | Belegzuordnung zu Kontakten | - | Belege automatisch Kontakten zuordnen |

#### 02 - Rechnungswesen

| Datei | Name | Beschreibung |
|-------|------|-------------|
| `wf05-*.json` | Angebot -> Rechnung Pipeline | Webhook-getrieben: Angebot → Auftragsbestaetigung → Rechnung |
| `wf06-*.json` | Wiederkehrende Rechnungen | Zeitgesteuert: Monatliche Rechnungen aus Vorlage |
| `wf07-*.json` | Sammelrechnung Lieferscheine | Lieferscheine gruppieren und als Sammelrechnung |
| `wf08-*.json` | Rechnung PDF per E-Mail | PDF generieren und per Gmail versenden |

#### 03 - Mahnwesen

| Datei | Name | Beschreibung |
|-------|------|-------------|
| `wf09-*.json` | 3-Stufen Mahnwesen | Ueberfaellige Rechnungen in 3 Stufen mahnen |
| `wf10-*.json` | Ueberfaellige Rechnungen Dashboard | Taegliches Dashboard per Slack |
| `wf11-*.json` | Zahlungseingang bestaetigen | Webhook bei Zahlung: Bestaetigung per E-Mail |

#### 04 - Kontakte

| Datei | Name | Beschreibung |
|-------|------|-------------|
| `wf12-*.json` | Kontakte aus Webformular | Webhook: Kontakt anlegen wenn nicht vorhanden |
| `wf13-*.json` | Duplikate erkennen | Taeglich pruefen auf doppelte Kontakte |
| `wf14-*.json` | CRM-Sync Google Sheets | Kontakte nach Google Sheets synchronisieren |

#### 05 - Reporting

| Datei | Name | Beschreibung |
|-------|------|-------------|
| `wf15-*.json` | Monatlicher Umsatzbericht | Umsatz aggregiert per Slack/E-Mail |
| `wf16-*.json` | Top-10 Kunden Umsatz | Ranking der umsatzstaerksten Kunden |
| `wf17-*.json` | USt-VA Vorbereitung | Vorsteuerdaten fuer Umsatzsteuervoranmeldung |
| `wf18-*.json` | DATEV-Export | CSV-Export im DATEV-nahen Format |

#### 06 - Automatisierung

| Datei | Name | Beschreibung |
|-------|------|-------------|
| `wf19-*.json` | Artikelpreise aus Google Sheet | Preise aus Sheet lesen und Artikel aktualisieren |
| `wf20-*.json` | Echtzeit-Benachrichtigungen | Slack-Hub fuer Lexware-Events |

### Workflow-Architektur (KI-Workflows WF01)

```
Schedule Trigger (alle 30 Min)
  │
  ▼
Lexware Buchungskategorien (postingCategory/getAll)
  │  Laedt die echten Buchungskategorien aus dem Lexware-Konto
  ▼
Kategorien zusammenfassen (Code)
  │  Komprimiert alle Kategorie-Items in ein Array
  ▼
Lexware Belege abrufen (voucherlist/getAll, Status: unchecked)
  │
  ▼
Belege extrahieren (Code)
  │  Extrahiert content-Array in einzelne Items
  ▼
Belege einzeln verarbeiten (SplitInBatches)
  │
  ├──▶ Beleg-Details abrufen (voucher/get)
  │      │
  │      ▼
  │    Kategorien & Prompt (Code)
  │      │  Baut System-Prompt mit echten Kategorien
  │      │  + User-Prompt mit Belegdaten
  │      ▼
  │    KI Kategorisierung (OpenAI / Gemini / Anthropic)
  │      │
  │      ▼
  │    KI-Antwort verarbeiten (Code)
  │      │  Parst JSON aus KI-Antwort
  │      ▼
  │    Beleg aktualisieren (voucher/update)
  │      │
  └──◀──┘  (Loop)
```

### Wichtige Code-Patterns in Workflows

#### content-Array extrahieren (PFLICHT bei getAll-Operationen)

Die Lexware API gibt bei `voucherlist/getAll` und `contact/getAll` ein Objekt mit `content`-Array zurueck:

```json
{ "content": [{ "id": "...", ... }, { "id": "...", ... }], "totalPages": 1, "totalElements": 3 }
```

Die n8n-Node gibt dieses Objekt als **ein einziges Item** weiter. Daher muss immer ein Code-Node nachgeschaltet werden:

```javascript
// Belege extrahieren
const rawData = $input.first().json;
const items = rawData.content || [];
return items.map(v => ({ json: v }));
```

Ohne diesen Schritt sind Felder wie `$json.id` `undefined`.

#### Kontakt extrahieren (bei contact/getAll)

```javascript
// Kontakt extrahieren - gibt erstes Ergebnis oder _contactExists: false
const rawData = $input.first().json;
const contacts = rawData.content || [];
if (contacts.length > 0) {
  return [{ json: { _contactExists: true, ...contacts[0] } }];
} else {
  return [{ json: { _contactExists: false } }];
}
```

#### KI-Antwort parsen (fuer alle drei KI-Provider)

```javascript
let responseText = item.text || item.content || item.message || '';
if (typeof responseText === 'object') responseText = JSON.stringify(responseText);
const jsonMatch = responseText.match(/\{[\s\S]*\}/);
if (!jsonMatch) throw new Error('Kein JSON in KI-Antwort gefunden');
const ai = JSON.parse(jsonMatch[0]);
```

#### Referenz auf fruehere Nodes im Loop

```javascript
// Daten von einem Node AUSSERHALB des Loops referenzieren
const categories = $('Kategorien zusammenfassen').first().json.categories;
```

### KI-Provider Native n8n Nodes

| Provider | n8n Node Type | Model | Credential |
|----------|--------------|-------|------------|
| OpenAI | `@n8n/n8n-nodes-langchain.openAi` | `gpt-4o-mini` | `openAiApi` |
| Google Gemini | `@n8n/n8n-nodes-langchain.googleGemini` | `models/gemini-2.0-flash` | `googlePalmApi` |
| Anthropic | `@n8n/n8n-nodes-langchain.anthropic` | `claude-sonnet-4-20250514` | `anthropicApi` |

Alle verwenden: `resource: "text"`, `operation: "message"`, `simplifyOutput: true`, `jsonOutput: true`, `temperature: 0.2`

---

## Bekannte Besonderheiten der Lexware API

### content-Array bei Listenabfragen

`voucherlist/getAll`, `contact/getAll` und `article/getAll` geben **kein Array** zurueck, sondern ein Paginierungs-Objekt mit einem `content`-Array. Die Node gibt dieses als einzelnes Item weiter. In Workflows muss das `content`-Array immer manuell extrahiert werden (siehe oben).

### Posting Categories

`GET /v1/posting-categories` gibt ein **flaches Array** (kein Paginierungs-Objekt) zurueck. Jede Kategorie hat:

```json
{
  "id": "uuid",
  "name": "Buerobedarf",
  "type": "expense",
  "contactRequired": false,
  "splitAllowed": true,
  "groupName": "Sonstige betriebliche Aufwendungen"
}
```

Diese Kategorien sind **kontoabhaengig** und spiegeln den im Lexware-Konto konfigurierten Kontenrahmen (SKR03 oder SKR04) wider.

### Profile-Endpoint

`GET /v1/profile` gibt u.a. zurueck:
- `taxType`: Besteuerungsart (z.B. Regelbesteuerung)
- `smallBusiness`: Kleinunternehmerregelung (ja/nein)
- `features`: Aktivierte Features im Konto

### API-Rate-Limits

- **2 Requests pro Sekunde** (Token-Bucket-Algorithmus)
- HTTP 429 bei Ueberschreitung
- In Workflows mit `SplitInBatches` sind Pausen ggf. noetig

### API-URL Migration

- **Alt:** `https://api.lexoffice.io` (funktioniert bis Dezember 2025)
- **Neu:** `https://api.lexware.io` (produktiv seit Mai 2025)
- Im Node-Code ist aktuell noch die alte URL hardcoded (`requestDefaults` und `execute()`)

### Voucher voucherType-Werte

Fuer `voucherlist/getAll` muss `voucherType` explizit gesetzt werden (nicht leer lassen!):
- `salesinvoice`, `salescreditnote`, `purchaseinvoice`, `purchasecreditnote`
- `invoice`, `creditnote`, `orderconfirmation`, `quotation`, `deliverynote`
- Leeres Array `[]` fuehrt zu "voucherType ist nicht definiert"-Fehler

---

## Entwicklung

### Voraussetzungen

- Node.js >= 22
- npm

### Setup

```bash
git clone https://github.com/andreashenkel/n8n-nodes-lexware-office.git
cd n8n-nodes-lexware-office
npm install
```

### Lokale Entwicklung

```bash
npm run dev    # Startet n8n mit geladener Node + Hot-Reload
```

### Build

```bash
npm run build  # Kompiliert TypeScript nach dist/
```

### Linting

```bash
npm run lint
npm run lint:fix
```

### Code-Style

- Tabs fuer Einrueckung
- Single Quotes
- Trailing Commas
- Print Width: 100
- LF Line Endings

---

## Weiterentwicklung - Offene Aufgaben

### Prioritaet Hoch

- [ ] **API-URL auf `https://api.lexware.io` migrieren** - Alte URL laeuft Dezember 2025 aus. Betrifft `requestDefaults.baseURL` und die hardcoded URL in `execute()` (Zeile ~538)
- [ ] **Fehlende API-Endpunkte implementieren:**
  - `Payment` (`GET /v1/payments/{id}`) - Zahlungsinformationen abrufen
  - `Payment Conditions` (`GET /v1/payment-conditions`) - Zahlungsbedingungen
  - `Dunning` (Mahnungen) - create, get, downloadFile
  - `Down Payment Invoice` - get, downloadFile
  - `Files` - upload, download
  - `Recurring Templates` - get, getAll
  - `Print Layouts` - getAll

### Prioritaet Mittel

- [ ] **WF02 (Eingangsrechnungen) mit Posting Categories erweitern** - Wie WF01 echte Kategorien laden und der KI zur Verfuegung stellen
- [ ] **Automatische Paginierung** - Aktuell muss der Nutzer manuell `page` und `size` setzen. Eine Auto-Pagination waere komfortabler
- [ ] **Voucher-Update mit categoryId** - Im Beleg-Update die von der KI vorgeschlagene `categoryId` direkt setzen (aktuell wird nur der Original-Beleg zurueckgeschrieben)
- [ ] **Rate-Limiting / Retry-Logik** - Automatisches Retry bei HTTP 429

### Prioritaet Niedrig

- [ ] **Weitere KI-Workflows** - WF03/WF04 koennten ebenfalls KI-Varianten bekommen
- [ ] **Tests** - Unit-Tests fuer getApiRoute(), buildQueryParams(), buildBody()
- [ ] **Error-Handling verbessern** - Spezifischere Fehlermeldungen bei API-Fehlern
- [ ] **Webhook-Trigger-Node** - Eigene Trigger-Node fuer Lexware Event Subscriptions

---

## API-Dokumentation

- [Lexware Office API Docs](https://developers.lexoffice.io/docs/)
- [Lexware Partner API Docs](https://developers.lexoffice.io/partner/docs/)
- [n8n Community Nodes Dokumentation](https://docs.n8n.io/integrations/community-nodes/)

## Lizenz

[MIT](LICENSE.md)