Finanzmanager/docs/qif-import.md
fds2610-bot 851e8204e9 Phase 3: Budgetplaner und Monatsabschluss
Budgetplaner mit Monatswerten je Kategorie, Monatsabschluss mit Prüflauf,
Snapshot und Buchungssperre. Dazu drei Vorarbeiten in der Oberfläche.

Budgetplaner
- Migration 004: budgets, budget_positionen, monatsabschluesse
- Zeilen sind der Kategoriebaum, Spalten die Monate; die ganze Tabelle wird in
  einem Formular gespeichert, damit mehrere Änderungen zusammen ankommen
- Zwei Ansichten: Verlauf (Jahressumme, abgelaufene Monate zusammengefasst,
  laufender Monat mit Budget/Ist/Saldo, danach Folgemonate) und alle Monate
- Ausgaben ohne Vorzeichen eingeben, es kommt aus der Kategorieart. Saldo ist
  Ist minus Budget: positiv heißt besser als geplant, negativ ist die
  Überschreitung und wird rot ausgewiesen
- Übergeordnete Kategorien summieren ihre Nachfahren und sind nicht beplanbar
- Ein Monat ohne Position ist "nicht geplant" und damit etwas anderes als
  "mit 0,00 geplant"
- Wert auf Folgemonate übertragen, Budget als Kopie ins neue Jahr

Monatsabschluss
- Prüflauf über nicht kategorisierte Buchungen, nicht abgeglichene Umsätze und
  Splitsummen-Differenzen; er hält niemanden auf, sondern zeigt den Stand
- Snapshot der Kontosalden und Kategoriesummen als JSON, bleibt auch nach einer
  Wiedereröffnung stehen
- Abgeschlossene Monate sperren Buchungen in beide Richtungen: weder hinein noch
  heraus verschieben. Abschaltbar über monatsabschluss_sperrt_buchungen

Vorarbeiten
- .knopf.primaer trug einen eigenen margin-top und stand dadurch in jeder
  Knopfleiste tiefer als seine Nachbarn - sichtbar an "Abbrechen" neben
  "Konto anlegen". Der Abstand liegt jetzt allein bei der Knopfleiste
- Kontengruppen werden wie Konten und Kategorien auf einer eigenen
  Formularseite gepflegt statt inline; dort hängt auch die Zuordnung der
  Konten (ankreuzen statt jedes Konto einzeln zu öffnen)
- Der QIF-Export enthält keine Kontengruppen: kein Feld im !Account-Eintrag,
  nichts in der QSD-Datei. Import-Oberfläche und Doku sagen das jetzt
- Die Baum-Einrückung lief über ein style-Attribut und wurde von der
  Content-Security-Policy verworfen; sie steht jetzt als data-ebene im Markup.
  Aus demselben Grund blendete app.js Erfolgsmeldungen nie aus

Tests: 11_BudgetTest, 12_MonatsabschlussTest (76 Prüfungen), E2E planung.spec.js.
Einzelheiten in docs/planung.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 07:31:25 +02:00

7.1 KiB
Raw Blame History

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.

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.

Auf der Kommandozeile:

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.

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 €).