Finanzmanager/docs/cockpit.md
fds2610-bot f388d04c05 Phasen 4 bis 7: Cockpit, Online-Banking, Wertpapiere, Nebenmodule
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>
2026-08-12 06:59:24 +02:00

9.6 KiB
Raw Blame History

Cockpit

Die Startseite. Jede Kachel beantwortet eine Frage auf einen Blick.

Welche Kacheln erscheinen

Es gibt fünf: Vermögensentwicklung, Einnahmen und Ausgaben, Meine Ausgaben, Kontostände und Offene Vorgänge. Welche davon gezeigt werden, steht im Einstellungsdialog unter Darstellung – der Katalog dazu in Cockpit::KACHELN, die Auswahl als kommagetrennte Liste unter cockpit_kacheln.

Vorgabe ist: alle. Eine abgewählte Kachel wird nicht nur ausgeblendet, sondern gar nicht erst berechnet – CockpitController::index() holt die Daten je Kachel einzeln. Wer das Cockpit entschlacken will, macht es damit auch schneller.

Die Reihenfolge im Raster ist die des Katalogs, nicht die der Ankreuzfelder: Ein Formular liefert Ankreuzfelder in der Reihenfolge des HTML, und die wäre für den Nutzer keine sinnvolle Sortierung.

Vermögensentwicklung

Ein gestapeltes Balkendiagramm der Kontostände zum Monatsende über die letzten zwölf Monate – ein Balken je Monat, ein Farbton je Konto.

Woher die Zahlen kommen

Welche Konten zählen, entscheidet allein der Schalter „nur aktive“. Das Σ-Kennzeichen (in_summe) spielt hier keine Rolle – anders als in der Kontenübersicht. Stillgelegte Konten tragen es meist nicht; würde es mitfiltern, fiele beim Ausschalten des Schalters genau das weg, was man sehen will. Die Summe der Kachel kann bei ausgeschaltetem Schalter deshalb von der Summe der Kontenübersicht abweichen.

Der Stand eines Monats ist der Stand des Vormonats plus dessen Bewegungen:

  • Buchung::saldenJeKonto() liefert den Stand am Tag vor dem Zeitraum – alles Ältere steckt damit im ersten Balken.
  • Buchung::bewegungenJeKontoUndMonat() liefert die Bewegungen innerhalb des Zeitraums, nach Monat getrennt.

Zwei Abfragen statt zwölf Stichtagsabfragen. Beide Methoden zählen dieselben drei Quellen: die Buchungen des Kontos, die Gegenseiten fremder Umbuchungen und die Gegenseiten aus Splitzeilen. Eine Umbuchung verschiebt deshalb zwischen zwei Balkenstücken, ohne die Summe zu ändern.

Konten, die zwölf Monate lang auf null standen, fallen weg; sie zeichnen ohnehin keinen Balken.

Der Schalter „nur aktive“

Oben rechts an der Kachel sitzt ein Ein/Aus-Schalter. Eingeschaltet – der Standard – zählen nur Konten, die noch geführt werden. Ausgeschaltet kommen stillgelegte dazu: Sonst fehlte im Rückblick Vermögen, das damals auf einem inzwischen aufgelösten Konto lag.

Der Zustand liegt unter cockpit_vermoegen_nur_aktive in den Einstellungen und gilt bis zur nächsten Umschaltung – auch über das Abmelden hinaus. Im Einstellungsdialog erscheint er nicht: Er gehört an die Kachel, nicht in einen Reiter.

Umgeschaltet wird über einen abschickenden Knopf, nicht über ein Ankreuzfeld. Ein Ankreuzfeld könnte sein Formular nur mit einem Inline-Skript selbst abschicken, und die Content-Security-Policy verbietet Inline-Skripte.

Stapel und Farben

Positive Stände stapeln sich über der Nulllinie, negative darunter – ein überzogenes Girokonto oder eine Kreditkarte zieht den Balken nach unten.

