Finanzmanager/docs/buchungen.md
fds2610-bot 1dbc848cd1 Phase 2: Buchungen, Kontoblatt und QIF-Buchungsimport
Der Plan ging davon aus, Umbuchungen stünden zweimal im Export und müssten
zusammengeführt werden. Der Vollexport zeigt das Gegenteil: Eine Umbuchung
steht einmal, in dem Konto, in dem sie erfasst wurde; die Gegenseite ist nur
durch L[Zielkonto] benannt und muss beim Import erzeugt werden. Von 965
Umbuchungen hat keine ein zweites Vorkommen. Mit erzeugter Gegenseite stimmen
acht gegen den FinanzManager geprüfte Kontosalden auf den Cent.

Datenmodell: Migration 003 mit buchungen, buchung_splits und buchungsadressen.
Eine Umbuchung ist ein Datensatz mit gegenkonto_id, kein gespiegeltes Paar -
zwei Zeilen laufen früher oder später auseinander. Das Kontoblatt des
Gegenkontos zeigt sie mit umgekehrtem Vorzeichen. Auch einzelne Splitzeilen
dürfen ein Gegenkonto tragen. Konten führen zusätzlich in_summe, die
Sigma-Spalte der Kontenübersicht, und saldo_online für Phase 5.

Oberfläche: Kontoblatt mit laufendem Saldo, Filter auf Zeitraum, Kategorie,
Klasse und Abgleich, Volltextsuche und Seitenblättern. Buchungsmaske für
Einnahme, Ausgabe und Umbuchung in beide Richtungen, mit Splitzeilen und
Restbetragsanzeige.

Import: QifBuchungsImport arbeitet mit Stichtag. Alles davor wird je Konto zum
Anfangssaldo aufsummiert, Gegenbuchungen eingerechnet; alles danach bis zum
Importtag kommt einzeln in die Datenbank. Spätere Buchungen sind
Vorausberechnungen des FinanzManagers und bleiben draußen. Wertpapiergeschäfte
schlagen vorerst nur mit ihrem Geldfluss auf dem Verrechnungskonto zu Buche.
Am Vollexport geprüft: 833 Buchungen ab 01.01.2026, 7946 in die Anfangssalden
von 35 Konten verrechnet.

Behoben: Die confirm()-Rückfragen vor dem Löschen waren wirkungslos.
public/static/app.js brachte den Mechanismus mit, war aber nie im Layout
eingebunden; die ersatzweise gesetzten onsubmit-Attribute verstoßen gegen die
eigene Content-Security-Policy und wurden vom Browser verworfen - gelöscht
wurde ohne Rückfrage. Ein E2E-Test besteht jetzt auf dem Dialog.

Tests: 307 PHP-Prüfungen und 21 Playwright-Tests, alle grün.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 16:40:31 +02:00

3 KiB
Raw Blame History

Buchungen

Kontoblatt, Buchungsmaske und die Saldenrechnung.

Umbuchungen: ein Datensatz, zwei Sichten

Eine Umbuchung wird einmal gespeichert – als Buchung auf dem Ausgangskonto mit gegenkonto_id. Das Kontoblatt des Gegenkontos zeigt sie mit umgekehrtem Vorzeichen.

Der Grund ist nicht nur Sparsamkeit: Zwei gespiegelte Datensätze laufen früher oder später auseinander (einer wird geändert, der andere nicht). Und der QIF-Export macht es genauso – dort steht jede Umbuchung genau einmal, die Gegenseite nur als L[Zielkonto].

Buchung::kontoblatt() vereinigt deshalb drei Mengen:

  1. Buchungen mit konto_id = X (eigene Sicht)
  2. Buchungen mit gegenkonto_id = X ohne Splitzeilen, Betrag negiert
  3. Splitzeilen mit gegenkonto_id = X, Betrag negiert

Zeilen aus 2 und 3 tragen gegensicht = true. Sie sind nicht bearbeitbar – das geht auf dem Ursprungskonto –, damit eine Umbuchung nicht aus zwei Richtungen widersprüchlich geändert wird.

Dieselbe Dreiteilung gilt für die Salden (Buchung::saldenJeKonto()), die alle Konten mit sechs Abfragen auf einmal berechnet statt je Konto einzeln.

Splitbuchungen

Eine Buchung ist entweder einfach zugeordnet oder gesplittet. Sobald Splitzeilen vorliegen, räumt das Model kategorie_id und gegenkonto_id der Buchung; die Zuordnung steckt dann ausschließlich in den Zeilen.

Die Summe der Zeilen muss dem Buchungsbetrag entsprechen – geprüft im Model (Toleranz ein halber Cent, für Rundungen aus der Betragsumrechnung) und in der Maske als Restbetragsanzeige. Eine Splitzeile darf ein Gegenkonto tragen; dann erscheint auf jenem Konto nur der Betrag dieser Zeile.

Salden

Spalte Bedeutung
Online-Saldo Stand des letzten Bankabrufs (konten.saldo_online, ab Phase 5)
Aktueller Saldo Anfangssaldo plus alle Buchungen bis heute
Erwarteter Saldo zusätzlich die künftig datierten Buchungen

In die Summe der Kontostände zählen nur Konten mit in_summe = 1 – das entspricht der Σ-Spalte im FinanzManager. Durchlaufende Auslagen und Rentenversicherungen bleiben so außen vor, erscheinen aber weiter in der Liste. Der QIF-Export kennt das Kennzeichen nicht; es wird im Kontoformular gepflegt.

Beträge in der Oberfläche

Eingegeben wird immer ein positiver Betrag; die Richtung bestimmt die Auswahl Ausgabe / Einnahme / Umbuchung. KontenController::zahl() liest das deutsche Format und entscheidet bei einem Punkt ohne Komma nach der Stellenzahl: drei Ziffern dahinter heißt Tausendertrenner (1.500), sonst Dezimaltrenner (1234.56).

Bestätigungen und die Content-Security-Policy

Die CSP erlaubt keine Inline-Skripte (script-src 'self'). Rückfragen vor dem Löschen dürfen deshalb nicht als onsubmit="return confirm(…)" im HTML stehen – der Browser verwirft sie stillschweigend, und gelöscht wird ohne Nachfrage. Stattdessen:

<form method="post" action="…" data-bestaetigen="Buchung wirklich löschen?">

public/static/app.js hängt den Dialog daran. Ein E2E-Test besteht auf dem Dialog, damit das nicht unbemerkt zurückfällt.