# 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. ## Mehrfachauswahl im Kontoblatt Vor dem Datum steht je Zeile ein Ankreuzfeld, in der Titelzeile eines für alle gerade angezeigten Buchungen. *Auswahl löschen* in der Aktionsleiste schickt die Auswahl an `POST /konten/{id}/buchungen/loeschen`. Zwei Punkte, die sich nicht ändern dürfen: * Der Knopf steht **über** der Tabelle, die Ankreuzfelder stehen **in** ihr. Verbunden sind sie über `form="buchungsauswahl"`, nicht über Verschachtelung – ein Formular um die ganze Tabelle würde das Filterformular umschließen. * Zeilen mit `gegensicht = true` bekommen kein Ankreuzfeld, und der Controller prüft zusätzlich `konto_id`. Die Gegenseite einer fremden Umbuchung wird nur auf ihrem Ursprungskonto gelöscht – dieselbe Regel wie beim Bearbeiten. Filter und Seite hängen an der Abfragezeichenfolge des Formularziels. Der Controller baut den Weg zurück aus den bekannten Feldern neu zusammen, statt ein mitgeschicktes Ziel zu übernehmen; so lässt sich kein fremder Weiterleitungsort einschleusen. ## 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
``` `public/static/app.js` hängt den Dialog daran. Ein E2E-Test besteht auf dem Dialog, damit das nicht unbemerkt zurückfällt.