768 lines
20 KiB
Markdown
768 lines
20 KiB
Markdown
# Firefly Import Preprocessor — Dokumentation
|
||
|
||
**Version:** 1.0.0
|
||
**Datum:** 03. Mai 2026
|
||
**Status:** Production Ready
|
||
|
||
🌐 [English](README.md)
|
||
|
||
---
|
||
|
||
## 📋 Inhaltsverzeichnis
|
||
|
||
1. [Überblick](#überblick)
|
||
2. [Installation & Setup](#installation--setup)
|
||
3. [Schnellstart](#schnellstart)
|
||
4. [Konfiguration](#konfiguration)
|
||
5. [Transformationstypen](#transformationstypen)
|
||
6. [CLI-Referenz](#cli-referenz)
|
||
7. [Debug-Modus](#debug-modus)
|
||
8. [Firefly III Integration](#firefly-iii-integration)
|
||
9. [Architektur](#architektur)
|
||
10. [Fehlerbehandlung](#fehlerbehandlung)
|
||
|
||
---
|
||
|
||
## Überblick
|
||
|
||
Der **Firefly Import Preprocessor** ist ein produktionsreifer PHP-Preprocessor für Banken-CSV-Exportdateien. Er transformiert Bankdaten in ein standardisiertes Format und kann sie optional in Firefly III importieren.
|
||
|
||
### Kernfeatures
|
||
|
||
✅ **Vollständige CSV-Transformation** mit komplexen Pipelines
|
||
✅ **Metadaten-Extraktion** mit Regex (IBAN, Währung, Kontoname)
|
||
✅ **14 Transformationstypen** für flexible Datenverarbeitung
|
||
✅ **Firefly III Integration** — CLI, Docker und HTTP-Upload
|
||
✅ **Debug-Modus** für Transparenz bei Verarbeitung
|
||
✅ **Production Ready** mit vollständiger Fehlerbehandlung
|
||
✅ **Zero Dependencies** für Core-Funktionalität
|
||
|
||
### Workflow
|
||
|
||
```text
|
||
Input CSV
|
||
↓
|
||
Metadaten extrahieren (Regex)
|
||
↓
|
||
Datenzeilen transformieren (Pipeline)
|
||
↓
|
||
Output CSV schreiben
|
||
↓
|
||
[Optional] In Firefly III importieren
|
||
```
|
||
|
||
---
|
||
|
||
## Installation & Setup
|
||
|
||
### Voraussetzungen
|
||
|
||
- PHP 8.1+
|
||
- Composer (empfohlen)
|
||
- [Optional] Docker für Firefly III Integration
|
||
|
||
### Installation
|
||
|
||
```bash
|
||
# 1. Repository clonen/kopieren
|
||
cd ff-imp-preprocessor
|
||
|
||
# 2. Abhängigkeiten installieren (optional)
|
||
composer install
|
||
|
||
# 3. Konfiguration erstellen
|
||
cp config/config.example.json config/config.json
|
||
# Bearbeite config/config.json mit deinen Einstellungen
|
||
|
||
# 4. Directories erstellen
|
||
mkdir -p config/import/{source,output,archive,error}
|
||
chmod 755 config/import/{source,output,archive,error}
|
||
|
||
# 5. Test durchführen
|
||
php bin/transformer.php validate config/config.json input.csv
|
||
```
|
||
|
||
---
|
||
|
||
## Schnellstart
|
||
|
||
### 1. Konfiguration anpassen
|
||
|
||
Bearbeite `config/config.json` und stelle sicher, dass die Extraction-Rules zu deinem CSV-Format passen:
|
||
|
||
```json
|
||
{
|
||
"metadata": {
|
||
"extractionRules": [
|
||
{
|
||
"name": "account_iban",
|
||
"lineNumber": 2,
|
||
"regex": "IBAN:\\s*([A-Z0-9 ]+)",
|
||
"captureGroup": 1
|
||
}
|
||
]
|
||
},
|
||
"csvStructure": {
|
||
"headerLine": 5,
|
||
"delimiter": ";",
|
||
"encoding": "UTF-8"
|
||
}
|
||
}
|
||
```
|
||
|
||
### 2. CSV validieren
|
||
|
||
```bash
|
||
php bin/transformer.php validate config/config.json input.csv
|
||
```
|
||
|
||
### 3. Transformation durchführen
|
||
|
||
```bash
|
||
php bin/transformer.php transform input.csv config/config.json
|
||
|
||
# Mit Debug-Modus für Fehlersuche
|
||
php bin/transformer.php transform input.csv config/config.json --debug
|
||
```
|
||
|
||
### 4. Output prüfen
|
||
|
||
```bash
|
||
php bin/transformer.php test input.csv config/config.json --debug
|
||
# Zeigt max. 10 transformierte Zeilen und Debug-Logs
|
||
```
|
||
|
||
---
|
||
|
||
## Konfiguration
|
||
|
||
### config.json Struktur
|
||
|
||
#### `metadata` - Metadaten-Extraktion
|
||
|
||
```json
|
||
{
|
||
"metadata": {
|
||
"extractionRules": [
|
||
{
|
||
"name": "account_iban",
|
||
"lineNumber": 2,
|
||
"regex": "IBAN:\\s*([A-Z0-9 ]+)",
|
||
"captureGroup": 1
|
||
},
|
||
{
|
||
"name": "currency_code",
|
||
"lineNumber": 3,
|
||
"regex": "Währung:\\s*([A-Z]{3})",
|
||
"captureGroup": 1
|
||
}
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
| Feld | Typ | Beschreibung |
|
||
| ------ | ----- | ------------- |
|
||
| `name` | string | Name der Metadaten-Variable (verwendet in constantvalue) |
|
||
| `lineNumber` | int | Zeilennummer in CSV (1-basiert, menschenlesbar) |
|
||
| `regex` | string | Regex-Pattern zur Extraktion (ohne Delimiter) |
|
||
| `captureGroup` | int | Nummer der Klammer-Gruppe (0=komplett, 1=erste Klammer, etc.) |
|
||
|
||
**Beispiel Regex:**
|
||
|
||
- Pattern: `IBAN:\s*([A-Z0-9 ]+)`
|
||
- Input: `IBAN: CH93 0077 2020 6262 5252 7`
|
||
- Capture Group 1: `CH93 0077 2020 6262 5252 7`
|
||
|
||
#### `csvStructure` - CSV-Format
|
||
|
||
```json
|
||
{
|
||
"csvStructure": {
|
||
"headerLine": 5,
|
||
"delimiter": ";",
|
||
"encoding": "UTF-8",
|
||
"hasBom": false
|
||
}
|
||
}
|
||
```
|
||
|
||
| Feld | Typ | Default | Beschreibung |
|
||
| ------ | ----- | --------- | ------------- |
|
||
| `headerLine` | int | 5 | Zeilennummer der Header (1-basiert) |
|
||
| `delimiter` | string | `;` | CSV-Delimiter |
|
||
| `encoding` | string | `UTF-8` | Zeichenkodierung (UTF-8, ISO-8859-1, CP1252) |
|
||
| `hasBom` | bool | false | Hat die Datei BOM (Byte Order Mark)? |
|
||
|
||
#### `columnTransformations` - Spalten-Transformationen
|
||
|
||
```json
|
||
{
|
||
"columnTransformations": [
|
||
{
|
||
"sourceColumn": "Buchungsdatum",
|
||
"transformations": [
|
||
{
|
||
"type": "dateformat",
|
||
"fromFormat": "d.m.Y",
|
||
"toFormat": "Y-m-d"
|
||
}
|
||
],
|
||
"outputColumn": "date",
|
||
"outputAction": "overwrite"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
**outputAction:**
|
||
|
||
| Wert | Verhalten |
|
||
|---|---|
|
||
| `overwrite` | Ziel-Spalte mit dem Transformations-Ergebnis überschreiben (Standard) |
|
||
| `create` | Ergebnis in eine neue Ausgabe-Spalte schreiben |
|
||
| `append` | Ergebnis ans Ende des bestehenden Spalten-Werts anhängen. Mit `"appendDelimiter": " "` (beliebige Zeichenkette) wird ein Trennzeichen zwischen bestehendem und neuem Wert eingefügt — der Trennzeichen entfällt, wenn die Ziel-Spalte noch leer ist |
|
||
| `append-if-not-empty` | Wie `append` (inkl. `appendDelimiter`), aber überspringt die Operation vollständig, wenn das Transformations-Ergebnis leer ist — geeignet für optionale Werte wie Tags oder Notiz-Zeilen |
|
||
| `append-line` | Wie `append`, aber als Trennzeichen wird immer ein Zeilenumbruch `\n` verwendet; kein führender Zeilenumbruch wenn die Ziel-Spalte leer ist |
|
||
| `overwrite-if-empty` | Ergebnis nur schreiben, wenn die Ziel-Spalte aktuell leer ist |
|
||
| `overwrite-if-not-empty` | Ergebnis nur schreiben, wenn das Transformations-Ergebnis nicht leer ist |
|
||
|
||
#### `directories` - Dateisystem
|
||
|
||
```json
|
||
{
|
||
"directories": {
|
||
"source": "/opt/ff-imp-preprocessor/import/source",
|
||
"output": "/opt/ff-imp-preprocessor/import/output",
|
||
"archive": "/opt/ff-imp-preprocessor/import/archive",
|
||
"error": "/opt/ff-imp-preprocessor/import/error"
|
||
}
|
||
}
|
||
```
|
||
|
||
| Feld | Beschreibung |
|
||
| ------ | ------------- |
|
||
| `source` | Eingabe-Verzeichnis |
|
||
| `output` | Ausgabe-Verzeichnis |
|
||
| `archive` | Archiv für verarbeitete Dateien |
|
||
| `error` | Error-Verzeichnis für ungültige Dateien |
|
||
|
||
#### `fireflyImport` - Firefly III Integration
|
||
|
||
Optional. Mit dem Flag `--do-import` beim `transform`-Kommando (oder via `auto-import`) wird der Firefly III Data Importer nach dem Schreiben der Output-CSV aufgerufen.
|
||
|
||
Details und vollständige Beispiele: [Firefly III Integration](#firefly-iii-integration).
|
||
|
||
---
|
||
|
||
## Transformationstypen
|
||
|
||
Es gibt **14 unterstützte Transformationstypen**, die als Pipeline kombiniert werden können:
|
||
|
||
### 1. **trim** - Leerzeichen entfernen
|
||
|
||
```json
|
||
{ "type": "trim" }
|
||
```
|
||
|
||
- Input: ` Coop Pronto ` → Output: `Coop Pronto`
|
||
|
||
---
|
||
|
||
### 2. **lowercase** - Zu Kleinbuchstaben
|
||
|
||
```json
|
||
{ "type": "lowercase" }
|
||
```
|
||
|
||
- Input: `COOP PRONTO CHUR` → Output: `coop pronto chur`
|
||
|
||
---
|
||
|
||
### 3. **uppercase** - Zu Grossbuchstaben
|
||
|
||
```json
|
||
{ "type": "uppercase" }
|
||
```
|
||
|
||
- Input: `Coop Pronto Chur` → Output: `COOP PRONTO CHUR`
|
||
|
||
---
|
||
|
||
### 4. **ucwordsfirst** - Grossschreibung nach Trennzeichen
|
||
|
||
```json
|
||
{ "type": "ucwordsfirst" }
|
||
```
|
||
|
||
- `COOP PRONTO CHUR` → `Coop Pronto Chur`
|
||
- `migros-rail city` → `Migros-Rail City`
|
||
- `O'NEILL STORE` → `O'Neill Store`
|
||
|
||
Trennzeichen: Leerzeichen, Bindestrich, Apostroph, Slash, Punkt, Komma, Semikolon, Doppelpunkt, Klammern.
|
||
|
||
> **Guard:** Wenn der Eingabe-String bereits sowohl Groß- als auch Kleinbuchstaben enthält (gemischte Groß-/Kleinschreibung), wird er unverändert zurückgegeben. So werden bereits korrekt formatierte Strings wie `"Coop pronto chur"` nicht verändert. Vollständig groß- oder kleingeschriebene Strings werden weiterhin verarbeitet.
|
||
|
||
---
|
||
|
||
### 5. **replace** - String-Replacement
|
||
|
||
```json
|
||
{ "type": "replace", "search": " ", "replace": " " }
|
||
```
|
||
|
||
- Input: `Coop Pronto` → Output: `Coop Pronto`
|
||
|
||
---
|
||
|
||
### 6. **split** - Spalte teilen
|
||
|
||
```json
|
||
{ "type": "split", "delimiter": ";", "part": 0 }
|
||
```
|
||
|
||
- Input: `Coop Pronto Chur;7007 Chur` → Output: `Coop Pronto Chur`
|
||
|
||
---
|
||
|
||
### 7. **regex** - Regex-Ersetzung
|
||
|
||
```json
|
||
{ "type": "regex", "pattern": "^(.*?);.*$", "replace": "$1" }
|
||
```
|
||
|
||
- Kein Match → Originalwert bleibt **unverändert** (pipeline-sicher)
|
||
|
||
---
|
||
|
||
### 8. **regexextract** - Regex-Extraktion
|
||
|
||
```json
|
||
{ "type": "regexextract", "pattern": "(\\d{4,} [^;]+)" }
|
||
```
|
||
|
||
- Kein Match → leerer String (**nicht** pipeline-sicher)
|
||
|
||
---
|
||
|
||
### 9. **dateformat** - Datum-Umformat
|
||
|
||
```json
|
||
{ "type": "dateformat", "fromFormat": "d.m.Y", "toFormat": "Y-m-d" }
|
||
```
|
||
|
||
- Input: `10.12.2025` → Output: `2025-12-10`
|
||
|
||
---
|
||
|
||
### 10. **truncate** - String kürzen
|
||
|
||
```json
|
||
{ "type": "truncate", "maxLength": 100 }
|
||
```
|
||
|
||
---
|
||
|
||
### 11. **constantvalue** - Konstanten-Wert aus Metadaten
|
||
|
||
```json
|
||
{
|
||
"sourceColumn": "_constant_",
|
||
"transformations": [{ "type": "constantvalue", "metadataKey": "account_iban" }],
|
||
"outputColumn": "account_iban",
|
||
"outputAction": "create"
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 12. **map** - Spalte kopieren
|
||
|
||
```json
|
||
{ "type": "map" }
|
||
```
|
||
|
||
---
|
||
|
||
### 13. **pipeline** - Verschachtelte Pipeline
|
||
|
||
```json
|
||
{
|
||
"type": "pipeline",
|
||
"steps": [
|
||
{ "type": "trim" },
|
||
{ "type": "lowercase" },
|
||
{ "type": "ucwordsfirst" }
|
||
]
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 14. **timeperiod** - Zeit einer Tagesperiode zuordnen
|
||
|
||
Parst eine Zeitangabe und gibt das Label des passenden Perioden-Bereichs zurück.
|
||
Unterstützt mitternachtübergreifende Bereiche (z. B. 22:00–03:59).
|
||
Gibt `default` (standardmäßig leer) zurück, wenn keine Periode passt oder die Eingabe ungültig ist.
|
||
|
||
```json
|
||
{
|
||
"type": "timeperiod",
|
||
"timeFormat": "H:i:s",
|
||
"periods": [
|
||
{ "from": "04:00:00", "to": "08:59:59", "label": "Morgen" },
|
||
{ "from": "09:00:00", "to": "10:59:59", "label": "Vormittag" },
|
||
{ "from": "11:00:00", "to": "13:59:59", "label": "Mittag" },
|
||
{ "from": "14:00:00", "to": "17:59:59", "label": "Nachmittag" },
|
||
{ "from": "18:00:00", "to": "21:59:59", "label": "Abend" },
|
||
{ "from": "22:00:00", "to": "03:59:59", "label": "Nacht" }
|
||
],
|
||
"default": ""
|
||
}
|
||
```
|
||
|
||
- `"09:30:00"` → `"Vormittag"`
|
||
- `"23:00:00"` → `"Nacht"` (mitternachtübergreifender Bereich)
|
||
- `"02:00:00"` → `"Nacht"` (mitternachtübergreifender Bereich)
|
||
- `""` oder nicht parsbare Eingabe → `""`
|
||
|
||
`timeFormat` folgt der PHP-Syntax `DateTime::createFromFormat` (Standard: `H:i:s`).
|
||
|
||
---
|
||
|
||
### Zeilen-Filterung — `skipIf`
|
||
|
||
Zeilen können durch einen Top-Level-Schlüssel `skipIf` in der Konfiguration ausgeschlossen werden.
|
||
Der Wert ist ein Filter-Knoten — entweder eine einzelne Bedingung oder eine verschachtelte `and`/`or`-Gruppe.
|
||
|
||
**Einzelne Bedingung:**
|
||
|
||
```json
|
||
"skipIf": { "column": "Buchungstext", "operator": "equals", "value": "Saldovortrag" }
|
||
```
|
||
|
||
**AND-Gruppe:**
|
||
|
||
```json
|
||
"skipIf": {
|
||
"and": [
|
||
{ "column": "Beschreibung1", "operator": "empty" },
|
||
{ "column": "Beschreibung2", "operator": "empty" }
|
||
]
|
||
}
|
||
```
|
||
|
||
**Verschachtelte AND/OR-Gruppen:**
|
||
|
||
```json
|
||
"skipIf": {
|
||
"or": [
|
||
{ "column": "Amount", "operator": "gt", "value": "10000" },
|
||
{
|
||
"and": [
|
||
{ "column": "Type", "operator": "equals", "value": "Saldo" },
|
||
{ "column": "Notes", "operator": "empty" }
|
||
]
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
**Unterstützte Operatoren:**
|
||
|
||
| Operator | Passt wenn… |
|
||
|---|---|
|
||
| `empty` | Spaltenwert ist leer |
|
||
| `not-empty` | Spaltenwert ist nicht leer |
|
||
| `equals` | Spaltenwert gleich `"value"` |
|
||
| `not-equals` | Spaltenwert ungleich `"value"` |
|
||
| `contains` | Spaltenwert enthält `"value"` |
|
||
| `not-contains` | Spaltenwert enthält `"value"` nicht |
|
||
| `matches` | Spaltenwert entspricht Regex `"pattern"` |
|
||
| `not-matches` | Spaltenwert entspricht Regex `"pattern"` nicht |
|
||
| `gt` | `(float) Spalte > (float) value` |
|
||
| `gte` | `(float) Spalte >= (float) value` |
|
||
| `lt` | `(float) Spalte < (float) value` |
|
||
| `lte` | `(float) Spalte <= (float) value` |
|
||
|
||
---
|
||
|
||
### Pipeline-Beispiel
|
||
|
||
```json
|
||
{
|
||
"sourceColumn": "Buchungstext",
|
||
"transformations": [
|
||
{ "type": "trim" },
|
||
{ "type": "replace", "search": " ", "replace": " " },
|
||
{ "type": "lowercase" },
|
||
{ "type": "ucwordsfirst" }
|
||
],
|
||
"outputColumn": "description",
|
||
"outputAction": "overwrite"
|
||
}
|
||
```
|
||
|
||
**Verarbeitung:**
|
||
|
||
1. `" COOP PRONTO "` → trim → `"COOP PRONTO"`
|
||
2. `"COOP PRONTO"` → replace → `"COOP PRONTO"`
|
||
3. `"COOP PRONTO"` → lowercase → `"coop pronto"`
|
||
4. `"coop pronto"` → ucwordsfirst → `"Coop Pronto"`
|
||
|
||
---
|
||
|
||
## CLI-Referenz
|
||
|
||
```bash
|
||
php bin/transformer.php <command> [input] [config] [options]
|
||
```
|
||
|
||
### Kommandos
|
||
|
||
| Kommando | Beschreibung |
|
||
| -------- | ------------- |
|
||
| `test` | Test-Run (max. 10 Zeilen) |
|
||
| `transform` | Vollständige Transformation |
|
||
| `validate` | Konfiguration validieren |
|
||
| `auto-import` | Verzeichnis-Überwachung |
|
||
| `help` | Hilfe anzeigen |
|
||
|
||
### Optionen
|
||
|
||
| Option | Beschreibung |
|
||
| -------- | ------------- |
|
||
| `--debug`, `-d` | Debug-Modus aktivieren |
|
||
| `--rows=N` | Max. N Zeilen (test-Kommando) |
|
||
| `--output=FILE`, `-o` | Output-Pfad |
|
||
| `--do-import` | Nach der Transformation in Firefly III importieren (`transform`) |
|
||
| `--strict` | Strikte Validierung |
|
||
| `--watch` | Kontinuierliche Überwachung |
|
||
| `--interval=SEC` | Prüfintervall in Sekunden |
|
||
| `--dry-run` | Simulationsmodus |
|
||
|
||
---
|
||
|
||
## Debug-Modus
|
||
|
||
```bash
|
||
php bin/transformer.php test input.csv config/config.json --debug
|
||
```
|
||
|
||
Der Debug-Modus protokolliert Ereignisse in folgenden Kategorien:
|
||
|
||
| Kategorie | Wann |
|
||
| ----------- | ------ |
|
||
| `transformer` | Anfang/Ende Transformation |
|
||
| `csv_reader` | Beim CSV lesen |
|
||
| `metadata` | Bei Metadaten-Extraktion |
|
||
| `metadata_warning` | Bei Problemen |
|
||
| `transformation` | Bei jeder Transformation |
|
||
| `csv_writer` | Beim CSV schreiben |
|
||
|
||
---
|
||
|
||
## Firefly III Integration
|
||
|
||
Drei Betriebsmodi decken alle typischen Deployment-Szenarien ab.
|
||
|
||
**`chunkSize`** (optional, Standard: 0 = deaktiviert): Die Output-CSV wird vor dem Import in Blöcke von maximal N Datenzeilen aufgeteilt. Jeder Block wird als separate Anfrage gesendet. Das verhindert serverseitige Timeouts bei grossen Dateien (Faustregel: ~3–4 s/Transaktion im HTTP-Modus). Der `timeout`-Wert gilt pro Block, nicht für den gesamten Lauf.
|
||
|
||
**`chunkRetries`** (optional, Standard: 0 = kein Retry): Anzahl zusätzlicher Importversuche pro Block nach dem ersten. Bei einem Fehler wiederholt der Importer den Upload bis zu dieser Anzahl, bevor er abbricht. Nur wirksam wenn `chunkSize > 0`.
|
||
|
||
**`chunkRetryDelay`** (optional, Standard: 0 = keine Pause): Pause in Sekunden vor jedem Block-Request ab dem zweiten Block sowie zwischen Wiederholungsversuchen desselben fehlgeschlagenen Blocks. Ein einziger Wert für Cooldown und Retry-Back-off. Nur wirksam wenn `chunkSize > 0`.
|
||
|
||
**`connectionTimeout`** (optional, Standard: 10): Maximale Wartezeit in Sekunden für den Aufbau der TCP-Verbindung zum Importer-Server. Unabhängig von `timeout` (der die gesamte Übertragungsdauer begrenzt). Nur im Modus `http`.
|
||
|
||
### Modus `cli`
|
||
|
||
Transformer und Importer auf demselben Server.
|
||
|
||
```json
|
||
"fireflyImport": {
|
||
"mode": "cli",
|
||
"jsonConfig": "/opt/firefly-data-importer/storage/configurations/ubs-import.json",
|
||
"importerCommand": "php /opt/firefly-data-importer/artisan importer:import",
|
||
"chunkSize": 50,
|
||
"chunkRetries": 3,
|
||
"chunkRetryDelay": 10,
|
||
"timeout": 300,
|
||
"environment": {
|
||
"FIREFLY_III_URL": "https://localhost",
|
||
"FIREFLY_III_ACCESS_TOKEN": "your-token-here"
|
||
}
|
||
}
|
||
```
|
||
|
||
### Modus `docker`
|
||
|
||
Transformer lokal, Importer in Docker. Das Ausgabeverzeichnis muss als Volume eingebunden sein. `jsonConfig` ist der Pfad **innerhalb des Containers**.
|
||
|
||
```json
|
||
"fireflyImport": {
|
||
"mode": "docker",
|
||
"jsonConfig": "/import/configs/ubs-import.json",
|
||
"importerCommand": "docker exec firefly-importer php artisan importer:import",
|
||
"chunkSize": 50,
|
||
"chunkRetries": 3,
|
||
"chunkRetryDelay": 10,
|
||
"timeout": 300
|
||
}
|
||
```
|
||
|
||
### Modus `http`
|
||
|
||
Transformer lokal, Importer über HTTP(S) erreichbar. Benötigt `ext-curl`.
|
||
|
||
**Voraussetzungen auf dem Importer-Server:**
|
||
|
||
```text
|
||
CAN_POST_FILES=true
|
||
AUTO_IMPORT_SECRET=<secret> # mindestens 16 Zeichen
|
||
```
|
||
|
||
```json
|
||
"fireflyImport": {
|
||
"mode": "http",
|
||
"importerUrl": "https://importer.your-server.com",
|
||
"personalSecret": "your-auto-import-secret-min-16-chars",
|
||
"accessToken": "your-firefly-iii-personal-access-token",
|
||
"jsonConfig": "config/ubs-import.json",
|
||
"chunkSize": 50,
|
||
"chunkRetries": 3,
|
||
"chunkRetryDelay": 10,
|
||
"connectionTimeout": 10,
|
||
"timeout": 300
|
||
}
|
||
```
|
||
|
||
Die Anfrage geht an `POST {importerUrl}/autoupload?secret={personalSecret}` mit CSV und JSON-Config als Multipart-Felder. `accessToken` wird als `Authorization: Bearer` gesendet. Falls `FIREFLY_III_ACCESS_TOKEN` bereits in der Importer-Umgebung gesetzt ist, kann `accessToken` weggelassen werden.
|
||
|
||
---
|
||
|
||
### Serverseitige Konfiguration
|
||
|
||
Bei grossen Importen liegt der Engpass meist auf dem Firefly III Data Importer-Server, nicht im Transformer. Die folgenden Einstellungen gehören in die Umgebung des Importers (`.env` oder `docker-compose.yml`):
|
||
|
||
| Einstellung | Empfohlener Wert | Hinweis |
|
||
|---|---|---|
|
||
| `PHP_MEMORY_LIMIT` | `512M` – `2048M` | Docker-Umgebungsvariable. Erhöhen, wenn PHP mit „Allowed memory size exhausted" abbricht. |
|
||
| `CONNECTION_TIMEOUT` | `60` | Sekunden für den TCP-Verbindungsaufbau zu Firefly III. Standard ~31 s (π × 10). |
|
||
| `IGNORE_DUPLICATE_ERRORS` | `true` | Doppelte Transaktionswarnungen bei Wiederholungsimporten unterdrücken. |
|
||
|
||
**nginx Reverse Proxy** (falls vorhanden):
|
||
```nginx
|
||
proxy_read_timeout 600s; # muss länger sein als der längste Einzelblock-Import
|
||
client_max_body_size 64M; # muss die grösste Chunk-CSV abdecken
|
||
```
|
||
|
||
**Docker Compose** Beispiel:
|
||
```yaml
|
||
services:
|
||
firefly-importer:
|
||
environment:
|
||
- PHP_MEMORY_LIMIT=1024M
|
||
- CONNECTION_TIMEOUT=60
|
||
- IGNORE_DUPLICATE_ERRORS=true
|
||
```
|
||
|
||
---
|
||
|
||
### Verwendung
|
||
|
||
```bash
|
||
# Nur transformieren (kein Import)
|
||
php bin/transformer.php transform input.csv config/config.json
|
||
|
||
# Transformieren und in Firefly III importieren
|
||
php bin/transformer.php transform input.csv config/config.json --do-import
|
||
|
||
# Watch-Modus: automatisch transformieren und importieren bei neuer CSV
|
||
php bin/transformer.php auto-import config/config.json --watch
|
||
```
|
||
|
||
---
|
||
|
||
## Architektur
|
||
|
||
```text
|
||
bin/transformer.php (CLI Entry Point)
|
||
↓
|
||
TransformerEngine (Orchestrierung)
|
||
├─ ConfigurationLoader (Config laden/validieren)
|
||
├─ CsvReader (CSV einlesen)
|
||
├─ MetadataExtractor (Metadaten mit Regex)
|
||
├─ ColumnTransformer (Transformationen anwenden)
|
||
├─ CsvWriter (CSV schreiben)
|
||
├─ FireflyImporter (Firefly III Integration)
|
||
└─ DebugLogger (Debug-Protokolle)
|
||
```
|
||
|
||
| Klasse | Verantwortung |
|
||
| -------- | --------------- |
|
||
| `TransformerEngine` | Orchestriert gesamten Workflow |
|
||
| `ConfigurationLoader` | Lädt und validiert JSON-Konfiguration |
|
||
| `CsvReader` | Liest CSV mit Metadaten |
|
||
| `MetadataExtractor` | Extrahiert Metadaten mit Regex |
|
||
| `ColumnTransformer` | Transformiert Spalten (Pipeline) |
|
||
| `CsvWriter` | Schreibt CSV |
|
||
| `FireflyImporter` | Importiert in Firefly III |
|
||
| `DebugLogger` | Statischer Logger für Debug |
|
||
|
||
---
|
||
|
||
## Fehlerbehandlung
|
||
|
||
### Häufige Fehler
|
||
|
||
#### "Input file not found"
|
||
|
||
```bash
|
||
# Prüfe Dateipfad
|
||
ls -la input.csv
|
||
|
||
# Nutze absoluten Pfad wenn relativ nicht funktioniert
|
||
php bin/transformer.php transform /absolute/path/input.csv config.json
|
||
```
|
||
|
||
#### "Missing metadata: account_iban"
|
||
|
||
```bash
|
||
# Prüfe erste Zeilen des CSV
|
||
head -5 input.csv
|
||
|
||
# Überprüfe lineNumber und regex in config.json
|
||
php bin/transformer.php validate config.json input.csv --debug
|
||
```
|
||
|
||
#### "Invalid JSON"
|
||
|
||
```bash
|
||
php -r "json_decode(file_get_contents('config/config.json'), true) or die('JSON invalid');"
|
||
```
|
||
|
||
#### "Configuration: 'csvStructure.headerLine' required"
|
||
|
||
```bash
|
||
diff config/config.json config/config.example.json
|
||
```
|
||
|
||
---
|
||
|
||
## Version & Änderungen
|
||
|
||
**v1.0.0 (03. Mai 2026)**
|
||
|
||
- ✅ Initial Release
|
||
- ✅ 14 Transformationstypen
|
||
- ✅ Metadaten-Extraktion mit Regex
|
||
- ✅ Debug-Modus
|
||
- ✅ Firefly III Integration (cli / docker / http)
|
||
- ✅ Vollständige Dokumentation
|
||
|
||
---
|
||
|
||
**Lizenz:** GPL-3.0
|
||
**Author:** PHP CSV Transformer Project
|
||
**Repository:** [git.andare.ch/david.reindl/ff-imp-preprocessor](https://git.andare.ch/david.reindl/ff-imp-preprocessor)
|