Die Palette in app.css hat zwölf Töne (.serie-1 bis .serie-12). Gibt es mehr Konten, behalten die größten ihre eigene Farbe und der Rest wird zu Übrige Konten (n) zusammengefasst – zwei Konten in derselben Farbe wären in der Legende nicht auseinander zu halten. Jede Serie setzt --farbe; davon leben der Balken (fill) und der Legendenpunkt (background) gleichermaßen, und beide folgen dem hellen wie dem dunklen Thema.

Ebenen: in „Übrige Konten“ hineinsehen

Der Legendeneintrag Übrige Konten (n) ist ein Link. Er führt auf /?ebene=1: Dort stehen die nächstelf Konten einzeln, und was dann noch bleibt, sammelt sich erneut. So geht es beliebig tief, bis der Rest in die Palette passt – dann entfällt das Sammelstück, und das ist die letzte Ebene. Vermoegensentwicklung::ebenen() rechnet aus, wie viele es sind; eine zu hohe Ebene fällt auf die letzte zurück, statt eine leere Kachel zu zeigen.

Zurück geht es über die Zeile unter der Legende: eine Ebene zurück und, ab der dritten, zu den größten Konten.

Jedes Konto steht auf genau einer Ebene: Was oben schon eine eigene Farbe hatte, ist im Sammelstück nicht mehr enthalten. Sonst wüchse der Rest mit jeder Ebene, statt zu schrumpfen. Weil auf tieferen Ebenen die großen Konten fehlen, passt sich die Achse an – die kleinen Konten werden dadurch überhaupt erst sichtbar. Die Ebene steht in der Adresse und nicht in den Einstellungen: Beim nächsten Aufruf beginnt man wieder oben.

SVG von Hand

Keine Diagrammbibliothek (siehe PLAN.md): src/Views/cockpit/vermoegensdiagramm.php schreibt das SVG selbst. Farben stehen als Klasse am Element, nicht als fill-Attribut – ein Attribut käme zwar an der Content-Security-Policy vorbei, könnte aber nicht auf die Themenumschaltung reagieren. Ein style-Attribut verwürfe die CSP ohnehin.

Die Achse rundet über Vermoegensentwicklung::skala() auf Schritte von 1, 2, 2,5 oder 5 mal einer Zehnerpotenz und schließt die Null immer ein. Jedes Balkenstück trägt ein <title> mit Konto, Monat und Betrag – das ist der Tooltip, ohne eine Zeile JavaScript.

Senkrecht rechnet die Zeichnung in Bildpunkten, waagerecht in Prozent: Die Balkenmitte von Monat i liegt bei (i + 0,5) / 12 der Fläche, der Balken selbst ist feste 16 Punkte breit und wird per transform um seine halbe Breite nach links gerückt. In einer breiten Kachel rücken die Balken damit auseinander, statt breiter zu werden.

Deshalb sind es zwei SVGs nebeneinander (.diagrammflaeche): links die Achsenbeschriftung mit fester Breite, rechts die Zeichenfläche. In einem einzigen SVG ginge das nicht – Prozentangaben rechnen immer auf die volle Breite, und ein Attribut kann davon keine feste Achsenbreite abziehen. Ein viewBox verbietet sich aus demselben Grund: Es würde alles gleichmäßig strecken, die Balken eingeschlossen.

Die Kachel ist eine gewöhnliche Kachel des Cockpit-Rasters, so breit wie ihre Nachbarn: Ein Ausgriff über zwei Spalten machte sie zum Fremdkörper zwischen den anderen dreien.

Die Legende führt nur Farbe und Kontoname und steht darunter, in kleinerer Schrift und so vielen Spalten, wie die Kachel hergibt (auto-fill). Die Beträge stehen am Balken im Tooltip und in der Kachel Kontostände; in der Legende kosteten sie nur Breite.

Einnahmen und Ausgaben

Balkenpaare über die letzten zwölf Monate: links die Einnahmen, rechts die Ausgaben, darüber der Saldo als Linie mit einem Punkt je Monat.

