Finanzmanager/docs/qif-import.md
fds2610-bot 3e55b3ba73 QIF-Import: nur ausgewählte Konten übernehmen
Der Import kannte bisher nur den Zeitraum als Filter – ein Export ließ sich
nur ganz einlesen. Neu ist die Option "konten": im Dialog eine Ankreuzliste,
die erst nach dem Trockenlauf erscheint (vorher ist die Datei ungelesen), auf
der Kommandozeile --konto=NAME und --konten.

Maßgeblich ist nicht allein der !Account-Abschnitt, sondern jedes Konto, das
der Eintrag berührt: das eigene, die Umbuchungsziele aus L und den Splitzeilen
und beim Wertpapiergeschäft das Verrechnungskonto. Eine Umbuchung steht nur
einmal in der Datei – sonst fehlte dem gewählten Konto die Gegenseite.

Die Anfangssalden werden weiter über die ganze Datei gerechnet, aber nur für
die gewählten Konten geschrieben; ein Teilimport überschreibt sonst fremde
Salden. Die Fingerabdrücke entstehen jetzt vorab in einem eigenen Durchgang:
Der laufende Zähler für gleiche Fingerabdrücke hing sonst an der Auswahl, und
dieselbe Buchung wäre beim zweiten Lauf mit anderer Auswahl doppelt angelegt
worden.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 14:50:43 +02:00

