Damit ist der geplante Funktionsumfang beisammen. Phase 4 abgeschlossen - Konfigurierbare Cockpit-Kacheln; abgewählte werden nicht berechnet - Einnahmen/Ausgaben je Monat als Balkenpaare mit Saldolinie, aufgesetzt auf den Summenbericht statt auf eine eigene Abfrage - Meine Ausgaben als Ring auf der obersten Kategorieebene - Offene Vorgänge aus allen Modulen Phase 5 – Online-Banking - nemiah/php-fints als Fork auf Codeberg, psr/log direkt eingebaut: die Anwendung hängt an keinem fremden Update-Zyklus - PIN AES-256-GCM-verschlüsselt; Schlüssel aus APP_KEY oder aus data/schluessel/app_key, den der Entrypoint einmalig erzeugt - TAN-Dialog über mehrere Requests (FinTs::persist plus serialisierte Aktion in der Sitzung) – ein wartender Prozess wäre mit php-fpm der falsche Weg - Abgleich statt Direktbuchung: verbinden, übernehmen oder verwerfen - Regeln mit vier Vergleichsarten, Betragsgrenzen und Split-Schema Phase 6 – Wertpapiere - Depotbuchung und Geldbuchung als zwei Seiten eines Geschäfts; betrag ist der Bruttowert, Nebenkosten stehen daneben - Einstand nach gleitendem Durchschnitt, realisierte Gewinne, Erträge - Kursabruf über Leeway oder FMP, täglicher Lauf per Cron - QIF-Import für !Type:Invst Dabei behoben: Der Import buchte den Geldfluss jedes Wertpapiergeschäfts als Abgang – auch bei Verkäufen und Dividenden; und er erkannte das Verrechnungskonto nicht, wenn dem Konto im L-Feld eine Kategorie vorangeht (bei Verkäufen also immer). Phase 7 – Nebenmodule und Zahlungsverkehr - Buchungsvorlagen inklusive QIF-Import, Splits werden mitskaliert - Regelmäßige Buchungen mit Fälligkeitslauf, Finanzkalender, Erinnerungen - Adressen mit IBAN-Prüfung, Verträge mit Kündigungsfrist, Sparziele - Kredite mit gerechnetem Tilgungsplan - Überweisungen über FinTS; die Buchung entsteht erst nach Annahme durch die Bank. Die pain.001-Nachricht erzeugt SepaXml selbst Nebenbei: .dockerignore ergänzt (ohne sie brach der Build an data/mariadb ab), PlatzhalterController entfernt, zwei Inline-Handler beseitigt, die die Content-Security-Policy ohnehin blockierte. 888 PHP-Prüfungen und 45 E2E-Tests grün. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
477 lines
28 KiB
Markdown
477 lines
28 KiB
Markdown
# Änderungen
|
||
|
||
## Phasen 4 bis 7 – Cockpit, Online-Banking, Wertpapiere, Nebenmodule (12.08.2026)
|
||
|
||
Damit ist der geplante Funktionsumfang beisammen: Von der Kontenübersicht bis zur
|
||
Überweisung läuft alles in der Anwendung.
|
||
|
||
### Phase 4 abgeschlossen – die übrigen Grafiken und das Cockpit
|
||
|
||
**Konfigurierbare Kacheln** (`src/Services/Cockpit.php`)
|
||
- Fünf Kacheln: Vermögensentwicklung, Einnahmen und Ausgaben, Meine Ausgaben,
|
||
Kontostände, Offene Vorgänge. Die Auswahl steht im Einstellungsdialog unter
|
||
*Darstellung*; abgewählte Kacheln werden nicht nur ausgeblendet, sondern gar nicht
|
||
erst berechnet.
|
||
- Die Reihenfolge im Raster kommt aus dem Katalog, nicht aus der Reihenfolge der
|
||
Ankreuzfelder – letztere wäre für den Nutzer keine sinnvolle Sortierung.
|
||
|
||
**Einnahmen und Ausgaben je Monat**
|
||
- Balkenpaare über zwölf Monate, darüber der Saldo als Linie.
|
||
- Die Zahlen kommen aus dem Summenbericht, nicht aus einer eigenen Abfrage: So zählt die
|
||
Kachel dieselben Bewegungen wie der Bericht und beide laufen nie auseinander.
|
||
|
||
**Meine Ausgaben als Ring**
|
||
- `Auswertung::verteilung()` fasst auf der **obersten Kategorie** zusammen – „300 - Wohnen“
|
||
statt fünfzehn Unterkategorien. Höchstens acht Segmente, der Rest wird gesammelt.
|
||
- Negative Kategoriesalden bekommen kein Segment, zählen aber in der Summe mit; sonst
|
||
ergänzten sich die Anteile nicht mehr zu hundert Prozent.
|
||
- Sonderfall Vollkreis: Bei einer einzigen Kategorie fielen Anfangs- und Endpunkt des
|
||
Bogens zusammen und es entstünde keine Fläche – deshalb zwei Halbringe.
|
||
|
||
**Offene Vorgänge**
|
||
- Nicht kategorisierte Buchungen, nicht abgeglichene Buchungen, fälliger Monatsabschluss –
|
||
später ergänzt um offene Bankumsätze, fällige Termine und Erinnerungen.
|
||
|
||
### Phase 5 – Online-Banking über FinTS
|
||
|
||
**Die Bibliothek als eigener Fork**
|
||
- `nemiah/php-fints` liegt als Fork auf Codeberg (`fds2610-bot/phpFinTS`), eingebunden über
|
||
ein VCS-Repository, Version fest auf `4.1.1` gepinnt.
|
||
- **psr/log ist dort direkt eingebaut** (acht Interface-Dateien unter `psr-log/`, eigener
|
||
Autoload-Eintrag, kein `require` mehr). Der Finanzmanager hängt damit an keinem fremden
|
||
Update-Zyklus; wie der Eingriff beim Nachziehen zu wiederholen ist, steht im Fork.
|
||
|
||
**Zugangsdaten**
|
||
- Migration `006_banking.sql`: `bank_zugaenge`, `bank_konten`, `bank_umsaetze`,
|
||
`abgleich_regeln`, `bank_abrufe`.
|
||
- PIN und TAN-Medium AES-256-GCM-verschlüsselt (`Services\Crypto`, übernommen aus
|
||
`Steuerkram`). Der Schlüssel kommt aus `APP_KEY` oder aus `data/schluessel/app_key`, den
|
||
der Entrypoint beim ersten Start erzeugt – eigenes Bind-Mount, damit er einen Neubau des
|
||
Images überlebt.
|
||
- Ein leeres PIN-Feld heißt „unverändert“, nicht „PIN löschen“.
|
||
|
||
**Abruf über mehrere Requests**
|
||
- Fast jede Bank verlangt schon beim Login eine TAN. `Services\FinTsAbruf` sichert den
|
||
Dialog (`FinTs::persist()`) und die laufende Aktion in der Sitzung und macht nach der
|
||
Eingabe an derselben Stelle weiter. Ein wartender Prozess wäre mit php-fpm der falsche
|
||
Weg.
|
||
- Klassische Verfahren zeigen die photoTAN-Grafik, entkoppelte (pushTAN) fragen auf
|
||
Knopfdruck nach, statt zu warten.
|
||
- Umsätze, die vor einer TAN-Unterbrechung geschrieben wurden, bleiben erhalten.
|
||
|
||
**Abgleich statt Direktbuchung**
|
||
- Abgerufene Umsätze landen in `bank_umsaetze` und werden nicht sofort gebucht. Die
|
||
Vorschlagsliste kennt drei Wege: mit einer erfassten Buchung **verbinden**, als neue
|
||
Buchung **übernehmen** oder **verwerfen**.
|
||
- Gleiches Konto, gleicher Betrag, Datum innerhalb der Toleranz (Vorgabe fünf Tage,
|
||
einstellbar) – so findet der Abgleich die schon erfasste Buchung. Eine bereits
|
||
verbundene Buchung wird nie ein zweites Mal vorgeschlagen.
|
||
- `extern_id` verhindert, dass derselbe Umsatz bei überlappenden Abrufzeiträumen zweimal
|
||
hereinkommt.
|
||
|
||
**Regeln**
|
||
- Muster auf Empfänger und/oder Verwendungszweck, vier Vergleichsarten samt regulärem
|
||
Ausdruck, Betragsgrenzen mit Vorzeichen, Wirkung auf Kategorie, Klasse, Gegenkonto oder
|
||
ein festes Split-Schema in Prozent.
|
||
- Die erste passende Regel gewinnt (nach Priorität) – kein Sammeln über mehrere Regeln,
|
||
sonst wäre nicht mehr nachvollziehbar, warum am Ende die eine Kategorie dasteht.
|
||
- Reguläre Ausdrücke werden beim Speichern geprüft; ein kaputtes Muster brächte sonst
|
||
jeden Abruf zum Stehen.
|
||
|
||
### Phase 6 – Wertpapiere und Depots
|
||
|
||
- Migration `007_wertpapiere.sql`: `wertpapiere`, `depot_buchungen`, `kurse`.
|
||
- **Zwei Seiten je Geschäft**: die Depotbuchung mit Stück und Kurs, die Geldbuchung auf dem
|
||
Verrechnungskonto. `betrag` ist der Bruttowert, Gebühren und Steuern stehen daneben –
|
||
nur so enthält der Einstand die Nebenkosten genau einmal.
|
||
- **Einstand nach gleitendem Durchschnitt**, realisierte Gewinne beim Verkauf. (Für die
|
||
Steuer gilt FIFO; maßgeblich ist dort die Abrechnung der Bank.)
|
||
- Ohne Kurs wird nicht bewertet: Die Position sagt „kein Kurs“, statt den Einstand als
|
||
Ersatzwert auszugeben.
|
||
- **Kursabruf** über Leeway.tech oder Financial Modeling Prep, Kürzel wird nach dem ersten
|
||
Fund gespeichert. Abgerufen wird nur für Papiere mit Bestand und nur einmal am Tag;
|
||
täglicher Lauf über `scripts/kursabruf.php`.
|
||
- **QIF-Import für `!Type:Invst`**: Wertpapier, Depotbuchung und Geldbuchung in einem Zug.
|
||
- Depots erscheinen in der Kontenübersicht mit ihrem bewerteten Bestand.
|
||
|
||
**Dabei behoben:** Der Import buchte den Geldfluss *jedes* Wertpapiergeschäfts als Abgang –
|
||
auch bei Verkäufen und Dividenden. Das QIF nennt im Feld `$` immer einen positiven Betrag,
|
||
die Richtung steckt allein im Vorgang. Zudem wurde das Verrechnungskonto nicht erkannt,
|
||
wenn dem Konto im `L`-Feld eine Kategorie vorangeht
|
||
(`LKursgewinne:Realisierte Gewinne|[Konto]`) – bei Verkäufen also immer.
|
||
|
||
### Phase 7 – Nebenmodule und Zahlungsverkehr
|
||
|
||
- Migration `008_nebenmodule.sql`: `buchungsvorlagen`, `regelmaessige_buchungen`,
|
||
`erinnerungen`, `adressen`, `vertraege`, `sparziele`, `kredite`, `zahlungsauftraege`.
|
||
- **Buchungsvorlagen** inklusive QIF-Import (`!Type:Memorized`); Splitzeilen werden bei
|
||
abweichendem Betrag im Verhältnis mitskaliert.
|
||
- **Regelmäßige Buchungen** mit Fälligkeitslauf: automatisch oder auf Bestätigung, jeder
|
||
zurückliegende Termin wird einzeln gebucht, abgeschlossene Monate bleiben unangetastet.
|
||
- **Finanzkalender** aus vier Quellen – gerechnet, nicht gespeichert.
|
||
- **Adressen** mit IBAN-Prüfung nach ISO 13616 (Länge je Land, Prüfziffer modulo 97).
|
||
- **Verträge** mit Kündigungsfrist und Jahresbetrag; ablaufende Fristen stehen oben und im
|
||
Kalender.
|
||
- **Sparziele**, deren Stand aus dem Kontosaldo kommt statt aus einem zweiten Zähler.
|
||
- **Kredite** mit gerechnetem Tilgungsplan (Annuität, Sondertilgung, Restschuld zum
|
||
Zinsbindungsende). Deckt die Rate die Zinsen nicht, gibt es einen Hinweis statt eines
|
||
Plans.
|
||
- **Zahlungsverkehr**: Überweisungen und Terminüberweisungen über FinTS. Die Buchung
|
||
entsteht erst, wenn die Bank den Auftrag angenommen hat; gesendete Aufträge bleiben als
|
||
Beleg unveränderlich stehen.
|
||
- **SEPA-XML selbst erzeugt** (`Services\SepaXml`, pain.001.001.03) statt einer weiteren
|
||
Abhängigkeit – samt Umschrift der Umlaute in den SEPA-Zeichenvorrat.
|
||
|
||
### Nebenbei
|
||
|
||
- `.dockerignore` ergänzt: Ohne sie schickte der Build das ganze Projektverzeichnis an den
|
||
Daemon, einschließlich `data/mariadb` – der Build brach mit „permission denied“ ab.
|
||
- `PlatzhalterController` entfernt: Alle Navigationspunkte haben jetzt ihr Modul.
|
||
- Neue Reiter im Einstellungsdialog: *Online-Banking* und *Wertpapiere*.
|
||
|
||
**Doku:** `docs/cockpit.md`, `docs/banking.md`, `docs/wertpapiere.md`,
|
||
`docs/nebenmodule.md`.
|
||
|
||
**Tests:** 15 bis 18 (Cockpit, Banking, Wertpapiere, Nebenmodule) – zusammen 888 Prüfungen.
|
||
|
||
## Phase 4 – Auswertungen mit Berichtsmodul (11.08.2026)
|
||
|
||
**Datenmodell**
|
||
- Migration `005_berichte.sql`: `berichte`, `berichtsgruppen`
|
||
- Ein Bericht ist Name, Typ und Filter – die Zahlen entstehen bei jedem Aufruf neu aus den
|
||
Buchungen. Gespeichert wird ausschließlich die Frage.
|
||
- Der Filter liegt als JSON in einer Spalte und läuft beim Lesen immer durch
|
||
`Auswertung::normalisiere()`: Unbekannte Felder fallen weg, unbekannte Werte auf die
|
||
Vorgabe zurück. Keine eigene Spalte je Filterfeld – das sind Anzeigeparameter ohne
|
||
Fremdschlüssel, und jede neue Filtermöglichkeit bräuchte sonst eine Migration.
|
||
- Keine neuen Einstellungen: Zeitraum, Zeilen-Summe und Einrückung gehören zum einzelnen
|
||
Bericht, nicht in den Einstellungsdialog.
|
||
|
||
**Fünf Berichtstypen** (`src/Services/Auswertung.php`)
|
||
- **Summenbericht**: Summen je Zeilen-Summe und Spalte, getrennt nach Einnahmen und
|
||
Ausgaben, mit Schlusszeile *Gesamt Einnahmen − Ausgaben*
|
||
- **Buchungsbericht**: die Einzelbuchungen, gruppiert mit Zwischensummen
|
||
- **Vergleichsbericht**: Zeitraum gegen denselben Zeitraum des Vorjahres, mit Differenz
|
||
und Veränderung in Prozent
|
||
- **Kontostandsbericht**: Salden je Konto zu den Enden der Spaltenzeiträume
|
||
- **Budgetbericht**: Budget gegen Ist je Kategorie, mit Saldo
|
||
|
||
**Was gezählt wird**
|
||
- Bewegungen statt Buchungen: nicht gesplittete Buchungen mit ihrem Betrag, gesplittete
|
||
über ihre Splitzeilen – sonst zählte eine Splitbuchung doppelt oder gar nicht.
|
||
- **Umbuchungen bleiben außen vor** (weder Einnahme noch Ausgabe, wie im Budgetplaner).
|
||
Nur der Buchungsbericht führt sie auf Wunsch mit; sie erscheinen dort mit dem Gegenkonto
|
||
in eckigen Klammern.
|
||
- Unkategorisierte Buchungen stehen als eigene Zeile *Nicht kategorisiert* am Ende ihres
|
||
Abschnitts, statt lautlos aus der Summe zu fallen.
|
||
- Einnahme oder Ausgabe entscheidet die **Art der Kategorie**, nicht das Vorzeichen: Eine
|
||
Erstattung ist eine negative Ausgabe und gehört unter die Ausgaben.
|
||
- Aggregiert wird in PHP. Die Datenbank liefert die Bewegungen, das Einsortieren in Zeilen
|
||
und Spalten passiert im Service – Baumsummen, Vorzeichenregeln und Spaltenraster wären
|
||
in SQL erheblich schwerer nachzulesen.
|
||
|
||
**Vorzeichen**
|
||
- Intern durchgehend Buchhaltung (Ausgaben negativ), in der Anzeige dreht der
|
||
Ausgabenabschnitt sein Vorzeichen – wie im FinanzManager steht dort 636,39 statt
|
||
−636,39. Die Schlusszeile rechnet mit den echten Werten.
|
||
- Buchungs- und Budgetbericht sowie der Drilldown behalten das Vorzeichen: Sie haben keine
|
||
Abschnitte, die es erklären.
|
||
|
||
**Zeitraum und Spalten** (`src/Services/Zeitraum.php`, reine Kalenderrechnung ohne Datenbank)
|
||
- Neun Vorgaben von *Aktueller Monat* bis *Gesamter Zeitraum*; sie rechnen sich bei jedem
|
||
Aufruf neu aus dem Tagesdatum aus. Ein Bericht mit *Letzter Monat* zeigt im August den
|
||
Juli, nicht für immer den Monat seiner Entstehung.
|
||
- Spalten-Aufteilung nach Monat, Quartal oder Jahr; die Randspalten werden auf den
|
||
Zeitraum gestutzt.
|
||
- *Gesamter Zeitraum* nimmt die Datumsgrenzen der Buchungen (`Buchung::datumsgrenzen()`)
|
||
und endet spätestens heute – ein Ende in der Zukunft brächte nur leere Spalten.
|
||
- Beim Vorjahresvergleich bleibt ein Monatsende ein Monatsende, sonst träfe der 28.02. auf
|
||
einen 29.02.
|
||
|
||
**Summenbericht als Baum**
|
||
- Jede Kategorie zeigt ihre eigenen Werte, Kategorien mit Nachfahren bekommen dahinter
|
||
*Gesamt <Name>* mit der Astsumme. Eine Elternkategorie ohne eigene Buchungen steht als
|
||
bloße Kopfzeile da – genau wie im Original.
|
||
- *ohne Einrückung* schaltet auf eine flache Liste mit vollem Pfad und ohne Astsummen.
|
||
- Ein Kategoriefilter schließt die Unterkategorien ein: Wer „400 - Leben“ wählt, meint den
|
||
ganzen Ast.
|
||
|
||
**Oberfläche**
|
||
- Übersicht mit Kachel *Standardvorlagen* und je einer Kachel pro Berichtsgruppe,
|
||
Menü *Letzte Berichte*, Verwaltung der Gruppen unter `/auswertungen/gruppen`.
|
||
- Filterleiste sendet per GET: Jeder Zwischenstand ist eine Adresse, die man sich merken,
|
||
weitergeben und mit *Speichern unter* festhalten kann.
|
||
- *Speichern unter* kann die Berichtsgruppe gleich mit anlegen – wer sie erst anderswo
|
||
erzeugen müsste, verlöre den eingestellten Filter.
|
||
- Eine gelöschte Gruppe nimmt ihre Berichte nicht mit: Sie rücken unter *Nicht gruppiert*.
|
||
- Die Aufklappmenüs sind `<details>`-Elemente, kein JavaScript.
|
||
- Der Platzhalter für `/auswertungen` entfällt aus dem `PlatzhalterController`.
|
||
|
||
**Drilldown**
|
||
- Zeilenname und jede Zahl führen in die Buchungen dahinter, die Zahl zusätzlich
|
||
eingeschränkt auf ihre Spalte.
|
||
- Gefiltert wird durch Nachsieben derselben Bewegungen, aus denen die Zelle entstand –
|
||
nicht durch Übersetzen der Zeile in einen neuen Filter. Nur so trifft der Drilldown auch
|
||
*Nicht kategorisiert* oder eine Astsumme über mehrere Unterkategorien.
|
||
|
||
**CSV-Export** (`src/Services/BerichtExport.php`)
|
||
- Dieselben Zeilen und Vorzeichen wie am Bildschirm, je Berichtstyp eigene Spalten.
|
||
- Semikolon, Dezimalkomma, kein Tausenderpunkt, CRLF und BOM – sonst zeigt die
|
||
Tabellenkalkulation „Übrige“ statt „Übrige“ und liest Beträge als Text.
|
||
|
||
**Tests**
|
||
- `tests/14_AuswertungTest.php` (142 Prüfungen) und
|
||
`tests/e2e/specs/auswertungen.spec.js` (7 Abläufe)
|
||
- Einzelheiten in `docs/auswertungen.md`
|
||
|
||
## 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 `<title>`
|
||
als Tooltip, ohne eine Zeile JavaScript.
|
||
- **Übrige Konten (n)** ist ein Link: Er führt eine Ebene tiefer (`/?ebene=N`) und zeigt
|
||
die nächstkleineren Konten einzeln – beliebig tief, bis der Rest in die Palette passt.
|
||
Unter der Legende steht der Weg zurück. Jedes Konto erscheint auf genau einer Ebene, und
|
||
eine zu hohe Ebene fällt auf die letzte zurück (`Vermoegensentwicklung::ebenen()`).
|
||
- Die Balken nutzen die Kachelbreite aus, indem sie **auseinanderrücken**, nicht indem sie
|
||
breiter werden: Die Zeichenfläche rechnet waagerecht in Prozent, die Balkenbreite bleibt
|
||
bei 16 Punkten. Achsenbeschriftung und Zeichenfläche sind dafür zwei SVGs nebeneinander –
|
||
ein einzelnes könnte von seinen Prozentangaben keine feste Achsenbreite abziehen.
|
||
- Die Kachel ist so groß wie ihre Nachbarn: Der Ausgriff über zwei Rasterspalten
|
||
(`.kachel.breit`) und das engere Cockpit-Raster (`.kachelraster.dicht`) entfallen wieder.
|
||
- Die Legende steht unter der Zeichnung statt daneben und in kleinerer Schrift (.74rem);
|
||
sie füllt so viele Spalten, wie die Kachel hergibt. Der Schalter teilt sich die erste
|
||
Zeile mit der Überschrift, damit er in der schmaleren Kachel oben rechts bleibt.
|
||
|
||
## 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
|