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>
28 KiB
Ä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-fintsliegt als Fork auf Codeberg (fds2610-bot/phpFinTS), eingebunden über ein VCS-Repository, Version fest auf4.1.1gepinnt.- psr/log ist dort direkt eingebaut (acht Interface-Dateien unter
psr-log/, eigener Autoload-Eintrag, keinrequiremehr). 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 ausSteuerkram). Der Schlüssel kommt ausAPP_KEYoder ausdata/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\FinTsAbrufsichert 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_umsaetzeund 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_idverhindert, 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.
betragist 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
.dockerignoreergänzt: Ohne sie schickte der Build das ganze Projektverzeichnis an den Daemon, einschließlichdata/mariadb– der Build brach mit „permission denied“ ab.PlatzhalterControllerentfernt: 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
/auswertungenentfällt aus demPlatzhalterController.
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) undtests/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()undBudgetplaner::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|allefallen 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.jswar 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_positionenist 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 Einstellungmonatsabschluss_sperrt_buchungen - Wiedereröffnen bleibt am Datensatz vermerkt
Vorarbeiten in der Oberfläche
.knopf.primaertrug einen eigenenmargin-topund 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 derQSD-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 alsdata-ebeneim Markup. Aus demselben Grund blendeteapp.jsErfolgsmeldungen nie aus – das läuft jetzt über eine Klasse statt überelement.style.
Tests
11_BudgetTest,12_MonatsabschlussTest(76 Prüfungen), E2Eplanung.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()reichtein_summegar 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 auftrue/falseabgebildet.- 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_KontenTestprüft die Formularabbildung und den Weg durch das Model; E2Estammdaten.spec.jshakt 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) sowiesaldo_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
QifBuchungsImportmit 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.jsbrachte 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, Filter10_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 SplitzeilenQifStammdatenImportfü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.phpfü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ühren07_KontenTest– Kontoarten, Gruppierung und Summen, Betragseingabe08_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::MONATEundView::MONATE_KURZvor, abrufbar überView::monat(). Budgetplaner und Auswertungen greifen später darauf zu. 05_DarstellungTestprüft Monatsnamen, Betrags- und Datumsformat gegen eine feste Sollliste – der Vergleich gegendate('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.shlegt Datenverzeichnisse an und spielt Migrationen einscripts/migrate.php– nummerierte SQL-Migrationen, idempotent, mit Journal-Tabellescripts/update.sh– Update für den Produktionsserver: Backup,git pull, Image-Neubau bei Bedarf, Migrationen, Rechte, Testsscripts/backup.sh– Datenbank- und Dateisicherung mit Rotation (7 Tage / 4 Wochen / 12 Monate), gegen Parallelläufe perflockabgesichert
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-Attribute02_EinstellungenTest– Lesen/Schreiben, Cache-Invalidierung, Bereichsabdeckung03_RouterTest– Pfad-Muster, Platzhalter, keine Treffer über Pfadgrenzen04_DebugLogTest– Schalter, Maskierung, Fehlerprotokollierung