187 lines
9.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# QIF-Import
Der Lexware FinanzManager exportiert erweitertes Quicken-QIF. Die `.QDF`-Datei ist
verschlüsselt und damit keine Migrationsquelle – der QIF-Export ist der einzige Weg.
Stand: Klassen, Kategorien, Konten (Phase 1) sowie Buchungen samt Splitzeilen und
Umbuchungen (Phase 2) werden übernommen. Wertpapierbestände folgen in Phase 6,
Buchungsvorlagen in Phase 7.
## Aufbau der Datei
```
!Option:MDY Datumsformat für die gesamte Datei
!Type:Class Klassen
!Type:Cat Kategorien
!Option:AutoSwitch Beginn der Kontenliste
!Account ein Konto je Eintrag
!Clear:AutoSwitch Ende der Kontenliste
!Account / !Type:… ab hier abwechselnd Konto und dessen Buchungen
```
Ein Eintrag endet mit `^`. Jede Zeile beginnt mit einem Feldbuchstaben, der Rest ist der
Wert. Felder dürfen mehrfach auftreten (Splitzeilen, Adresszeilen), deshalb hält
`QifParser` jeden Eintrag als Liste von Paaren und bietet `feld()`, `felder()` und
`hatFeld()`.
**Die Datei ist zustandsbehaftet.** Der Kontobezug einer Buchung steckt allein darin,
welcher `!Account`-Abschnitt zuletzt kam – nach Sektionstypen gruppiert gelesen wäre er
verloren. Der Parser liefert daher eine flache Liste in Dateireihenfolge, jeder Eintrag mit
Sektionstyp und aktuellem Konto.
Jeder Kontoeintrag setzt zugleich das aktuelle Konto. In der Kontenliste folgen mehrere
aufeinander, ohne dass Buchungen dazwischenstehen; das schadet nicht und deckt zugleich die
zweite Schreibweise ab, bei der ein einzelner `!Account` den Kontowechsel bedeutet.
## Felder der Stammdaten
| Sektion | Feld | Bedeutung |
|---|---|---|
| `!Type:Class` | `N` / `D` | Name, Beschreibung |
| `!Type:Cat` | `N` | vollständiger Pfad mit `:` |
| | `D` | Beschreibung |
| | `I` / `E` | Einnahme bzw. Ausgabe (Marker ohne Wert) |
| | `T` | steuerrelevant (Marker ohne Wert) |
| | `R` | Kennziffer des Steuerformulars |
| `!Account` | `N` / `T` | Name, Kontotyp (siehe `docs/stammdaten.md`) |
| | `D` | Beschreibung; `Kontonr. 12345` wird als Kontonummer übernommen |
| | `L` | Kreditlimit im US-Format |
**Kontengruppen fehlen im Export.** Ein `!Account`-Eintrag führt nur die vier Felder oben –
kein Feld nennt die Gruppe, und auch die `QSD`-Datei enthält weder Gruppennamen noch
Zuordnung. Der Import kann sie deshalb nicht anlegen: nach dem Einlesen stehen alle Konten
unter „Nicht gruppiert“. Die Gruppen werden einmalig unter *Verwaltung → Kontengruppen*
angelegt; dort lassen sich die Konten einer Gruppe in einem Zug ankreuzen.
## Fallstricke
1. **Zeichensatz ISO-8859-1, Zeilenenden CRLF.** `QifParser::nachUtf8()` prüft erst, ob
schon UTF-8 vorliegt – eine zweite Konvertierung zerstörte die Umlaute.
2. **Beträge im US-Format**: `-1,241.04`. Das Komma ist Tausendertrenner.
3. **Datum laut Kopfzeile**, meist `MDY` mit zweistelligem Jahr: `D3.18.26` ist der
18.03.2026. Jahreszahlen ab 70 gelten als 19xx.
4. **Kategorie und Klasse in einer Zeile**: `L400 - Leben:B-Essen alleine/Privat`.
5. **Unterkategorien ohne genannte Elternkategorie.** Der Import legt fehlende
Zwischenstufen selbst an (`Kategorie::findenOderAnlegenNachPfad()`).
6. **Konten stehen zweimal in der Datei** – einmal in der Kontenliste, einmal als Kopf
ihrer Buchungen. Der zweite Treffer ist keine Dublette.
7. **Umbuchungen stehen nur einmal in der Datei** – in dem Konto, in dem sie erfasst
wurden. Die Gegenseite ist allein durch `L[Zielkonto]` benannt und wird beim Import
erzeugt; ohne sie fehlte dem Gegenkonto der Betrag. Auch Splitzeilen können ein
Umbuchungsziel tragen (`S[Konto]`). Eine Buchung, die auf ihr eigenes Konto verweist,
ist die Eröffnungsbuchung und bekommt keine Gegenseite.
8. **`C` ist der Abgleichstatus** (`X` = abgeglichen), kein Kennzeichen für
vorausberechnete Buchungen – Wertpapierbuchungen führen es nie. Geplante Buchungen
sortiert man über das Datum aus.
9. **Anfangssalden stehen nicht als eigenes Feld im Export.** Sie ergeben sich aus den
Buchungen vor dem Stichtag, Gegenbuchungen eingerechnet.
## Arbeitsweise der Importeure
Beide sind **idempotent**: Was es schon gibt, wird gezählt und übersprungen, nie doppelt
angelegt. Ein zweiter Lauf derselben Datei ändert nichts. Der echte Lauf liegt jeweils in
einer Transaktion – ein Abbruch mitten in der Kategorieliste hinterließe sonst einen halben
Baum.
`QifStammdatenImport` (Klassen, Kategorien, Konten): Der **Trockenlauf** schreibt gar
nicht, sondern zählt mit einer Menge bereits bekannter Pfade mit; auch die Zwischenstufen,
die der Import selbst anlegen würde, erscheinen in der Vorschau.
`QifBuchungsImport` arbeitet mit einem **Stichtag**:
* Buchungen **vor** dem Stichtag werden nicht gespeichert, sondern je Konto zum
Anfangssaldo aufsummiert – einschließlich der Gegenbuchungen aus Umbuchungen. Die Salden
stimmen dadurch exakt, ohne die ganze Historie mitzuschleppen.
* Buchungen **im Zeitraum** kommen einzeln in die Datenbank, mit Splitzeilen, Klasse,
Abgleichstatus und Adresszeilen.
* Buchungen **nach dem Enddatum** (Vorgabe: heute) sind Vorausberechnungen des
FinanzManagers und bleiben draußen.
* **Wertpapiergeschäfte** schlagen vorerst nur mit ihrem Geldfluss auf dem
Verrechnungskonto zu Buche (`L|[Konto]` und `$`). Ein Betrag auf dem Depot ohne
Gegenwert würde die Summe der Kontostände verfälschen; Bestände folgen in Phase 6.
## Nur einzelne Konten übernehmen
Der Import lässt sich auf einzelne Konten begrenzen (Option `konten`, im Dialog eine
Ankreuzliste, auf der Kommandozeile `--konto=NAME`). Drei Dinge sind dabei entschieden:
1. **Maßgeblich ist jedes berührte Konto, nicht nur der `!Account`-Abschnitt.** Eine
Umbuchung steht nur einmal in der Datei, beim erfassenden Konto. Zählte allein dieser
Abschnitt, fehlte dem gewählten Konto die Gegenseite. Deshalb kommt ein Eintrag mit,
sobald sein Konto, ein Umbuchungsziel aus `L` oder `S` oder – beim Wertpapiergeschäft –
das Verrechnungskonto in der Auswahl steht. Umgekehrt heißt das: Wer nur das Zielkonto
wählt, bekommt die Buchung auch auf dem erfassenden Konto zu sehen; sie gehört dort hin
und lässt sich nicht halbieren.
2. **Anfangssalden werden weiterhin über die ganze Datei gerechnet, aber nur für die
gewählten Konten geschrieben.** Der Saldo eines Kontos steckt auch in Buchungen, die
anderswo erfasst wurden – ohne den vollen Durchlauf wäre er falsch. Und ein Teilimport
darf die Salden der übrigen Konten nicht überschreiben.
3. **Die Fingerabdrücke entstehen vorab in einem eigenen Durchgang.** Der laufende Zähler
für gleiche Fingerabdrücke (siehe unten) hinge sonst an der Auswahl: Dieselbe Buchung
bekäme im Teilimport einen anderen Hash als im Vollimport und käme ein zweites Mal in
die Datenbank. So lassen sich die Konten nacheinander einlesen, ohne dass die
Umbuchungen zwischen ihnen doppelt entstehen.
Die auswählbaren Konten liefert `QifBuchungsImport::kontenDerDatei()` – erst nach dem
Lesen steht fest, was die Datei führt. Enthalten sind auch Konten, die selbst keine
Buchung tragen und nur Umbuchungsziel sind; sonst ließe sich genau deren Gegenseite nicht
übernehmen.
Die Dublettenerkennung nutzt einen Fingerabdruck aus Konto, Datum, Betrag, Nummer und
Text. Weil es echte Doppelbuchungen am selben Tag gibt (zweimal dasselbe im selben Laden),
zählt ein laufender Index gleiche Fingerabdrücke durch – stabil über Läufe hinweg, weil
die Datei immer dieselbe Reihenfolge hat.
## Bedienung
Im Dialog unter *Verwaltung → QIF-Import*: Datei hochladen, Trockenlauf, dann übernehmen.
Hochgeladene Dateien bleiben im Importverzeichnis (`data/import`), damit derselbe Export
erst geprüft und dann übernommen werden kann.
Nach dem Trockenlauf steht unter *Konten* die Ankreuzliste der Konten, die in der Datei
vorkommen – vorher gibt es nichts zu wählen, weil die Datei noch nicht gelesen wurde. Von
dort führen zwei Wege weiter: ein erneuter Trockenlauf mit der Auswahl oder das Übernehmen.
Ohne Häkchen zählt die ganze Datei.
Auf der Kommandozeile:
```bash
docker compose exec -u www-data php php scripts/qif-import.php data/import/Export.QIF
docker compose exec -u www-data php php scripts/qif-import.php --uebernehmen data/import/Export.QIF
```
Ohne `--uebernehmen` läuft nur der Trockenlauf. `--benutzer=NAME` wählt den Mandanten,
sonst gilt der erste aktive Benutzer.
`--stichtag=` und `--bis=` überschreiben die Vorgaben; ohne Angabe gilt der Stichtag aus
den Einstellungen (Reiter *Import/Export*) und als Enddatum der heutige Tag.
`--nur-stammdaten` lässt die Buchungen aus.
```bash
docker compose exec -u www-data php php scripts/qif-import.php --konten data/import/Export.QIF
docker compose exec -u www-data php php scripts/qif-import.php \
--konto="DKB Girokonto" --konto="Tagesgeld" --uebernehmen data/import/Export.QIF
```
`--konten` nennt die Konten der Datei und beendet sich, ohne etwas anzufassen.
`--konto=` grenzt den Import ein und ist mehrfach angebbar; ein Name, den die Datei nicht
führt, bricht ab – sonst liefe der Import durch und legte nichts an.
## Prüfung gegen den echten Export
Der Vollexport (8780 Buchungen, 2017–2026) ergibt mit Stichtag 01.01.2026:
```
Klassen 17 angelegt
Kategorien 105 angelegt
Konten 37 angelegt, 36 vorhanden (die Zweitnennung je Konto)
Buchungen 833 angelegt
vor dem Stichtag verrechnet: 7946 Buchungen auf 35 Konten
nach dem Enddatum verworfen: 1 vorausberechnete Buchung
darunter Wertpapiergeschäfte: 16 (nur Geldfluss)
```
Der Härtetest sind die Salden: Acht Konten, deren Werte aus der Kontenübersicht des
FinanzManagers vorliegen, stimmen danach **auf den Cent** – von „Whg Besigheim“
(210.601,99 €) bis „Auslagen Eltern Wohnungskauf“ (−22.410,94 €).