# Änderungen
## Cockpit: Vermögensentwicklung als gestapeltes Balkendiagramm (11.08.2026)
- Die Kachel *Vermögensentwicklung* war ein Platzhalter und zeigt jetzt die Kontostände
der letzten zwölf Monate je Monatsende – ein Balken pro Monat, ein Farbton pro Konto
(`Vermoegensentwicklung::reihe()`, `src/Views/cockpit/vermoegensdiagramm.php`).
- Negative Stände stapeln unter der Nulllinie.
- Schalter *nur aktive* oben rechts an der Kachel, im Standard eingeschaltet: Er blendet
stillgelegte Konten samt Legendeneintrag aus und merkt sich seinen Zustand über die
Einstellungen (`cockpit_vermoegen_nur_aktive`) – auch über das Abmelden hinaus. Er ist
das einzige Kriterium; das Σ-Kennzeichen filtert hier **nicht** mit, weil stillgelegte
Konten es meist nicht tragen und beim Ausschalten sonst genau das fehlte, was man sehen
will.
- `Buchung::bewegungenJeKontoUndMonat()` liefert die Monatsbewegungen in einer Abfrage je
Quelle – zwölf Stichtagsabfragen wären es sonst.
- Mehr Konten als Farbtöne: Die größten behalten ihre Farbe, der Rest wird zu *Übrige
Konten (n)* zusammengefasst. Zwei Konten in derselben Farbe wären in der Legende nicht
auseinanderzuhalten.
- Eigenes SVG, keine Diagrammbibliothek. Die Farben stehen als Klasse am Element und
folgen damit dem hellen wie dem dunklen Thema; jedes Balkenstück trägt ein `
`
als Tooltip, ohne eine Zeile JavaScript.
## Budgetplaner: drei Ansichten und Verteilen über das Zahnrad (11.08.2026)
- Abgelaufene Monate werden **nicht mehr zusammengefasst**. Alle zwölf Monate stehen
einzeln da; die Sammelspalte „Januar–Vormonat“ entfällt samt
`Budgetplaner::abgelaufenBis()` und `Budgetplaner::summe()`.
- Statt *Verlauf* und *alle Monate* gibt es drei Ansichten: **Alles** (Budget, Ist und
Saldo je Monat), **Nur Budget** und **Nur IST** mit je einer Spalte. Alte URLs mit
`ansicht=verlauf|alle` fallen auf *Alles* zurück.
- Die Budgetspalte ist überall dort, wo sie steht, editierbar – auch in vergangenen
Monaten.
- Vor jedem Eingabefeld sitzt ein **⚙**: *Ganzes Jahr* oder *Restliches Jahr* schreibt den
Wert des Feldes in die übrigen Monate der Zeile und überschreibt, was dort stand.
Gearbeitet wird auf den Eingabefeldern, sichtbar vor dem Speichern; der Knopf **↦** und
`Budget::aufFolgemonateUebertragen()` entfallen dafür.
- `app.js` war doppelt eingebunden (Kopf **und** Fuß): Jeder Handler lief zweimal, jede
Rückfrage kam zweimal. Jetzt nur noch im Kopf.
## Phase 3 – Budgetplaner und Monatsabschluss (11.08.2026)
**Datenmodell**
- Migration `004_budget_monatsabschluss.sql`: `budgets`, `budget_positionen`,
`monatsabschluesse`
- Ein Budget gilt für ein Jahr, mehrere je Jahr sind möglich, genau eines ist aktiv
- Ein Monat ohne Zeile in `budget_positionen` ist *nicht geplant* – etwas anderes als
*mit 0,00 geplant*
**Budgetplaner**
- Zeilen sind der Kategoriebaum (auf- und zuklappbar), 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* zum Planen
- Ausgaben werden ohne Vorzeichen eingetragen; das Vorzeichen 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 selbst nicht beplanbar
- Wert auf alle 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, auch nach Wiedereröffnung
- Abgeschlossene Monate sperren Buchungen (`Buchung::anlegen/speichern/loeschen`), in
beide Richtungen: weder hinein- noch herausschieben. Abschaltbar über die Einstellung
`monatsabschluss_sperrt_buchungen`
- Wiedereröffnen bleibt am Datensatz vermerkt
**Vorarbeiten in der Oberfläche**
- `.knopf.primaer` trug einen eigenen `margin-top` und stand dadurch in jeder Knopfleiste
tiefer als seine Nachbarn – sichtbar etwa 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 in der Tabelle. Dort hängt auch die Zuordnung der Konten: ankreuzen statt
jedes Konto einzeln zu öffnen (`Kontengruppe::kontenZuordnen()`).
- Der QIF-Export enthält **keine** Kontengruppen – kein Feld im `!Account`-Eintrag, nichts
in der `QSD`-Datei. Import-Oberfläche und Dokumentation sagen das jetzt und verweisen
auf die Zuordnung.
- Die Einrückung des Kategoriebaums lief über ein `style`-Attribut und wurde von der
Content-Security-Policy (`style-src 'self'`) stillschweigend verworfen; sie steht jetzt
als `data-ebene` im Markup. Aus demselben Grund blendete `app.js` Erfolgsmeldungen nie
aus – das läuft jetzt über eine Klasse statt über `element.style`.
**Tests**
- `11_BudgetTest`, `12_MonatsabschlussTest` (76 Prüfungen), E2E `planung.spec.js`
- Einzelheiten in `docs/planung.md`
## Korrektur – Σ-Kennzeichen ließ sich nicht abschalten (10.08.2026)
Das Kennzeichen *Zählt zur Summe der Kontostände* blieb beim Speichern eines Kontos
unverändert stehen, obwohl die Anwendung „Konto gespeichert“ meldete.
- Ursache: `KontenController::formulardaten()` reichte `in_summe` gar nicht durch. Ein
nicht angehaktes Ankreuzfeld steht nicht im POST, also griff im Model der Standardwert
„zählt mit“ – gespeichert wurde ein Ja, das niemand angegeben hatte.
- `formulardaten()` nimmt das POST-Array jetzt als Parameter und ist damit ohne HTTP
prüfbar; die Ankreuzfelder werden ausdrücklich auf `true`/`false` abgebildet.
- Der Standard „zählt zur Summe“ bleibt für Aufrufe ohne den Schlüssel bestehen – der
QIF-Import gibt das Feld nicht an und soll es auch nicht.
- `07_KontenTest` prüft die Formularabbildung und den Weg durch das Model; E2E
`stammdaten.spec.js` hakt das Kennzeichen in der Oberfläche ab, setzt es wieder und
sieht in beiden Fällen nach, statt der Erfolgsmeldung zu glauben.
## Phase 2 – Buchungen, Kontoblatt und Buchungsimport (10.08.2026)
**Erkenntnis aus dem Vollexport**
Der Plan ging davon aus, Umbuchungen stünden zweimal in der Datei und müssten
zusammengeführt werden. Das Gegenteil ist der Fall: 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 im Export hat keine ein zweites
Vorkommen. Mit erzeugter Gegenseite stimmen acht gegen den FinanzManager geprüfte
Kontosalden auf den Cent.
**Datenmodell**
- Migration `003_buchungen.sql`: `buchungen`, `buchung_splits`, `buchungsadressen`
- Eine Umbuchung ist ein Datensatz mit `gegenkonto_id`, kein gespiegeltes Paar – das
Kontoblatt des Gegenkontos zeigt sie mit umgekehrtem Vorzeichen
- Auch einzelne Splitzeilen dürfen ein Gegenkonto tragen
- Konten führen `in_summe` (die Σ-Spalte des FinanzManagers) sowie `saldo_online`
**Kontoblatt und Buchungsmaske**
- 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; Splitzeilen mit
Restbetragsanzeige, geprüft auch serverseitig
- Aktueller und erwarteter Saldo; in die Summen zählen nur Konten mit Σ-Kennzeichen
**QIF-Buchungsimport**
- `QifBuchungsImport` mit Stichtag: alles davor wird je Konto zum Anfangssaldo
aufsummiert – einschließlich der erzeugten Gegenbuchungen –, alles danach importiert
- Buchungen mit Datum nach dem Importtag gelten als Vorausberechnung und bleiben draußen
- Eröffnungsbuchungen (`L[eigenes Konto]`) werden nicht zu Umbuchungen
- Wertpapiergeschäfte schlagen vorerst nur mit ihrem Geldfluss auf dem Verrechnungskonto
zu Buche; Bestände folgen in Phase 6
- Idempotent über einen Fingerabdruck je Buchung, der auch echte Doppelbuchungen am
selben Tag auseinanderhält
- Am Vollexport geprüft: 833 Buchungen ab 01.01.2026, 7946 in die Anfangssalden von 35
Konten verrechnet, eine vorausberechnete verworfen
**Behoben**
- Die `confirm()`-Rückfragen vor dem Löschen waren wirkungslos. `public/static/app.js`
brachte den Mechanismus (`data-bestaetigen`) von Anfang an mit, war aber nie im Layout
eingebunden; ersatzweise standen Inline-Handler (`onsubmit`) im HTML, und die verstoßen
gegen die eigene Content-Security-Policy (`script-src 'self'`) – der Browser verwarf
sie stillschweigend, gelöscht wurde ohne Rückfrage. Jetzt ist das Skript eingebunden,
die Inline-Handler sind raus, und ein E2E-Test besteht auf dem Dialog.
**Tests**
- `09_BuchungenTest` – Umbuchungen mit zwei Sichten, Splitzeilen samt Summenprüfung,
Splitzeile mit Gegenkonto, aktueller und erwarteter Saldo, Σ-Kennzeichen, Filter
- `10_QifBuchungsImportTest` – Stichtag, Anfangssalden aus der Vorgeschichte, erzeugte
Gegenbuchung, Eröffnungsbuchung, Zukunftsfilter, zweiter Lauf ohne Wirkung
- E2E `buchungen.spec.js` – Ausgabe, Umbuchung auf beiden Kontoblättern, Splitbuchung,
abgewiesene Splitsumme, Suche, Löschbestätigung
## Phase 1 – Stammdaten und QIF-Stammdatenimport (09.08.2026)
**Datenmodell**
- Migration `002_stammdaten.sql`: `kontengruppen`, `konten`, `kategorien`, `klassen`
- Alle Tabellen führen `benutzer_id`; `Mandant::id()` liefert die Kennung aus der Session
oder – im CLI – vom ersten aktiven Benutzer
**Konten**
- Kontenübersicht nach Kontengruppen gebündelt, mit Gruppensummen und Gesamtsumme
- Die drei Saldospalten des FinanzManagers (Online-Saldo, aktueller und erwarteter Saldo)
sind angelegt und werden bis Phase 2 aus dem Anfangssaldo gespeist
- Kontoarten des FinanzManagers samt Zuordnung zu den QIF-Typen
- Kontenpflege mit Anfangssaldo, Kreditlimit, IBAN/BIC, Kontengruppe, aktiv/inaktiv
- Kontengruppen anlegen, umbenennen, sortieren, löschen – die Konten bleiben erhalten
**Kategorien und Klassen**
- Hierarchische Kategorien mit `:` als Trenner; der vollständige Pfad wird mitgeführt und
beim Umbenennen oder Verschieben für den ganzen Teilbaum neu geschrieben
- Art (Einnahme/Ausgabe) gilt für den ganzen Ast und vererbt sich an Unterkategorien
- Verschieben, Zusammenführen und Löschen; Verschieben in den eigenen Teilbaum wird
abgewiesen
- Register *Alle / Einnahmen / Ausgaben / Umbuchungskonten* wie im Original
- Klassen als flache zweite Auswertungsdimension, in der Liste direkt bearbeitbar
**QIF-Import**
- `QifParser`: zustandsbehaftetes, sequenzielles Lesen; ISO-8859-1 nach UTF-8, US-Beträge,
Datumsformat aus der Kopfzeile, Mehrfachfelder für spätere Splitzeilen
- `QifStammdatenImport` für Klassen, Kategorien und Konten – idempotent, mit Trockenlauf
und Protokoll; fehlende Zwischenstufen im Kategoriepfad entstehen selbsttätig
- Dialog unter *Verwaltung → QIF-Import* mit Upload, Trockenlauf und Übernahme
- `scripts/qif-import.php` für dieselbe Aufgabe auf der Kommandozeile
- Geprüft am echten Export: 17 Klassen, 105 Kategorien, 37 Konten
**Tests**
- `06_KategorienTest` – Hierarchie, Pfadpflege, Artvererbung, Verschieben, Zusammenführen
- `07_KontenTest` – Kontoarten, Gruppierung und Summen, Betragseingabe
- `08_QifImportTest` – Parser, Zeichensatz, Beträge und Datum, Import, Trockenlauf,
Prüfung der Dateinamen
- E2E `stammdaten.spec.js` – Konto, Kategorie mit Unterkategorie, Register, Klasse,
QIF-Upload mit Trockenlauf
## Korrektur – Monatsnamen auf Deutsch (09.08.2026)
- Der Einstellungsdialog zeigte unter *Buchhaltung* englische Monatsnamen, weil
`date('F')` sprachlich nicht umschaltbar ist.
- Monatsnamen liegen jetzt zentral als `View::MONATE` und `View::MONATE_KURZ` vor,
abrufbar über `View::monat()`. Budgetplaner und Auswertungen greifen später darauf zu.
- `05_DarstellungTest` prüft Monatsnamen, Betrags- und Datumsformat gegen eine feste
Sollliste – der Vergleich gegen `date('F')` allein würde April, August, September und
November nicht auffallen lassen.
## Phase 0 – Projektgerüst (09.08.2026)
Grundgerüst der Anwendung. Fachliche Module folgen ab Phase 1.
**Betrieb**
- Docker Compose mit drei Services: `nginx` (Port 8000), `php` (php-fpm 8.3), `db`
(MariaDB 11)
- `docker-entrypoint.sh` legt Datenverzeichnisse an und spielt Migrationen ein
- `scripts/migrate.php` – nummerierte SQL-Migrationen, idempotent, mit Journal-Tabelle
- `scripts/update.sh` – Update für den Produktionsserver: Backup, `git pull`,
Image-Neubau bei Bedarf, Migrationen, Rechte, Tests
- `scripts/backup.sh` – Datenbank- und Dateisicherung mit Rotation (7 Tage / 4 Wochen /
12 Monate), gegen Parallelläufe per `flock` abgesichert
**Anwendung**
- `Config`, `Database` (PDO-Wrapper, verschachtelbare Transaktionen), `Router`
(Platzhalter-Routen, CSRF-Prüfung für alle verändernden Requests), `View`, `Auth`
- Anmeldung mit Session-Härtung, Ersteinrichtung des ersten Benutzers
- Zentraler Einstellungsdialog mit Reitern (Allgemein, Darstellung, Buchhaltung,
Online-Banking, Import/Export, Wertpapiere, Diagnose); Werte in Tabelle
`einstellungen`
- Debug-Log, ein- und ausschaltbar, mit Maskierung von PINs, Passwörtern, TANs und
API-Keys; Ausnahmen werden immer protokolliert
- Layout nach Vorbild des FinanzManagers: feste Navigation links, Werkzeugleiste oben,
Tab-Leiste; Farbschema hell/dunkel/System
**Tests**
- Test-Harness mit Transaktions-Rollback und Aufräumen temporärer Pfade
- `01_AuthTest` – Anmeldung, Passwort-Hashing, CSRF, Cookie-Attribute
- `02_EinstellungenTest` – Lesen/Schreiben, Cache-Invalidierung, Bereichsabdeckung
- `03_RouterTest` – Pfad-Muster, Platzhalter, keine Treffer über Pfadgrenzen
- `04_DebugLogTest` – Schalter, Maskierung, Fehlerprotokollierung