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

69 lines
3 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.

# 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:
```html
<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.