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>
3 KiB
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:
- Buchungen mit
konto_id = X(eigene Sicht) - Buchungen mit
gegenkonto_id = Xohne Splitzeilen, Betrag negiert - 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.