Die Zahlen kommen aus dem Summenbericht (Cockpit::monatsreihe() ruft Auswertung::bericht() mit Zeitraum letzte zwölf Monate und Spalten-Aufteilung Monat). Damit zählt die Kachel dieselben Bewegungen wie der Bericht: Splitzeilen einzeln, Umbuchungen gar nicht. Eine eigene Abfrage wäre schneller zu schreiben und liefe früher oder später auseinander.

Ausgaben stehen wie im Bericht ohne Minuszeichen; der Saldo ist Einnahmen minus Ausgaben und kann natürlich negativ sein. Der Knopf unter der Kachel öffnet denselben Ausschnitt als Summenbericht.

Meine Ausgaben

Ein Ring der Ausgaben des laufenden Jahres, aufgeteilt nach oberster Kategorie: „300 - Wohnen“ statt Miete, Strom und Nebenkosten einzeln. Ein Ring mit hundert Segmenten beantwortet keine Frage; wer es genauer braucht, klickt sich in den Summenbericht.

Auswertung::verteilung() rechnet das: Bewegungen des Zeitraums, Einnahmen oder Ausgaben, je Bewegung die Wurzel ihrer Kategorie (über den Kategoriepfad, nicht über die eltern_id-Kette), Beträge vorzeichenlos gedreht wie im Ausgabenabschnitt der Berichte.

Mehr als acht Segmente werden nicht gezeichnet – die kleinsten wandern in Übrige (n). Kategorien, die im Saldo negativ sind (mehr Erstattung als Ausgabe), bekommen kein Segment; in der Gesamtsumme in der Mitte des Rings stecken sie weiter, sonst ergänzten sich die Anteile nicht mehr zu hundert Prozent.

Gezeichnet wird als SVG-Pfad mit zwei Bögen je Segment (ausgabendiagramm.php). Sonderfall ist die einzige Kategorie mit hundert Prozent: Anfangs- und Endpunkt des Bogens fielen zusammen und es entstünde keine Fläche – deshalb zwei Halbringe derselben Farbe.

Offene Vorgänge

Was auf Bearbeitung wartet, mit Zahl und Weg dorthin:

  • ohne Kategorie – Buchungen ohne Kategorie, die auch keine Umbuchung und nicht gesplittet sind. Umbuchungen brauchen keine Kategorie und zählen deshalb nicht mit.
  • nicht abgeglichen – Buchungen aktiver Konten bis heute ohne Abgleich-Kennzeichen.
  • Monatsabschluss steht aus – der Vorschlag aus Monatsabschluss::naechsterOffener(), aber nur, wenn dieser Monat wirklich noch offen ist und Buchungen trägt.

Ist nichts offen, bleibt die Liste leer und die Kachel sagt es ausdrücklich. Ab Phase 5 kommen die noch nicht zugeordneten Bankumsätze hinzu, ab Phase 7 die fälligen regelmäßigen Buchungen.

Tests

  • tests/15_CockpitTest.php – Kachelauswahl (Reihenfolge, Unbekanntes, leere Auswahl), Monatsreihe mit Trennung von Einnahmen und Ausgaben, Umbuchungen bleiben draußen, Verteilung auf die Wurzelkategorie, Kürzung auf die Segmentgrenze, offene Vorgänge
  • tests/13_VermoegensentwicklungTest.php – Zeitraum, Fortschreibung der Stände, Umbuchungen, Σ-Kennzeichen, Schalter „nur aktive“, Zusammenfassen jenseits der Palette, Ebenen (jedes Konto genau einmal, letzte Ebene ohne Sammelstück, zu tiefe Ebene fällt zurück), Skala
  • tests/e2e/specs/smoke.spec.js – die Kachel zeichnet zwölf Balken, keine Farbe der Legende kommt doppelt vor, der Schalter behält seinen Zustand über den Neuaufruf, und Übrige Konten führt eine Ebene tiefer und wieder zurück