2018 lines
127 KiB
Markdown
2018 lines
127 KiB
Markdown
# Änderungen
|
||
|
||
## Automatische Buchungsnummer (01.10.2026)
|
||
|
||
- Neue Buchungen ohne Nummer erhalten beim Anlegen die nächste laufende Nummer ihres Kontos (höchste rein numerische Nummer + 1, `Buchung::naechsteNummer()`). Vorgegebene Nummern (QIF, CSV, Formular) bleiben, bestehende Buchungen werden nicht verändert; die interne ID bleibt verborgen.
|
||
- Test: `53_BuchungsnummerTest.php`.
|
||
|
||
## „+ Neu“ öffnet das Formular als Dialog (01.10.2026)
|
||
|
||
- Das Formular zur Neuanlage stand in elf Ansichten unter der Liste. Jetzt öffnet der Knopf „+ Neu“ es als modalen Dialog (`app.js`, `.neudialog`); nach dem Speichern erscheint die Liste wieder, mit dem neuen Eintrag. Betroffen: Klassen, Buchungsvorlagen, Regelmäßige Buchungen, Erinnerungen, Verträge, Adressen, Sparziele, Kredite, Abgleichregeln, Budgets, Berichtsgruppen.
|
||
- E2E: `neudialog.spec.js`; bestehende Specs öffnen den Dialog zuerst.
|
||
|
||
## Regelmäßige Buchungen: Klick zum Bearbeiten (01.10.2026)
|
||
|
||
- In „Alle Reihen“ erscheinen alle Felder zunächst als Text und werden erst beim Anklicken zum Eingabefeld (`reihenliste.js`). Ja/Nein-Felder schalten per Klick um, „Speichern“ erscheint erst nach einer Änderung. Konto und Klasse stehen untereinander, die Spalte Name/Buchungstext nimmt den freien Platz.
|
||
|
||
## Klasse und nächster Termin in „Alle Reihen“ (01.10.2026)
|
||
|
||
- Unter der Kontoauswahl gibt es eine Klassenauswahl, der „Nächste Termin“ ist ein Datumsfeld und lässt sich von Hand verschieben (`RegelmaessigeBuchung::speichern()`, ungültige Angaben werden ignoriert).
|
||
|
||
## Buchungstext bei regelmäßigen Buchungen (01.10.2026)
|
||
|
||
- Das Feld „Buchungstext“ fehlte im Formular für neue regelmäßige Buchungen und war in der Liste nur ein verstecktes Feld. Jetzt beim Anlegen vorhanden und in der Liste unter dem Namen bearbeitbar (`termine/index.php`).
|
||
|
||
## Fälligkeit folgt der Buchung (01.10.2026)
|
||
|
||
- Eine per Fälligkeitslauf oder „Buchen“ erzeugte Buchung merkt sich jetzt ihre regelmäßige Buchung und den Soll-Termin (`buchungen.regelmaessige_id`, `faelligkeit`, Migration 023). Wird sie gelöscht, rückt die Fälligkeit auf diesen Termin zurück, eine ausgelaufene Reihe wird wieder aktiv, `zuletzt_am` folgt der jüngsten verbleibenden Buchung (`RegelmaessigeBuchung::terminWiederOeffnen()`). Bereits vorhandene Buchungen sind nicht verknüpft; bei ihnen bleibt es beim bisherigen Verhalten.
|
||
|
||
## Darkmode-Farben nach Mockup (01.10.2026)
|
||
|
||
- Dunkle Palette bläulicher (Navy) statt grau-blau, Seitenleiste, Flächen, Ränder und Akzent angepasst. Der aktive Menüeintrag ist wie im hellen Modus blau hinterlegt (`--navi-aktiv`), der Balken links bleibt weiß (neue Variable `--navi-balken`). Nur Farben, nur im Darkmode (`app.css`).
|
||
- Blaue Schrift (Links, Überschriften, Marken) besser lesbar: im Darkmode heller, im Hellmodus dunkler. Eigene Variable `--akzent-schrift`, Knöpfe und Rahmen bleiben unverändert.
|
||
|
||
## Favicon in hellem und dunklem Modus (01.10.2026)
|
||
|
||
- Das Favicon war ein schwarzes „€“ ohne Hintergrund und im Darkmode kaum zu erkennen. Jetzt weißes „€“ auf blauer Kachel – in beiden Modi gut sichtbar (`kopf.php`, `schlicht.php`).
|
||
|
||
## Offener Vorgang für fehlgeschlagene Bargeld-Tool-Prüfungen (30.09.2026)
|
||
|
||
- Die Kachel „Offene Vorgänge“ im Cockpit zählt jetzt auch Buchungen, deren letzter Abgleich
|
||
mit dem Bargeld-Tool an einem Abrufproblem gescheitert ist (`bargeld_status = 'fehler'`) –
|
||
nicht zu verwechseln mit „im Bargeld-Tool nicht gefunden“. Ein Klick führt auf die neue
|
||
Liste unter Verwaltung → Bargeld-Tool → fehlgeschlagene Prüfungen
|
||
(`/verwaltung/bargeld/fehler`), die diese Buchungen kontoübergreifend mit Konto, Betrag und
|
||
Fehlermeldung auflistet (`Buchung::mitBargeldFehler()`). Fix #102.
|
||
|
||
## phpFinTS auf v4.1.6 (30.09.2026)
|
||
|
||
- Fork neu auf Upstream vom 22.09.2026 aufgebaut (psr/log und Depotaufstellung v6, #598, bleiben
|
||
als eigene Commits). Der VOP-Patch entfällt, #584 ist upstream gemergt. Neu von Upstream u. a.:
|
||
gespeicherte BPD werden beim Laden neu geparst (#595), MT940-Schlusssalden (#593).
|
||
- Abrufe (Umsätze, Depot) und eine Echtzeitüberweisung bei der DKB laufen unverändert durch.
|
||
|
||
## Stichtag beim Einbuchen des Depotbestands (30.09.2026)
|
||
|
||
- „Bestand einbuchen“ (Wertpapiere → Bestand laut Bank) fragt in einem modalen Dialog nach dem
|
||
Stichtag. Er ist das Datum aller Ein- und Auslieferungen des Laufs; vorbelegt ist der Stand des
|
||
Bankabrufs. Die Notiz nennt weiterhin den Bankstand.
|
||
|
||
## Zuordnungsliste: Depots nur noch als Wertpapiergeschäft (30.09.2026)
|
||
|
||
- Wo die Gruppe „Wertpapiergeschäft im Depot“ steht (Kontoblatt, Buchungsmaske), fehlen die Depots
|
||
in der Gruppe „Umbuchung auf ein Konto“. Die fast gleich benannten Einträge führten dazu, dass
|
||
ein Kauf als schlichte Umbuchung ohne Stückzahl, Kurs und Nebenkosten gespeichert wurde. In den
|
||
Splitzeilen bleibt die Umbuchung ins Depot erhalten.
|
||
|
||
## Depotabruf per FinTS und Nachweis zur Überweisung (30.09.2026)
|
||
|
||
- Das Depot (Kontoart 30, ohne IBAN) fehlte in der Kontenabfrage, weil `HKSPA` nur Konten mit
|
||
IBAN liefert. Es steht in den Kontostammdaten der Bank (`HIUPD`) mit erlaubtem `HKWPD`.
|
||
- Neuer Modus `depot` in `FinTsAbruf`: Anmeldung, Depots aus der UPD, je Depot
|
||
`GetDepotAufstellung` (gleiche TAN-Behandlung und Protokollierung wie der Umsatzabruf). Der
|
||
Knopf sitzt unter Wertpapiere → *Bestand laut Bank*, nicht im Umsatzabruf: andere Daten,
|
||
eigene TAN.
|
||
- Das Depot im Finanzmanager wird über die Kontonummer gefunden (Kontenverwaltung).
|
||
`Depotabgleich::uebernehmen()` speichert den Stand des letzten Abrufs
|
||
(`depot_bankbestaende`, Migration 021) und trägt die Bewertungskurse bekannter Wertpapiere
|
||
ein. Gebucht wird nichts; `Depotabgleich::vergleich()` zeigt Abweichungen im Bestand.
|
||
- Nachweis zur Überweisung: `bank_protokolle.pruefsumme` (SHA-256 des Protokolls, beim Speichern
|
||
gebildet). Im Protokoll einer Überweisung lädt *Nachweis herunterladen* eine Textdatei mit
|
||
Auftrag, allen Durchläufen im vollen Wortlaut und den Prüfsummen; ein nachträglich geändertes
|
||
Protokoll fällt dort als ABWEICHUNG auf.
|
||
- phpFinTS auf v4.1.5: Die DKB meldet die Depotaufstellung nur in Version 6 (`HKWPD`/`HIWPD`/`HIWPDS`); Upstream-PR nemiah/phpFinTS#598.
|
||
- Zuordnung des Depots der Bank zu einem Depot im Finanzmanager (Migration 022,
|
||
`bank_konten.art`): Das Depot steht als eigene Zeile bei den Bankkonten des Zugangs und wird beim
|
||
Kontenabruf (ohne zusätzliche TAN) oder beim Depotabruf erkannt. Passt die Kontonummer zu einem
|
||
eigenen Depot, wird gleich zugeordnet; sonst unter Online-Banking → Konten zuordnen (dort auch „neues Konto anlegen“). Der Umsatzabruf sieht nur
|
||
Konten (`Bankzugang::konten()`), Depots über `Bankzugang::depots()`.
|
||
- „Bestand einbuchen“ über der Tabelle unter Wertpapiere → Bestand laut Bank
|
||
(`Depotabgleich::einbuchen()`): Mehrbestand wird als Einlieferung, Minderbestand als Auslieferung gebucht
|
||
(ohne Geldseite), unbekannte Wertpapiere werden angelegt. Kurs der Einlieferung: Einstandskurs der Bank,
|
||
sonst Bewertungskurs.
|
||
- Spalte „Einstandskurs“ (soweit die Bank ihn meldet) in der Tabelle Bestand laut Bank.
|
||
- Test 51.
|
||
|
||
## Buchungsmaske: Splitzeilen bleiben bei falscher Summe erhalten (29.09.2026)
|
||
|
||
- Scheiterte das Speichern einer Buchung an der Summenprüfung der Splitzeilen (Fehlermeldung
|
||
„Die Splitzeilen ergeben …, die Buchung lautet über …“), leitete `BuchungenController` bisher
|
||
mit einer Flash-Meldung auf eine frische Maske weiter – die gerade erfassten Splitzeilen (und
|
||
alle anderen Feldwerte) waren damit weg, obwohl in der Datenbank nichts geändert wurde: Die
|
||
Validierung läuft schon vor der Transaktion, es geht nichts kaputt, nur die Eingabe.
|
||
- `BuchungenController::anlegen()`/`speichern()` zeichnen die Maske jetzt bei einem
|
||
Validierungsfehler direkt neu, mit den zuletzt abgeschickten Werten (`$_POST`) statt der
|
||
gespeicherten – die Fehlermeldung erscheint wie gewohnt über `View::flash()`, die
|
||
eigenen Eingaben bleiben stehen und lassen sich korrigieren. `View::render()` erlaubt dafür
|
||
jetzt, dass ein Aufruf zusätzlich zur vorgemerkten Flash-Meldung eine eigene mitgibt, statt
|
||
sie zu überschreiben.
|
||
- Neu: `tests/e2e/specs/buchungen.spec.js` prüft, dass eine unstimmige Splitsumme die Eingaben
|
||
(Datum, Betrag, Empfänger, beide Splitzeilen) unverändert lässt und sich nach einer Korrektur
|
||
wie gewohnt speichern lässt.
|
||
|
||
## Splitbuchungen: Summenzeile zeigt den Restbetrag, Speichern nur bei Null (29.09.2026)
|
||
|
||
- Die Summenzeile der Splitbuchungen (Buchungsmaske und Splitdialog im Kontoblatt) zeigte
|
||
bisher die Summe der erfassten Zeilen, mit „(Rest X)“ als Zusatz. Jetzt steht dort direkt
|
||
der noch offene Restbetrag zum Buchungswert (`Buchungswert - Summe der Zeilen`) – die
|
||
gemeinsame Rechenfunktion `FmBetrag.splitsumme()` in `betrag.js` liefert ihn beiden Masken.
|
||
- „Speichern“ (bzw. „Splitbuchung speichern“) bleibt gesperrt, solange mindestens eine
|
||
Splitzeile einen Betrag trägt und der Restbetrag nicht Null ist. Ohne Splitzeilen – egal ob
|
||
unbelegt oder mit direkter Kategorie/Gegenkonto zugeordnet – bleibt der Knopf wie bisher an;
|
||
das Model prüft die Summe der Splitzeilen ohnehin schon serverseitig.
|
||
- Der bisherige E2E-Test, der eine unstimmige Splitsumme über eine serverseitige Fehlermeldung
|
||
prüfte, prüft jetzt die neue Sperre im Browser; die serverseitige Prüfung selbst deckt
|
||
weiterhin Test 09 (PHP) ab.
|
||
|
||
## Regel direkt aus einer Buchung anlegen (29.09.2026)
|
||
|
||
- Neuer Toggle „Regel anlegen“ in der Buchungsmaske, standardmäßig aus; das bisherige
|
||
Verhalten bleibt unverändert, solange er aus ist.
|
||
- Ist er beim Speichern an, entsteht aus Empfänger (ersatzweise Buchungstext), Kategorie,
|
||
Klasse, Gegenkonto und Splits der Buchung eine neue Regel (`AbgleichRegel::ausBuchung()`).
|
||
Ohne Empfänger und Buchungstext gibt es kein sinnvolles Muster – dann bleibt es beim
|
||
gewöhnlichen Speichern ohne Regel.
|
||
- Das Kontoblatt zeigt die neue Regel danach in einem modalen Dialog (`regeldialog.js`,
|
||
natives `<dialog>` wie beim Splitdialog) zum Prüfen und Anpassen – mit denselben Feldern
|
||
wie „Neue Regel“ in den Bankregeln (ausgelagert nach `regelfelder.php`).
|
||
- „Abbruch“ löscht die gerade erst angelegte Regel wieder (über das bestehende
|
||
Lösch-Formular der Regelliste, nur mit Rücksprung zum Kontoblatt statt zur Regelliste);
|
||
die Buchung bleibt gespeichert. Die Escape-Taste verhält sich genauso, statt den Dialog
|
||
nur zu schließen und eine verwaiste Regel zurückzulassen.
|
||
- „Speichern“ übernimmt Änderungen und lässt die Regel in der Liste stehen.
|
||
- Neu: Test 51 (PHP) und `tests/e2e/specs/regelausbuchung.spec.js`.
|
||
|
||
## Umbuchungen: beide Seiten finden zueinander (29.09.2026)
|
||
|
||
- Eine Umbuchung erscheint auf zwei Kontoauszügen, wurde aber nicht als eine Buchung erkannt:
|
||
Der zweite Abruf legte die Gegenseite als eigene Buchung an, der Betrag stand doppelt im
|
||
Kontoblatt. Jetzt gilt (`Bankumsatz::gegenbuchung()`): anderes Konto, Betrag mit umgekehrtem
|
||
Vorzeichen, Buchungstag höchstens `banking_abgleich_tage` entfernt, keine Kategorie, keine
|
||
Splitbuchung, genau ein Umsatz – und die IBANs dürfen sich nicht widersprechen (Gegen-IBAN
|
||
muss eine IBAN des anderen Kontos sein). Unter mehreren Kandidaten gewinnt der zeitlich
|
||
nächste.
|
||
- Konten ohne IBAN (PayPal): Sind die IBANs nicht zu prüfen, wird nur bei einem eindeutigen
|
||
Treffer automatisch verbunden; bei mehreren bleibt es ein Vorschlag in der Liste.
|
||
- Fall 1: Der zweite Umsatz wird mit der Buchung der Gegenseite verbunden; sie bekommt das
|
||
Gegenkonto und wird zur Umbuchung (`Bankumsatz::verbinden()`).
|
||
- Fall 2: Haben beide Seiten schon eine eigene Buchung, führt der Abgleich sie zusammen
|
||
(`Bankumsatz::umbuchungenZusammenfuehren()`, am Ende von `Bankabgleich::laufenLassen()`).
|
||
Die ältere Buchung wird zur Umbuchung, die jüngere fällt weg. Nur Buchungen ohne Kategorie
|
||
und Klasse; ein abgeschlossener Monat landet im Bericht.
|
||
- Auch beim Zuordnen: Wählt man in der Schnellzuordnung oder im Buchungsformular ein
|
||
Gegenkonto, oder setzt eine Regel beim Übernehmen eines Umsatzes eines, sucht das Programm
|
||
die Gegenseite (`Bankumsatz::gegenseiteZusammenfuehren()`). Sie darf eine einzeln gebuchte
|
||
Buchung (ohne Kategorie und Klasse) oder eine Umbuchung in Gegenrichtung sein; beide werden
|
||
zusammengelegt, der Umsatz wandert an die verbleibende Buchung. Bei mehreren Kandidaten
|
||
gewinnen bestätigte IBANs, dann der zeitlich nächste.
|
||
- `passendeBuchung()` lässt je Kontoseite einen Umsatz zu.
|
||
- Neu: Test 50; Test 16 erweitert.
|
||
|
||
## Bekannte Umsätze bekommen IBAN und Bank der Gegenseite nachgetragen (29.09.2026)
|
||
|
||
- Der Tooltip im Kontoblatt blieb bei älteren Umsätzen leer: IBAN und Bank der Gegenseite
|
||
werden erst seit dem 28.09. gespeichert, und ein erneuter Abruf übersprang bekannte Umsätze
|
||
ganz. Jetzt trägt `Bankumsatz::anlegen()` bei einem bekannten Umsatz nach, was ihm fehlt
|
||
(`Bankumsatz::ergaenzen()`), und gibt es an die verbundene Buchung weiter. Nur leere Felder
|
||
werden gefüllt, vorhandene bleiben.
|
||
- Auch `Bankumsatz::verbinden()` überträgt die Angaben auf eine von Hand erfasste Buchung.
|
||
- Neu: Test 49.
|
||
|
||
## E2E-Läufe lassen die Einstellungen unverändert (29.09.2026)
|
||
|
||
- Die Browser-Tests laufen gegen die echte Instanz; der Bargeld-Test hatte dabei die Adresse
|
||
des Bargeld-Tools geleert. `tests/e2e/run.sh` sichert jetzt die ganze Tabelle
|
||
`einstellungen` vor dem Lauf und stellt sie danach wieder her (auch bei Abbruch mit
|
||
Strg+C). Eine nach hartem Abbruch liegengebliebene Sicherung wird vor dem nächsten Lauf
|
||
eingespielt.
|
||
- Neu: `Services/E2EEinstellungen`, `scripts/e2e-einstellungen.php sichern|wiederherstellen`,
|
||
Test 48. Die Sicherung liegt unter `data/e2e/` (nicht im Repository).
|
||
|
||
## Kippschalter statt Ankreuzfeldern (29.09.2026)
|
||
|
||
- Alle Ankreuzfelder erscheinen als Kippschalter im blauen Design des Projekts: aus =
|
||
graue Fläche mit ✕, an = Akzentfarbe mit Häkchen (helles und dunkles Thema). Reine CSS-
|
||
Änderung in `public/static/app.css`; die Elemente bleiben echte Ankreuzfelder.
|
||
- Ausgenommen ist die Zeilenauswahl in den Listen (Kontoblatt, Depotbuchungen,
|
||
Umsätze inkl. „alle wählen“).
|
||
|
||
## Regeln: Summe der Anteile in den Splittabellen (29.09.2026)
|
||
|
||
- Die Spalte „Anteil %“ heißt in den Splittabellen der Regeln nur noch „Anteil“, da die
|
||
Anteile zueinander gelten und keine Prozentwerte sein müssen.
|
||
- Unter den Anteilen steht eine Summenzeile, die sich mit jeder Eingabe in derselben
|
||
Tabelle neu berechnet (`public/static/app.js`).
|
||
|
||
## Zahlungsverkehr: Echtzeitüberweisung scheiterte am SEPA-Format der Bank (28.09.2026)
|
||
|
||
Eine Echtzeitüberweisung schlug mit „Für Echtzeitüberweisungen nimmt die Bank nur
|
||
SEPA-Formate an, die der Finanzmanager nicht erzeugt: …pain.001.001.09…“ fehl; ohne das
|
||
Echtzeit-Häkchen kam stattdessen die rohe Bibliotheksmeldung „Combined VOP match
|
||
confirmation and TAN request“ (#89). Zwei unabhängige Ursachen:
|
||
|
||
- `SepaXml` erzeugte ausschließlich pain.001.001.03 und das DK-Format pain.001.003.03.
|
||
Banken, die inzwischen nur noch das neuere ISO-Format **pain.001.001.09** führen, wiesen
|
||
jede Überweisung schon vor der TAN ab. `SepaXml` erzeugt es jetzt mit: Der
|
||
Ausführungstermin steht in einem eigenen `Dt`-Element (`ReqdExctnDt/Dt`), die
|
||
Bankkennung heißt `BICFI` statt `BIC`, und jede Zahlung trägt eine `UETR` (eine UUID, die
|
||
das SEPA-Instant-Regelwerk seit November 2019 verlangt und die für eine
|
||
Echtzeitüberweisung ohnehin nötig ist). `SepaXml::schemaWaehlen()` bevorzugt es, wenn die
|
||
Bank es meldet, vor dem älteren pain.001.001.03.
|
||
- Bestätigt die Bank den Namensabgleich mit einem Volltreffer und verlangt im selben
|
||
Antwortsatz schon die TAN, kennt `nemiah/php-fints` (Stand v4.1.4) diese Kombination noch
|
||
nicht und wirft dafür eine rohe `UnsupportedException` – obwohl der Vorgang beide
|
||
Angaben zu diesem Zeitpunkt schon erhalten hat. `FinTsUeberweisung` fängt genau diese
|
||
Ausnahme jetzt ab und lässt den ohnehin schon vorhandenen automatischen
|
||
Volltreffer-Namensabgleich (siehe unten) den Rest erledigen, statt den Auftrag mit der
|
||
englischen Bibliotheksmeldung scheitern zu lassen.
|
||
|
||
Erweiterte Tests in `tests/39_SepaSchemaTest.php` und `tests/43_EchtzeitueberweisungTest.php`
|
||
für das neue Format, neuer Test in `tests/40_NamensabgleichTest.php` für die abgefangene
|
||
Ausnahme.
|
||
|
||
## Buchungsmaske: Splitbuchung und direkte Zuordnung sperren sich gegenseitig (28.09.2026)
|
||
|
||
Splitzeilen und die Felder *Kategorie / Gegenkonto* und *Klasse* ließen sich in der
|
||
Buchungsmaske gleichzeitig ausfüllen (#77).
|
||
|
||
- Sobald in einer Splitzeile etwas eingetragen ist, sind *Kategorie / Gegenkonto* und
|
||
*Klasse* sofort gesperrt; eine Kategorie oder Klasse sperrt umgekehrt alle Splitzeilen.
|
||
- Neuer Knopf *Splitbuchungen löschen* rechts über der Splittabelle: leert alle
|
||
Splitzeilen und gibt Kategorie und Klasse wieder frei.
|
||
- E2E-Prüfung in `buchungen.spec.js`.
|
||
|
||
## Kontoblatt: Klassen der Splitzeilen in der Klassenspalte (28.09.2026)
|
||
|
||
Die Klassenspalte des Kontoblatts zeigte bei einer Splitbuchung nur die Klasse der Buchung
|
||
selbst, nicht die ihrer Splitzeilen (#76).
|
||
|
||
- Neu `Buchung::splitKlasse()`: Tragen alle Splitzeilen dieselbe Klasse, steht sie in der
|
||
Spalte. Unterscheiden sie sich, steht dort die Marke *Splitbuchung*, der Tooltip nennt je
|
||
Splitzeile Kategorie und Klasse. Ohne Klasse in den Zeilen bleibt es bei der Klasse der
|
||
Buchung.
|
||
- Ein Klick auf die Klassenzelle einer Splitbuchung öffnet den Splitdialog, in dem die
|
||
Klassen der Zeilen geändert werden.
|
||
- Neuer Test `tests/47_SplitKlasseTest.php`, E2E-Prüfung in `buchungen.spec.js`.
|
||
|
||
## Online-Banking: Alle Umsätze abrufen (28.09.2026)
|
||
|
||
Über der Tabelle der Bankverbindungen steht ein Knopf *Alle Umsätze abrufen*, der jede
|
||
aktive Bankverbindung der Reihe nach einzeln abruft (#75).
|
||
|
||
- Neuer Dienst `Services\Sammelabruf` führt die Warteschlange; sie liegt während einer
|
||
TAN-Runde in der Sitzung, nach der Freigabe geht es mit der nächsten Bank weiter.
|
||
- Die TAN-Maske zeigt im Sammelabruf, welche Bankverbindung gerade fragt, und bietet
|
||
*Diese Bank überspringen* neben *Abruf abbrechen*.
|
||
- Ein Fehler bei einer Bank hält die übrigen nicht auf; am Ende steht je Bankverbindung
|
||
eine eigene Meldung. Dafür neu: `View::flash()` für mehrere Meldungen auf einmal.
|
||
- Neuer Test `tests/46_SammelabrufTest.php`, E2E-Prüfung in `banking.spec.js`.
|
||
|
||
## Einnahme/Ausgabe entscheidet der Geldfluss, nicht die Kategorie-Art (28.09.2026)
|
||
|
||
Cockpit und Berichte zählten eine Bewegung nach der Art ihrer Kategorie zu Einnahmen
|
||
oder Ausgaben – nicht nach ihrem Vorzeichen. Eine Kategorie wie eine Rückstellung, die
|
||
in beide Richtungen bebucht wird (z. B. „010 - Rückst. USt“, angelegt als Einnahme),
|
||
ließ dadurch auch negative Beträge in den Einnahmen auftauchen (n.n., Buchung 29285).
|
||
|
||
- `Auswertung::artDerBewegung()` entscheidet jetzt allein über das Vorzeichen des
|
||
Betrags. Die zugewiesene Kategorie-Art spielt für die Auswertung keine Rolle mehr –
|
||
sie bleibt reine Verwaltungs-Metadaten (Reiter in der Kategorienverwaltung,
|
||
Vorzeichen im Budgetplaner).
|
||
- `Auswertung::kategoriezeilen()` baut das Zeilengerüst von Summen- und
|
||
Vergleichsbericht jetzt aus allen Kategorien statt nur denen der passenden Art –
|
||
sonst wäre eine Bewegung wie oben zwar in der Abschnittssumme, aber in keiner Zeile
|
||
aufgetaucht.
|
||
- `Auswertung::budgetbericht()` summiert den Ist-Wert einer Kategorie jetzt aus beiden
|
||
Töpfen (Einnahme und Ausgabe), da eine Kategorie nun in beiden auftauchen kann.
|
||
- Betroffen waren dadurch alle Cockpit-Kacheln, die auf dem Summenbericht aufsetzen
|
||
(*Einnahmen und Ausgaben*, *Meine Ausgaben*) sowie deren Legenden-Drilldown,
|
||
Summen-, Vergleichs- und Budgetbericht unter *Auswertungen*.
|
||
- Neuer Test `summenbericht_geldflussEntscheidet()` in `tests/14_AuswertungTest.php`
|
||
mit einer Kategorie, die in beide Richtungen bebucht wird.
|
||
|
||
## Cockpit: Legendeneinträge öffnen die Buchungen dahinter (28.09.2026)
|
||
|
||
In allen Cockpit-Kacheln mit Legende führt ein Klick auf einen Legendeneintrag zu den
|
||
Buchungen, aus denen sich der angezeigte Wert zusammensetzt (#84).
|
||
|
||
- *Meine Ausgaben*: jede Kategorie, „Nicht kategorisiert“ und „Übrige (n)“ öffnen die
|
||
Ausgaben des laufenden Jahres dieser Kategorien (samt Unterkategorien).
|
||
- *Einnahmen und Ausgaben*: „Einnahmen“, „Ausgaben“ und „Saldo“ öffnen die Buchungen der
|
||
letzten zwölf Monate.
|
||
- *Vermögensentwicklung*: ein Kontoname öffnet das Kontoblatt.
|
||
- Ziel ist der bestehende Drilldown der Auswertungen mit dem Filter der Kachel;
|
||
`Auswertung::zeilenbuchungen()` kennt dafür neu einen Art-Filter (Einnahme/Ausgabe),
|
||
„alle Bewegungen“ und mehrere Kategorien auf einmal. Aus dem Cockpit geht es zurück ins
|
||
Cockpit. Die Summe der Liste entspricht dem Legendenwert.
|
||
- Tests in `tests/15_CockpitTest.php` und `tests/e2e/specs/module.spec.js`.
|
||
|
||
## IBAN und Bank der Gegenseite im Kontoblatt (27.09.2026)
|
||
|
||
Bei einer Buchung aus dem Bankabruf ließ sich oft nicht mehr erkennen, woher sie
|
||
stammt – der Empfängername allein reicht nicht immer (z. B. bei Überweisungen
|
||
zwischen eigenen Konten verschiedener Banken).
|
||
|
||
- `Services\FinTsAbruf` löst IBAN/Kontonummer und Bankleitzahl bzw. BIC der Gegenseite
|
||
schon beim Abruf gegen das Bankleitzahlen-Verzeichnis auf (neu:
|
||
`Bankinstitut::findeNachBic()` / `findeNachBicOderBlz()`) und schreibt beides nach
|
||
`bank_umsaetze.gegen_iban` / `gegen_bankname`.
|
||
- `Bankumsatz::uebernehmen()` trägt beides auf die neue Buchung ein (`buchungen.iban`,
|
||
`buchungen.bankname`), Migration `020_buchung_herkunft.sql`.
|
||
- Im Kontoblatt zeigt der Verwendungszweck beim Mouseover IBAN und Bank, sofern
|
||
bekannt (`kontoblatt.js`, `data-herkunft`).
|
||
- Gilt nur für künftige Bankabrufe – bereits vorhandene Buchungen aus früheren
|
||
Abrufen (auch die den Anstoß gebende Buchung 31178) kennen die Gegenseite nicht
|
||
nachträglich, weil sie beim damaligen Abruf nicht mitgeschrieben wurde.
|
||
- Test in `tests/45_BuchungHerkunftTest.php`.
|
||
|
||
## Umbuchung wird auch von der Gegenkontoseite erkannt (27.09.2026)
|
||
|
||
Eine Umbuchung gehört ihrem Ursprungskonto; der automatische Abgleich suchte eine
|
||
passende Buchung bisher nur über deren `konto_id`. Kam der Kontoauszug der
|
||
Empfängerseite mit ein paar Tagen Verzug herein, fand er die schon gebuchte Umbuchung
|
||
nie und legte sie als eigenständige Buchung an – die Umbuchung stand danach doppelt im
|
||
Kontoblatt und verfälschte den Saldo.
|
||
|
||
- `Bankumsatz::passendeBuchung()` berücksichtigt jetzt auch die Gegenkonto-Seite einer
|
||
Umbuchung, mit umgekehrtem Vorzeichen wie im Kontoblatt auch.
|
||
- Test in `tests/16_BankingTest.php`
|
||
(`umbuchungWirdAuchVonDerGegenkontoseiteErkannt`).
|
||
|
||
## Buchungen im Kontoblatt verknüpfen (27.09.2026)
|
||
|
||
Liegen zwei Buchungen, die eigentlich derselbe Vorgang sind, zu weit auseinander (oder
|
||
gehörte eine ursprünglich einem anderen Ursprungskonto), findet der automatische
|
||
Abgleich sie nicht – die Umbuchung und ihre einzeln erfasste Dublette stehen dann
|
||
dauerhaft nebeneinander im Kontoblatt.
|
||
|
||
- Neu: `Buchung::verknuepfen()` führt eine Umbuchung (eigene Seite oder Gegenseite auf
|
||
diesem Konto) und eine einzeln erfasste Buchung mit passendem Betrag zusammen – die
|
||
Umbuchung bleibt, die Dublette fällt weg, ihr Bankumsatz hängt sich an die Umbuchung.
|
||
- Im Kontoblatt lässt sich dafür auch die Gegenseite einer fremden Umbuchung ankreuzen;
|
||
der Knopf „Verknüpfen“ neben „Auswahl löschen“ nutzt dieselbe Auswahl.
|
||
- Test in `tests/44_BuchungenVerknuepfenTest.php`.
|
||
|
||
## Buchungsbericht: Splitbuchung nach Konto/Empfänger zusammengefasst (27.09.2026)
|
||
|
||
Nach Kategorie oder Klasse gruppiert erscheint eine Splitbuchung weiterhin mit jeder
|
||
ihrer Zeilen einzeln – nur so stimmen die Gruppensummen mit dem Summenbericht überein.
|
||
Nach Konto oder Empfänger gruppiert landen aber ohnehin alle Zeilen einer Splitbuchung
|
||
in derselben Gruppe; dort erschien bisher trotzdem jede Zeile für sich, mit ihrem
|
||
Teilbetrag statt dem Gesamtbetrag – wer nur eine der beiden Zeilen sah, hielt das für
|
||
den kompletten Betrag der Buchung.
|
||
|
||
- `Auswertung::splitsZusammenfassen()` fasst bei Gruppierung nach Konto oder Empfänger
|
||
alle Splitzeilen einer Buchung zu einer Zeile mit dem Gesamtbetrag zusammen, analog
|
||
zum Kontoblatt. Die Zeile führt keine einzelne Kategorie, sondern das Badge
|
||
„Splitbuchung“; die Aufschlüsselung bleibt dem Kontoblatt vorbehalten.
|
||
- Bei Kategorie oder Klasse bleibt es beim bisherigen Verhalten.
|
||
- Test in `tests/14_AuswertungTest.php` (`buchungsbericht`).
|
||
|
||
## Regeln lassen sich auf Buchungen ohne Kategorie anwenden (27.09.2026)
|
||
|
||
Der Knopf unter Verwaltung → Regeln hieß „Gegen offene Umsätze prüfen“ und zählte nur,
|
||
wie viele noch nicht abgeglichene Umsätze eine Regel träfe – das beantwortet weder, wo im
|
||
Kontoblatt schon Lücken liegen, noch schließt es sie.
|
||
|
||
- Neu: `Buchung::ohneKategorie()` liefert Buchungen ganz ohne Kategorie – weder direkt
|
||
(`kategorie_id`) noch in einer Splitzeile –, ausgenommen Umbuchungen (die haben per
|
||
Definition ein Gegenkonto statt einer Kategorie).
|
||
- Neu: `Bankabgleich::regelnAufBuchungenOhneKategorieAnwenden()` trägt bei jeder Buchung
|
||
ohne Kategorie die erste passende Regel nach (Kategorie, Klasse, Gegenkonto oder
|
||
Split) – ohne Treffer bleibt die Buchung unverändert, ein abgeschlossener Monat oder
|
||
sonstiger Fehler betrifft nur die eine Buchung und bricht den Lauf nicht ab.
|
||
- Der Knopf „Regeln auf Buchungen ohne Kategorie anwenden“ (mit Rückfrage) ersetzt den
|
||
reinen Testlauf; die Regel-Trefferzähler zählen mit.
|
||
- Test in `tests/16_BankingTest.php` (`buchungOhneKategorieWirdGefunden`,
|
||
`regelnKategorisierenBuchungenOhneKategorie`).
|
||
|
||
## `update.sh` zieht aus dem Fork (25.09.2026)
|
||
|
||
- Das Update-Skript holt den Code ausdrücklich per `git pull --ff-only origin main`. Vorher
|
||
brach es ohne Tracking-Branch mit „Es gibt keine Tracking-Informationen“ ab.
|
||
- Quelle ist der Fork, weil es derzeit nur die Entwicklungsumgebung gibt; der Upstream ist
|
||
Backup und Veröffentlichung. Für eine spätere Produktion: `UPDATE_REMOTE=upstream`.
|
||
- Das Skript bricht bei unverändertem Code nicht mehr ab: Container starten und Migrationen
|
||
laufen immer, damit ein von Hand gezogener Stand nicht ohne Migration bleibt.
|
||
- Der Pull läuft mit HTTP/1.1 (Codeberg liefert bei HTTP/2 sonst 504).
|
||
|
||
## Echtzeitüberweisung im Zahlungsverkehr (25.09.2026)
|
||
|
||
Der Überweisungsdialog bietet jetzt das Häkchen **Echtzeitüberweisung**: Das Geld ist binnen
|
||
Sekunden beim Empfänger. SEPA behandelt die Echtzeitüberweisung als eigenes Verfahren
|
||
(SCT Inst), der Finanzmanager deshalb auch.
|
||
|
||
- Über FinTS geht der Auftrag als Geschäftsvorfall HKIPZ hinaus (`SendSEPARealtimeTransfer`
|
||
aus php-fints) statt als HKCCS. In der pain.001-Nachricht steht die Zahlungsart
|
||
`LclInstrm/Cd = INST`; das gibt es nur im ISO-Format `pain.001.001.03`.
|
||
- Eine Echtzeitüberweisung hat keinen Ausführungstermin: Der Dialog sperrt das Feld, ein
|
||
Termin in der Zukunft wird beim Speichern abgewiesen.
|
||
- Bietet die Bank keine Echtzeitüberweisung an, scheitert der Auftrag mit einer klaren
|
||
Meldung und bleibt stehen. Eine stille Umwandlung in eine gewöhnliche Überweisung durch
|
||
die Bank ist abgeschaltet.
|
||
- Die Übersicht kennzeichnet Echtzeitüberweisungen in der Spalte „Ausführung“, das
|
||
Übermittlungsprotokoll nennt sie ausdrücklich.
|
||
- Neue Spalte `zahlungsauftraege.echtzeit` (Migration `019`).
|
||
- Test 43 (`tests/43_EchtzeitueberweisungTest.php`) und ein E2E-Test für das Sperren des
|
||
Termins.
|
||
|
||
## Übermittlungsprotokoll für Überweisungen und Bankkommunikation (21.09.2026)
|
||
|
||
Als Nachweis gegenüber der Bank, dass eine Überweisung fristgerecht und korrekt übermittelt
|
||
wurde, speichert der Finanzmanager jetzt den gesamten Dialog – gesendete und empfangene
|
||
Meldungen im Wortlaut, im Erfolgs- wie im Fehlerfall. PIN und Benutzerkennung sind maskiert
|
||
(das erledigt die FinTS-Bibliothek, bevor die Zeilen ankommen).
|
||
|
||
- **Zahlungsverkehr:** Ein Klick auf den Status einer Überweisung öffnet ihr
|
||
Übermittlungsprotokoll in einem modalen Dialog. Eine Überweisung mit TAN-Freigabe hat
|
||
mehrere Durchläufe; alle stehen darin, der älteste zuerst. Ohne JavaScript führt der
|
||
Status auf dieselbe Ansicht als eigene Seite.
|
||
- **Verwaltung › Online-Banking › Protokoll** (`/banking/protokoll`): das Gesamtprotokoll
|
||
über jede Kommunikation mit den Banken – Überweisungen, Umsatzabrufe und die Abfrage der
|
||
TAN-Verfahren. Filterbar nach Bank, Zeitraum und Freitext.
|
||
- Neue Tabelle `bank_protokolle` (Migration `018`), Modell `Bankprotokoll`, Mitschnitt über
|
||
`Services\BankProtokollLogger`. Das Protokoll bleibt stehen, auch wenn die Überweisung
|
||
gelöscht wird; es wird nicht automatisch aufgeräumt.
|
||
- Test 42 (`tests/42_UebermittlungsprotokollTest.php`).
|
||
|
||
## Letzter roter E2E-Test aus dem Typeahead-Umbau behoben (04.09.2026)
|
||
|
||
Ein Test blieb nach der Anpassung an den Typeahead (siehe unten) noch rot:
|
||
„Bargeld-Tool › Eine vorgemerkte Kategorie wird beim Zuordnen geprüft“ – und zwar
|
||
zuverlässig, nicht nur gelegentlich. Ursache war kein Rest des Umbaus mehr, sondern ein zu
|
||
knapper Timeout: Die Schnellzuordnung im Kontoblatt prüft dabei gegen eine absichtlich
|
||
ungültige Adresse (`kasse.beispiel.invalid`), und bis der serverseitige Abruf daran
|
||
scheitert, vergehen durch `BargeldTool::ZEITGRENZE` (8 s Verbindungsaufbau) plus die
|
||
Namensauflösung der ungültigen Adresse über 15 Sekunden – mehr, als Playwrights
|
||
Standard-Timeout von 5 s für `toContainText()` hergibt. Die betroffenen Erwartungen tragen
|
||
jetzt einen eigenen Timeout von 25 s, der Test insgesamt 90 s statt 60 s.
|
||
|
||
## E2E-Datenbestand wird vor jedem Lauf aufgeräumt (04.09.2026)
|
||
|
||
Die Browser-Tests legten ihre Konten, Kategorien und Buchungen bei jedem Lauf neu an,
|
||
weggeräumt hat sie niemand: Zuletzt standen über dreitausend Zeilen Testdaten in der
|
||
Datenbank – vierzig „E2E Bankprovision …“ hintereinander im Kategoriefilter des
|
||
Kontoblatts, und kein Bildschirmfoto eines fehlgeschlagenen Tests war mehr zu gebrauchen.
|
||
|
||
`scripts/e2e-aufraeumen.php` räumt den Bestand weg; `tests/e2e/run.sh` ruft es vor jedem
|
||
Lauf auf. Vor dem Lauf und nicht danach, weil ein abgebrochener Lauf sonst einen Bestand
|
||
hinterlässt, an den niemand mehr denkt – und weil die Daten des letzten Laufs so bis zum
|
||
nächsten für die Fehlersuche stehen bleiben. `E2E_KEIN_AUFRAEUMEN=1` lässt sie liegen,
|
||
`--probe` zählt nur, was wegfiele.
|
||
|
||
Gelöscht wird, was am Testnutzer hängt – nicht, was nach Testdaten aussieht. Ein
|
||
Namensmuster übersähe die Buchung „Supermarkt“ und träfe irgendwann etwas Echtes; die
|
||
Fremdschlüssel dagegen sagen genau, was zu diesem Nutzer gehört, und nehmen über ihre
|
||
Kaskaden auch die Zeilen ohne eigene `benutzer_id` mit (Splitzeilen, Budgetpositionen,
|
||
Bankkonten, Kurse). Der Nutzer selbst bleibt stehen, sonst käme der nächste Lauf nicht
|
||
durch die Anmeldung.
|
||
|
||
Als Schranke gegen den Fehlgriff muss er den Anzeigenamen tragen, den
|
||
`scripts/e2e-benutzer.php` setzt: Wer `E2E_USER` versehentlich auf seinen eigenen Zugang
|
||
stellt, bekommt einen Abbruch statt eines leeren Datenbestands.
|
||
|
||
Das Aufräumen deckte auf, dass sieben Tests auf den Resten früherer Läufe standen: Die
|
||
Auswertungen prüfen, dass ein Bericht eine Tabelle statt eines Platzhalters zeigt, das
|
||
Cockpit, dass „Übrige Konten“ eine Ebene tiefer führt – Daten dafür legte keiner von ihnen
|
||
an. Auf einer frisch aufgeräumten Instanz fielen sie aus; vorher hing ihr Ergebnis davon
|
||
ab, wer zuletzt was hatte laufen lassen. `scripts/e2e-grundbestand.php` stellt deshalb nach
|
||
dem Aufräumen einen festgelegten Bestand her, dessen Zuschnitte an genau diesen Tests
|
||
hängen: mehr Konten als `Vermoegensentwicklung::SERIEN`, Buchungen in jedem Monat des
|
||
laufenden Jahres, ein Vorjahr für den Vergleichsbericht, ein Depot mit Wertpapiergeschäft.
|
||
Die Suite prüft damit dasselbe wie vorher, aber auf einem Bestand, den sie kennt.
|
||
|
||
## E2E-Tests an den Typeahead im Kontoblatt angepasst (04.09.2026)
|
||
|
||
Zwölf E2E-Tests standen seit dem Typeahead-Umbau (Fix #41) auf Rot – nicht, weil die
|
||
Anwendung etwas falsch machte, sondern weil die Specs den alten Stand prüften. Vier
|
||
Ursachen lagen darunter:
|
||
|
||
- **Die Kombobox versteckt ihr `<select>`.** `kombobox.js` lässt das ursprüngliche Feld
|
||
zwar im Formular stehen, setzt es aber auf `hidden`; `selectOption()` wartet darauf
|
||
vergeblich, bis der Test nach 30 Sekunden abbricht. Der neue Helfer
|
||
`tests/e2e/specs/hilfen.js` bedient stattdessen das Eingabefeld: Er tippt die
|
||
Beschriftung ein – das ist zugleich die Probe auf die Filterung – und klickt den genau
|
||
passenden Treffer an. Exakt muss der Vergleich sein, weil `[Depot]` sonst auch das
|
||
`[Depot] …` des Wertpapiergeschäfts träfe.
|
||
- **Zwei Erfolgsmeldungen übereinander.** Seit dem Schnellspeichern ohne Neuladen
|
||
(Fix #40) bleibt die Meldung des Anlegens stehen und die neue kommt darunter. Die Tests
|
||
meinen die neue und greifen jetzt zu `.last()`.
|
||
- **Die Adressliste ist eine Bearbeitungstabelle.** Seit Fix #51 stehen die Namen in
|
||
Eingabefeldern, wo `toContainText` nicht hinsieht. Geprüft wird nun der Feldwert – die
|
||
Adresse selbst wurde die ganze Zeit korrekt gespeichert.
|
||
- **Zwei Folgefehler.** Playwright startet nach jedem Fehlschlag einen frischen Worker;
|
||
damit wird das Spec-Modul neu geladen, seine Marke neu gezogen und `beforeAll` legt neue,
|
||
leere Konten an. Die Tests „Kontoblatt lässt sich durchsuchen“ und „Löschen fragt nach“
|
||
suchten deshalb eine Buchung, die auf dem Konto des vorigen Workers lag. Sie brauchten
|
||
keine Änderung.
|
||
|
||
Die Suite läuft wieder vollständig durch: 87 Tests grün, und ohne die Timeouts in 2,7
|
||
statt 7,1 Minuten.
|
||
|
||
## FinTS-Bibliothek auf den aktuellen Upstream-Stand gehoben (04.09.2026)
|
||
|
||
Der Fork `nemiah/php-fints` steht jetzt auf `v4.1.4` und damit auf dem Upstream-Stand vom
|
||
03.09.2026. Der eigene Patch für den beim Close Match gemeldeten Kontoinhaber
|
||
(`getDifferingPayeeName()`) ist dort **gemergt** und entfällt als eigener Commit; übrig
|
||
bleiben im Fork nur noch das eingebaute psr/log und der VOP-Stepwise-Patch, der als
|
||
[PR #584](https://github.com/nemiah/phpFinTS/pull/584) offen ist. Dieser wurde auf den neuen
|
||
Upstream-Stand rebased – der Konflikt entstand allein daraus, dass beide Patches ihre Tests
|
||
an dieselbe Stelle der Testdatei setzten.
|
||
|
||
Mitgenommen sind damit die fremden Verbesserungen seit Juli: Typsicherheit in den
|
||
Action-Klassen, vollständiges Auslesen mehrzeiliger Verwendungszwecke aus camt, die
|
||
Gegenparteien aus camt, Kreditkartenumsätze der DKB sowie ein behobener Typfehler beim
|
||
Lesen der minimalen Vorlaufzeit für SEPA-Lastschriften.
|
||
|
||
## Zahlungsverkehr: Auftragskonto über die Kontenzuordnung finden (01.09.2026)
|
||
|
||
Beim Senden suchte `FinTsUeberweisung` unter den Konten, die die Bank meldet, nach der IBAN
|
||
aus dem Konto selbst. Die ist in der Kontenverwaltung aber als optional ausgewiesen und
|
||
bleibt meist leer – dann scheiterte die Überweisung an „Die Bank führt kein Konto mit der
|
||
IBAN des Auftragskontos“, obwohl der Umsatzabruf für dasselbe Konto längst lief.
|
||
|
||
Maßgeblich ist jetzt dieselbe Zuordnung wie beim Abruf: die Zeile in `bank_konten`, die der
|
||
Nutzer dem Konto zugewiesen hat. Dort steht die IBAN, die die Bank selbst gemeldet hat, und
|
||
die Kontonummer für die Häuser, die keine IBAN liefern. Der bisherige Weg bleibt bestehen:
|
||
Wer die IBAN am Konto gepflegt hat, kommt auch ohne Zuordnung zum Ziel, und führt die Bank
|
||
nur ein einziges Konto, ist die Sache ohnehin eindeutig.
|
||
|
||
## Zahlungsverkehr: Namensabgleich vor der Überweisung (01.09.2026)
|
||
|
||
Seit Oktober 2025 gleicht die Bank vor der Ausführung den Empfängernamen mit dem
|
||
Kontoinhaber ab (Verification of Payee). Der Finanzmanager kannte diese Stufe nicht, die
|
||
Überweisung blieb stehen.
|
||
|
||
`FinTsUeberweisung` behandelt den Ablauf jetzt vollständig: Auf das Ergebnis wird gewartet,
|
||
solange die Bank es braucht; **stimmt der Name überein, bestätigt der Finanzmanager selbst**
|
||
und geht direkt zur TAN. Weicht der Name ab, ist er nur nahezu gleich oder konnte die Bank
|
||
nicht prüfen, kommt die neue Maske `zahlungsverkehr/vop.php` – mit dem Ergebnis in einem
|
||
deutschen Satz, dem Aufklärungstext der Bank als Klartext (ihr HTML wird abgeräumt) und der
|
||
Wahl zwischen *Trotzdem senden* und *Abbrechen*. Beim Abbrechen bleibt der Auftrag Entwurf.
|
||
|
||
Stimmt der Name nur **nahezu** überein, zeigt die Maske neben dem angegebenen Empfänger
|
||
auch den bei der Bank hinterlegten Kontoinhaber – die Angabe, die die Rückfrage überhaupt
|
||
beantwortbar macht. Weicht der Name ganz ab, nennt die Bank ihn nicht; das steht dann als
|
||
Erklärung in der Maske.
|
||
|
||
Dazu gehören zwei Eingriffe im FinTS-Fork (`v4.1.3`): Meldet eine Bank die schrittweise
|
||
Lieferung des Prüfberichts (`Art der Lieferung Payment Status Report` = `S`, etwa
|
||
comdirect), lehnte php-fints jede Überweisung ab. Der Parameter beschreibt laut
|
||
Spezifikation aber nur, wie die Bank den Bericht aufteilt, *wenn* sie ihn über mehrere
|
||
Runden liefert – bei einer Einzelüberweisung passiert das nie. Der Fork prüft ihn deshalb
|
||
erst, wenn tatsächlich eine Teillieferung eintrifft. Und der beim Close Match gemeldete
|
||
Kontoinhaber wurde zwar eingelesen, aber nicht an die Anwendung durchgereicht; dafür gibt
|
||
es jetzt `VopConfirmationRequest::getDifferingPayeeName()`. Beide Commits liegen zusätzlich
|
||
auf eigenen Branches (`vop-stepwise`, `vop-close-match-name`), damit daraus PRs an das
|
||
Upstream-Projekt werden können.
|
||
|
||
## Zahlungsverkehr: SEPA-Format der Bank (01.09.2026)
|
||
|
||
Die Überweisung wurde bisher immer als `pain.001.001.03` eingereicht. Banken, die nur
|
||
das ältere DK-Format `pain.001.003.03` führen, weisen sie damit ab, noch bevor eine TAN
|
||
angefordert wird ("The bank does not support the XML schema …").
|
||
|
||
`FinTsUeberweisung` liest die unterstützten Formate jetzt nach der Anmeldung aus den
|
||
Bankparameterdaten (Segment HISPAS) und `SepaXml::schemaWaehlen()` bestimmt daraus den
|
||
Namensraum der Nachricht – bevorzugt das ISO-Format, sonst das DK-Format. Der Zusatz
|
||
`_GBIC_x`, den manche Banken anhängen, stört den Vergleich nicht. Meldet die Bank
|
||
ausschließlich Formate, die der Finanzmanager nicht erzeugt, nennt die Fehlermeldung sie
|
||
jetzt beim Namen, statt die Bibliothek durchschlagen zu lassen.
|
||
|
||
Für die Einzelüberweisung sind beide Fassungen im Aufbau gleich; es genügt der andere
|
||
Namensraum. `tests/schemata/pain.001.003.03.xsd` (DFÜ-Abkommen Anlage 3, Version 2.7) liegt
|
||
der Testsuite bei, damit die erzeugte Nachricht wirklich gegen das Schema geprüft wird.
|
||
|
||
## Adressverwaltung in der Verwaltung, Typeahead im Zahlungsverkehr (31.08.2026)
|
||
|
||
Das Adressbuch (bisher nur über die Verträge-Kachel erreichbar) bekommt in der Verwaltung
|
||
eine eigene Kachel „Adressverwaltung“ (Feature #51) – es speist auch den Zahlungsverkehr,
|
||
der mit Verträgen nichts zu tun hat. Die Adressliste unter `/vertraege/adressen` bleibt die
|
||
gleiche Seite; zu jeder Adresse klappt jetzt ein `<details>`-Element ihre letzten Buchungen
|
||
auf. Da die Buchung kein Fremdschlüsselfeld auf die Adresse führt, sucht
|
||
`Buchung::letzteFuerEmpfaenger()` kontenübergreifend nach dem exakten Empfängernamen – die
|
||
eindeutige Kombination aus `benutzer_id` und `name` in `adressen` (Migration 008) macht das
|
||
zuverlässig.
|
||
|
||
Die Empfängerauswahl im Zahlungsverkehr-Formular ist jetzt ein Typeahead: `kombobox.js`
|
||
(schon für die Kategorie im Kontoblatt im Einsatz) baut das `<select>` in ein Eingabefeld mit
|
||
gefilterter Liste um, ohne dass App- oder Formular-Code etwas davon merken – Wert und
|
||
Ändern-Ereignis bleiben identisch. Geprüft wurde außerdem, dass „Empfänger ins Adressbuch
|
||
übernehmen“ beim Erfassen einer Überweisung tatsächlich eine Adresse anlegt
|
||
(`ZahlungsverkehrController::anlegen()` → `Adresse::anlegen()`).
|
||
|
||
## Zahlungsverkehr: automatische Bankauswahl (31.08.2026)
|
||
|
||
Beim Erfassen einer Überweisung musste bisher neben dem Auftragskonto auch die
|
||
Bankverbindung von Hand gewählt werden – obwohl sich diese meist schon aus dem Konto ergibt
|
||
(Feature #50). Das Formular fragt jetzt nur noch das Auftragskonto ab; die passende Bank
|
||
ermittelt `Bankzugang::zugangJeKonto()` anhand der Kontenzuordnung (`bank_konten.konto_id`)
|
||
und trägt sie beim Speichern automatisch in `zahlungsauftraege.zugang_id` ein.
|
||
|
||
Konten ohne konfigurierte Bankverbindung stehen als Auftragskonto gar nicht erst zur
|
||
Auswahl – ohne Bank ließe sich ohnehin nichts senden. Ein bereits erfasster Auftrag behält
|
||
sein Auftragskonto auch dann, wenn dessen Bank nachträglich entfernt wurde: Es bleibt in der
|
||
Auswahl, statt beim nächsten Speichern unbemerkt zu wechseln.
|
||
|
||
## Einstellungen: Reiter "Profil" zum Ändern des eigenen Passworts (20.08.2026)
|
||
|
||
Bislang ließ sich das eigene Passwort nur direkt über den Datenbestand ändern (Feature #46).
|
||
Der Einstellungsdialog bekommt dafür einen neuen Reiter `Profil`, der den angemeldeten
|
||
Benutzernamen anzeigt und ein eigenes Formular für die Passwortänderung enthält – getrennt
|
||
vom generischen Speichern-Formular der übrigen Reiter, weil hier kein Einstellungswert,
|
||
sondern der Anmeldedatensatz geschrieben wird.
|
||
|
||
`EinstellungenController::passwortAendern()` (Route `POST /einstellungen/passwort`) verlangt
|
||
das aktuelle Passwort (geprüft über `Benutzer::pruefeAnmeldung()`), ein neues mit mindestens
|
||
10 Zeichen – dieselbe Grenze wie bei der Ersteinrichtung – und dessen Wiederholung. Bei Erfolg
|
||
schreibt `Benutzer::passwortSetzen()` den neuen Hash; ein Eintrag landet wie bei An- und
|
||
Abmeldung im Debug-Log.
|
||
|
||
## CONTRIBUTE.md und Setup-Skript für Mitwirkende (19.08.2026)
|
||
|
||
`CONTRIBUTE.md` beschreibt, wie Beiträge ankommen (Issue, PR, E-Mail), welche
|
||
Unterstützung gesucht wird und die drei bindenden Leitlinien des Projekts: FOSS,
|
||
Datensparsamkeit und Security mit direktem Kontakt an fds2610@sonnengetrock.net
|
||
für Sicherheitslücken. Vorangestellt ist ein Disclaimer, dass VIBE Coding
|
||
ausdrücklich erlaubt ist – das Projekt selbst ist mit intensivem LLM-Einsatz
|
||
entstanden (Feature #43).
|
||
|
||
Neu ist außerdem `dev-setup.sh` im Repo-Root: richtet Docker (optional),
|
||
Checkout mit Fork- und Upstream-Remote, lokale Container und optional die
|
||
E2E-Testinfrastruktur ein. Zielverzeichnis, E2E-Setup und Docker-Installation
|
||
lassen sich über Flags oder Umgebungsvariablen abweichend vom Default setzen.
|
||
|
||
## Kontoblatt: Typeahead für die Kategorie (19.08.2026)
|
||
|
||
Die Kategorie ließ sich im Kontoblatt nur über eine lange Auswahlliste finden – bei vielen
|
||
Kategorien ein langes Blättern (Feature #41). Ein neues `kombobox.js` baut ein markiertes
|
||
`<select data-kombobox>` beim Laden der Seite in ein Eingabefeld mit darunter aufklappender,
|
||
gefilterter Liste um; Gruppen (Optgroups) und die Reihenfolge der Optionen bleiben erhalten.
|
||
Das ursprüngliche `<select>` bleibt unsichtbar im Formular stehen, damit Wert, Name und das
|
||
Ändern-Ereignis für vorhandenen Code unverändert bleiben.
|
||
|
||
Betroffen sind der Kategorie-Filter oben im Kontoblatt und die Schnellzuordnung je Buchung
|
||
(`kontoblatt.js` öffnet dort die Kombobox statt `showPicker()`, sobald ein `<select>`
|
||
umgebaut wurde). Die Klassen-Auswahl bleibt bewusst ein gewöhnliches `<select>` – ihre Liste
|
||
ist kurz. Ohne JavaScript bleibt es überall beim normalen `<select>`.
|
||
|
||
## Kontoblatt: Schnellzuordnung ohne Neuladen (19.08.2026)
|
||
|
||
Das Ändern von Kategorie oder Klasse im Kontoblatt lud bisher die ganze Seite neu, um die
|
||
Zeile zu aktualisieren – dabei sprang der Bildschirm nach ganz oben. Bei einer älteren
|
||
Buchung weit unten in der Liste musste man nach jeder Änderung wieder dorthin zurückscrollen
|
||
(Fehler #40).
|
||
|
||
kontoblatt.js schickt die Auswahl jetzt im Hintergrund per `fetch`; Kategorie- bzw.
|
||
Klassenspalte und – falls eingerichtet – das Symbol der Bargeld-Abgleichspalte aktualisieren
|
||
sich an Ort und Stelle, die Bildlaufposition bleibt stehen. Die Meldung „Zuordnung
|
||
gespeichert.“ erscheint wie gewohnt, jetzt über `window.FmMeldungen` (app.js) ohne
|
||
Seitenwechsel.
|
||
|
||
Serverseitig unterscheiden `BuchungenController::zuordnung()` und `::klasse()` per
|
||
`X-Requested-With: XMLHttpRequest`, ob eine JSON-Antwort erwartet wird oder die bisherige
|
||
Weiterleitung mit Flash-Meldung – Letztere bleibt der einzige Weg ohne JavaScript. Schlägt
|
||
die Hintergrundanfrage fehl (Netzwerk, abgelaufene Anmeldung), übernimmt wie bisher eine
|
||
echte Formularübermittlung.
|
||
|
||
## Bargeld-Tool: Adresse mit `/api/v1` (19.08.2026)
|
||
|
||
Der Verbindungstest scheiterte mit „Das Bargeld-Tool kennt die Schnittstelle nicht
|
||
(HTTP 404)“, wenn die hinterlegte Adresse den Schnittstellenpfad schon enthielt – der
|
||
Finanzmanager hängt ihn selbst an und fragte dann unter `/api/v1/api/v1/status` an. Ein
|
||
abschließendes `/api` bzw. `/api/v1` kürzt er jetzt beim Speichern weg; bereits
|
||
gespeicherte Adressen räumt Migration 017 auf.
|
||
|
||
## Abgleich mit dem Bargeld-Tool (19.08.2026)
|
||
|
||
Bargeldausgaben werden in einem zweiten Programm erfasst; im Finanzmanager kommen sie als
|
||
Kartenumsatz oder Abhebung an. **Beim Zuweisen einer Kategorie fragt der Finanzmanager
|
||
jetzt dort nach**, ob es zum Betrag schon eine Buchung gibt – und zeigt die Antwort im
|
||
Kontoblatt.
|
||
|
||
Eingerichtet wird das unter **Verwaltung → Bargeld-Tool**, einer neuen Kachel: Adresse,
|
||
Benutzerkennung, Passwort (AES-verschlüsselt wie die FinTS-PIN), Suchzeitraum in Tagen und
|
||
die Auswahl der Kategorien, die überhaupt abgeglichen werden. Die Vormerkung steht als
|
||
`bargeld_abgleich` an der Kategorie selbst und nicht als Liste von Nummern in den
|
||
Einstellungen – so verschwindet sie mit der Kategorie, statt als tote Nummer liegenzubleiben.
|
||
Ein Knopf *Verbindung testen* sagt, ob Adresse und Zugangsdaten stimmen, ohne dass dafür
|
||
eine Buchung herhalten muss.
|
||
|
||
Geprüft wird nur, was geprüft werden soll: eine Kategorie an der Buchung (keine Umbuchung,
|
||
keine Splitbuchung), diese Kategorie vorgemerkt, eine Adresse konfiguriert. Fällt eines weg,
|
||
verschwindet der Status – auch beim Umkategorisieren und beim Abwählen einer Kategorie in der
|
||
Verwaltung, sonst bliebe ein alter Haken an einer Buchung stehen, die niemand mehr abgleicht.
|
||
Gesucht wird rückwärts ab dem Buchungsdatum (Vorgabe zehn Tage): Bargeld wird ausgegeben,
|
||
bevor der Umsatz auf dem Konto ankommt, nie danach.
|
||
|
||
Im Kontoblatt steht das Ergebnis in einer schmalen Spalte **zwischen Klasse und Betrag**:
|
||
grauer Kreis (wird nicht abgeglichen), grüner Haken (gefunden), rotes X (nicht gefunden),
|
||
Ausrufezeichen (Abruf gescheitert). Das Ausrufezeichen behält ausdrücklich die Textfarbe des
|
||
Farbschemas – der Fehler liegt beim Abruf, nicht bei der Buchung; rot oder grün wäre eine
|
||
Aussage über die Buchung, die niemand treffen kann. Der Tooltip nennt den Treffer bzw. die
|
||
Meldung der Gegenstelle. Ohne hinterlegte Adresse entfällt die Spalte ganz; sie zeigte sonst
|
||
in jeder Zeile denselben grauen Kreis. Der Status steht an der Buchung, das Kontoblatt geht
|
||
also nicht bei jedem Blättern erneut übers Netz.
|
||
|
||
Ein Fehlschlag ist ein Ergebnis, kein Abbruch: Weder ein abgeschaltetes Bargeld-Tool noch ein
|
||
Netzfehler verhindert, dass die Kategorie gespeichert wird. Der Abruf bricht nach acht
|
||
Sekunden ab. Null Treffer sind kein Fehler – ein 404 dagegen schon, denn er kann ebenso gut
|
||
heißen, dass es den Endpunkt nicht gibt.
|
||
|
||
Die Anforderung an die Gegenstelle (zwei lesende Aufrufe, Basic Auth, JSON) steht im Ticket
|
||
und in `docs/buchungen.md`. Nebenbei: Test 02 verlangte bisher für jeden
|
||
Einstellungsbereich einen Reiter im Dialog. Die Bargeld-Einstellungen haben stattdessen eine
|
||
eigene Seite – deshalb gibt es jetzt `Einstellung::EIGENE_SEITEN`, und der Test prüft die
|
||
Regel, die eigentlich gemeint war: dass jeder Bereich erreichbar bleibt.
|
||
|
||
Der E2E-Test geht denselben Weg im Browser – Kachel, Formular, Zuordnung im Kontoblatt –
|
||
gegen eine Adresse, die es nicht gibt: Genau daran zeigt sich, dass ein gescheiterter Abruf
|
||
die Zuordnung nicht aufhält, sondern nur das Ausrufezeichen setzt.
|
||
|
||
Test 34 prüft den Abruf gegen einen echten HTTP-Server (der eingebaute Server von PHP mit
|
||
einem Router, der sich wie das Bargeld-Tool verhält). Nachgebildete Antworten allein würden
|
||
genau das übergehen, woran ein Abruf scheitert: Basic Auth, HTTP-Status, ein Rumpf, der kein
|
||
JSON ist.
|
||
|
||
## Berichte: Tabellen so breit wie nötig, Zahlen rechtsbündig (18.08.2026)
|
||
|
||
Ein Bericht mit zwei oder vier Spalten wurde bisher über die gesamte Fensterbreite gezogen –
|
||
zwischen Bezeichnung und Betrag klaffte ein leeres Feld, und das Auge musste die Zeile
|
||
zweimal überqueren. Die **Berichtstabellen nehmen jetzt nur den Platz, den ihre Spalten
|
||
brauchen** (`width: auto`) und stehen dabei mittig unter der Überschrift; erst wenn der
|
||
Inhalt mehr verlangt, wachsen sie bis zur vollen Breite. Im Scrollrahmen `.breittabelle` (zwölf Monatsspalten) bleibt es beim
|
||
bisherigen Verhalten: Die Tabelle darf hinausragen und waagerecht scrollen, statt umzubrechen.
|
||
|
||
Dabei fiel auf, dass die Zahlenspalten **gar nicht rechtsbündig** standen: `.rechts` und
|
||
`.betrag` sind nur einfach spezifisch, `table.liste th, table.liste td { text-align: left }`
|
||
weiter oben im Stilblatt ist spezifischer und gewann. Solange die Spalten über den Bildschirm
|
||
gezogen waren, fiel es kaum auf – in der schmalen Tabelle sofort. Die Ausrichtung steht jetzt
|
||
für **alle Listen** ausdrücklich (`table.liste th.rechts, table.liste td.rechts`): Beträge,
|
||
Salden, Stückzahlen und Aktionsspalten stehen überall rechts, wo `.rechts` gesetzt ist –
|
||
Kontenübersicht, Kontoblatt, Wertpapiere, Depotbuchungen, Kategorien und die Berichte
|
||
gleichermaßen. Die Klasse tut damit endlich das, was ihr Name sagt.
|
||
|
||
Der neue E2E-Test misst alles drei: dass der Summenbericht deutlich schmaler bleibt als sein
|
||
Rahmen und links wie rechts gleich viel Luft behält, dass die letzte Spalte
|
||
`text-align: right` hat und dass die Monatsansicht weiterhin breiter wird.
|
||
|
||
Nachgezogen: Der E2E-Test der eingeklappten Seitenleiste erwartete `toBeHidden()` für die
|
||
Beschriftungen. Die sind aber mit Absicht nur aus dem Bild geschoben (`clip-path`, 1×1 Pixel)
|
||
statt entfernt, damit Vorleseprogramme die Namen behalten – für Playwright bleibt so ein
|
||
Element „sichtbar“. Geprüft wird jetzt, dass die Beschriftung keinen Platz mehr einnimmt und
|
||
der Verweis trotzdem seinen zugänglichen Namen behält.
|
||
|
||
## Seitenleiste: Marke mit Münzstapel, ein Klick schiebt sie auf Symbolbreite (18.08.2026)
|
||
|
||
Oben in der Seitenleiste steht jetzt eine richtige **Marke**: das Bild `Coinstack.png` aus den
|
||
Altdaten als Symbol (`public/static/coinstack.png`, auf 128 px gerechnet) und daneben der
|
||
zweifarbige Schriftzug **Finanz**manager – dieselbe Machart wie die `sidebar-brand` im
|
||
Schwesterprojekt Steuerkram.
|
||
|
||
Die Marke ist zugleich der **Schalter**: Ein Klick schiebt die Seitenleiste von 232 px auf
|
||
64 px zusammen, dann stehen dort nur noch das Markensymbol und die Symbole der Menüpunkte –
|
||
mit unveränderten Zielen; der gewonnene Platz geht an den Hauptbereich. Ein Klick auf das
|
||
Symbol ganz oben klappt sie wieder auf ihre alte Breite auf.
|
||
|
||
Zwei Feinheiten: Der Zustand steht in einem **Cookie**, nicht im `localStorage` – so rendert
|
||
der Server die Leiste schon in der richtigen Breite, statt sie bei jedem Seitenwechsel kurz
|
||
breit aufblitzen zu lassen. Und die Beschriftungen werden eingeklappt nur **aus dem Bild
|
||
geschoben** (`clip-path`), nicht mit `display:none` entfernt: Sonst verlören die Verweise
|
||
ihren Namen für Vorleseprogramme. In schmalen Fenstern, wo die Navigation ohnehin waagerecht
|
||
liegt, bleibt alles wie gehabt.
|
||
|
||
Nebenbei behoben: Die Marke der Seitenleiste hieß bisher schlicht `.marke` – dieselbe Klasse
|
||
wie die kleine Statusmarke weiter unten im Stilblatt, deren Regeln sie überschrieben. Sie
|
||
heißt jetzt `.seitenleisten-marke`.
|
||
|
||
## Bankleitzahlen-Verzeichnis: die FinTS-Adresse nachschlagen statt suchen (18.08.2026)
|
||
|
||
Beim Anlegen einer FinTS-Verbindung fehlt in aller Regel genau eine Angabe: der
|
||
**PIN/TAN-Zugangspunkt**. Die Bankleitzahl kennt jeder auswendig, die Adresse steht tief in
|
||
der Hilfe zum Online-Banking und lautet oft ganz anders als der Name der Bank vermuten lässt
|
||
(1822direkt: `https://fints.1822direkt.com/fints/hbci`).
|
||
|
||
Neu ist deshalb unter *Verwaltung → Bankleitzahlen* (`/verwaltung/bankinstitute`) das
|
||
**Verzeichnis der FinTS-Institute**. Es nimmt die CSV-Liste der Deutschen Kreditwirtschaft
|
||
(`hbci-zka.de`) auf – dieselbe Datei, aus der auch der Lexware FinanzManager seine
|
||
Zugangsdaten zieht – und beantwortet damit die eine Frage, die das Formular stellt.
|
||
|
||
**Einlesen** heißt hier ersetzen: Ein Import wirft den bisherigen Bestand weg, statt ihn zu
|
||
ergänzen; sonst blieben Institute stehen, die es nicht mehr gibt. *Nur prüfen* und
|
||
*Übernehmen* sind zwei Knöpfe an derselben Datei statt zweier Schritte an einer abgelegten:
|
||
Der Inhalt landet vollständig in der Tabelle, ein zweites Exemplar im Importverzeichnis wäre
|
||
nur Ballast zwischen den Kontoauszügen. Der Prüfbericht nennt Zeilen, Bankleitzahlen,
|
||
Zugangspunkte und den Stand der Daten und zeigt die ersten Zeilen als Sichtprobe – stimmen
|
||
dort Umlaute und Spalten, stimmt der Rest auch.
|
||
|
||
Die Datei hat es in sich, und `Services\FintsInstituteImport` nimmt ihr das ab: **ISO-8859-1**
|
||
statt UTF-8, ein Institutsname über **zwei Zeilen** in Anführungszeichen, dieselbe
|
||
**Bankleitzahl vielfach** (einmal je Filiale, mit identischem Zugangspunkt), am Ende
|
||
**Hunderte leerer Zeilen** mit laufender Nummer, und wo es keinen PIN/TAN-Zugang gibt, steht
|
||
Text statt einer Adresse. Angesprochen werden die Spalten über ihre Überschrift, nicht über
|
||
ihre Position; fehlen `BLZ` und `Institut`, bricht der Import ab, statt lauter leere Zeilen
|
||
zu schreiben. Die Spalten der Chipkartenverfahren bleiben liegen – der Finanzmanager spricht
|
||
ausschließlich PIN/TAN.
|
||
|
||
**Gesucht** wird über Bankleitzahl, BIC, Name und Ort in einem Zug. Eine Ziffernfolge nur
|
||
deshalb als Bankleitzahl zu lesen, weil sie aus Ziffern besteht, verschwiege dem Suchenden
|
||
die Bank, die er meint: „1822“ steht bei der 1822direkt im Namen und bei der Frankfurter
|
||
Sparkasse im BIC – in keiner der beiden Bankleitzahlen. Die Bankleitzahl wird von vorn
|
||
verglichen, alles Übrige als Wortbestandteil; Treffer in der Bankleitzahl stehen oben.
|
||
|
||
Aus der Trefferliste führt **Verbindung anlegen** direkt ins FinTS-Formular, das mit Bank,
|
||
Bankleitzahl und Adresse gefüllt dasteht. Umgekehrt gilt: Die **FinTS-Adresse ist kein
|
||
Pflichtfeld** mehr – bleibt sie leer, wird sie zur Bankleitzahl nachgeschlagen. Findet auch
|
||
das nichts, weist `Bankzugang` den Zugang jetzt ab, statt ihn ohne Gegenstelle zu speichern.
|
||
|
||
Dazu Migration `015_bankinstitute.sql` (Tabelle `bankinstitute`, reine Nachschlagedaten ohne
|
||
`benutzer_id`), `Models\Bankinstitut`, `Controllers\BankinstituteController`,
|
||
`tests/32_BankinstituteTest.php` und `tests/e2e/specs/bankinstitute.spec.js`.
|
||
|
||
## PayPal als zweite Art von Bankverbindung (18.08.2026)
|
||
|
||
*Bankverbindung anlegen* führt nicht mehr direkt in die Bankenmaske, sondern zur **Wahl
|
||
der Art**: HBCI/FinTS oder PayPal. Die erste Karte führt in die bisherige Maske und den
|
||
bekannten Dreischritt, die zweite in ein neues Formular mit allen Angaben für die
|
||
PayPal-Abfrage. Die Wahl steht danach fest – ein umgestellter Zugang hätte weder passende
|
||
Zugangsdaten noch passende Konten.
|
||
|
||
**Die Abfrage** läuft über die Classic-NVP-API (`Services\PaypalNvp`, `TransactionSearch`
|
||
und `GetBalance`), übernommen aus dem Parallelprojekt `Steuerkram` samt der Fallstricke,
|
||
die dort schon aufgelaufen sind:
|
||
|
||
- Live- und Sandbox-Zugangsdaten passen nur zu ihrem eigenen Endpunkt; mit dem falschen
|
||
antwortet PayPal mit *„Security header is not valid“* – **dieselbe** Antwort kommt aber
|
||
auch bei schlicht falschen Angaben. Die Fehlermeldung nennt deshalb beide Ursachen, und
|
||
weil PayPal nie sagt, welche der drei Angaben klemmt, prüft
|
||
`PaypalNvp::auffaelligkeiten()` ihre Form (Benutzername `name_api1.domain.de`, Kennwort
|
||
16 Zeichen, Signatur 56) und schreibt Abweichungen ins Protokoll und in die Meldung –
|
||
als Hinweis, nicht als Abweisung, und ohne die Werte mitzuliefern. Kennwort und Signatur
|
||
werden beim Speichern getrimmt: Eine eingefügte Signatur bringt gern ein Leerzeichen mit,
|
||
das dieselbe Meldung auslöst.
|
||
- `TransactionSearch` liefert je Aufruf höchstens 100 Buchungen und meldet das nur als
|
||
Warnung – abgefragt wird deshalb in Abschnitten von 30 Tagen, und eine Warnung gilt als
|
||
Erfolg statt als Abbruch.
|
||
- `GetBalance` antwortet je nach Konto mit `AMT` oder mit `L_AMT0`; gelesen werden beide,
|
||
sonst bliebe der Kontostand auf Live-Konten leer.
|
||
- Gerechnet wird mit `L_NETAMT` (nach Gebühr), Nullbuchungen aus Einwilligungen und
|
||
Abonnements fallen heraus.
|
||
|
||
**Die Kontenzuordnung** steht im selben Formular: PayPal führt je Konto genau ein
|
||
Guthabenkonto, eine eigene Seite wäre eine Zeile Tabelle. Das Dropdown listet die eigenen
|
||
Konten und bietet *neues Konto anlegen* mit Name, Kontoart und Anfangssaldo. Ohne
|
||
Zuordnung bleibt die Verbindung nutzbar – die Umsätze warten dann in der Abgleichliste.
|
||
|
||
Der Fingerabdruck der Umsätze hängt an der Transaktionsnummer von PayPal, nicht an Datum,
|
||
Betrag und Text: Eine nachträglich korrigierte Buchung käme sonst ein zweites Mal herein.
|
||
|
||
**Freigaben (MFA)** sind vorgesehen, ohne erfundenen Code: Die NVP-API authentifiziert
|
||
allein über die drei Zugangsdaten und stellt keine Rückfrage. `PaypalAbruf` liefert aber
|
||
dieselbe Ergebnisform wie `FinTsAbruf` – Status `tan` eingeschlossen –, der Controller
|
||
wertet beide gleich aus, und `bank_abrufe` kennt `tan_offen` bereits. Einzuhängen wäre nur
|
||
die Stelle, an der eine Antwort als Freigabeanforderung erkannt wird.
|
||
|
||
Neu: `migrations/014_paypal_zugang.sql` (Spalten `typ`, `api_benutzer`, `api_kennwort`,
|
||
`api_signatur`, `sandbox`), `Services\PaypalNvp`, `Services\PaypalAbruf`,
|
||
`Views/banking/typ.php`, `Views/banking/paypal.php`, `tests/31_PaypalZugangTest.php` und
|
||
`tests/e2e/specs/banking.spec.js`. Bestehende Zugänge bleiben FinTS-Zugänge.
|
||
|
||
## Spiegelung der Umbuchungen unter Test (17.08.2026)
|
||
|
||
Neuer Test `tests/30_UmbuchungSpiegelungTest.php`. Er nagelt die zwei Zusagen fest, die
|
||
sich aus dem Datenmodell ergeben – eine Umbuchung ist EIN Datensatz mit `gegenkonto_id`,
|
||
die Gegenseite leitet `Buchung::kontoblatt()` daraus ab:
|
||
|
||
1. **Was ein Gegenkonto trägt, steht im Kontoblatt dieses Gegenkontos** – auf allen vier
|
||
Wegen, auf denen ein Gegenkonto an eine Buchung kommt: beim Anlegen, über die
|
||
Schnellzuordnung im Kontoblatt, über die Buchungsmaske und als einzelne Splitzeile.
|
||
Ein gewechseltes Gegenkonto bleibt nicht beim alten hängen, ein entferntes taucht
|
||
nirgends mehr auf, und Such-, Zeitraum- und Kategoriefilter verstecken die Gegensicht
|
||
nicht.
|
||
2. **Wer die Buchung löscht, löscht beide Sichten** – Kontoblatt und Saldo des
|
||
Gegenkontos sind danach wieder wie vorher, Splitzeilen bleiben nicht als Waisen
|
||
zurück. Eine Gegenseite ohne Ursprungsbuchung kann nicht entstehen: Sie trägt die
|
||
Nummer des Originals, ein Löschen „auf dem Gegenkonto“ trifft deshalb dieses.
|
||
|
||
Dazu eine Invariantenprüfung über alle Daten des Mandanten, die an sieben Stellen des
|
||
Testablaufs läuft: Zu jedem gesetzten Gegenkonto gibt es eine Gegensicht, und keine
|
||
Buchung trägt ein Gegenkonto, das durch `gesplittet = 1` verdeckt wäre – das ist die
|
||
einzige Konstellation, in der eine Umbuchung unsichtbar würde, weil dann weder der Zweig
|
||
für Buchungen (`gesplittet = 0`) noch der für Splitzeilen greift. Gegenprobe: Vier
|
||
absichtliche Eingriffe ins Modell (beide Gegensicht-Zweige des Kontoblatts abgeklemmt,
|
||
Gegenkonto hinter einer Splitbuchung stehen gelassen, Löschen wirkungslos gemacht)
|
||
lassen den Test zuverlässig fehlschlagen.
|
||
|
||
## Buchungsmaske nennt das Konto und passt auf einen Bildschirm (17.08.2026)
|
||
|
||
*Neue Buchung* und *Buchung bearbeiten* zeigen oben, in welchem Konto die Buchung liegt
|
||
bzw. gespeichert wird; bisher stand das Konto nur klein in der Kopfzeile über dem
|
||
Formular. Die Maske steht zweispaltig, die Beschriftungen links neben ihren Eingaben und
|
||
die Erklärungen in deren Tooltip – damit endet sie samt Splitzeilen und Knopfleiste
|
||
oberhalb der Fensterhöhe, wo sie vorher über 1190 px lang war.
|
||
|
||
- Kontoangabe als erstes Feld über beide Spalten: Name des Kontos, dahinter die IBAN –
|
||
oder, wenn keine hinterlegt ist, Bank und Kontonummer.
|
||
- Feldreihenfolge zeilenweise: Art/Datum, Betrag/Empfänger, Kategorie-Gegenkonto/Klasse,
|
||
Buchungstext/Nummer, darunter *Abgeglichen*. Namen, Werte und Feld-IDs bleiben
|
||
unverändert; der Bereich *Splitbuchung* läuft weiter über die volle Breite.
|
||
- Beschriftungen stehen links neben ihrer Eingabe, nicht darüber (neue Variante
|
||
`.formular.beschriftung-links`). Das halbiert die Höhe je Feld; Hinweise laufen unter
|
||
Beschriftung und Eingabe hinweg, unter 560 px Fensterbreite rutscht die Beschriftung
|
||
wieder nach oben.
|
||
- Die erklärenden Texte stehen nicht mehr als eigene Zeile zwischen den Feldern, sondern
|
||
im `title` ihrer Beschriftung: Konto, Betrag, *Kategorie / Gegenkonto* und
|
||
*Splitbuchung*. Eine gepunktete Unterstreichung zeigt, wo ein Tooltip hängt.
|
||
- `.formular.zweispaltig` (`public/static/app.css`) stellt jetzt auch Tabellen und
|
||
`.feldwert` richtig: Tabellen laufen über beide Spalten, ohne eigenen Rand.
|
||
- E2E-Test in `tests/e2e/specs/buchungen.spec.js`: Die Maske nennt das Konto, Datum steht
|
||
rechts neben der Art, die Beschriftung links neben ihrer Eingabe, die Erklärungen hängen
|
||
in deren Tooltip, und das ganze Formular endet oberhalb der Fensterhöhe.
|
||
|
||
## Kontoformular zweispaltig (17.08.2026)
|
||
|
||
*Konto bearbeiten* und *Neues Konto* zeigen ihre Felder in zwei Spalten. Alle Eingaben
|
||
stehen damit auf einem Bildschirm, ohne zu scrollen; bisher lagen die vierzehn Felder
|
||
untereinander.
|
||
|
||
- Neue Formularvariante `.formular.zweispaltig` (`public/static/app.css`): ein Grid mit
|
||
zwei Spalten, je Feld ein `.feld` aus Beschriftung, Eingabe und Hinweis. Überschriften,
|
||
Knopfleiste und `.feld.ganz` laufen über beide Spalten. Unter 720 px Fensterbreite fällt
|
||
das Formular auf eine Spalte zurück.
|
||
- Feldreihenfolge zeilenweise: Name/Kontoart, Kontengruppe/Beschreibung, Bank/Kontonummer,
|
||
IBAN/BIC, Anfangssaldo/Kreditlimit, Sortierung/Kennzeichen. Namen, Werte und Feld-IDs
|
||
bleiben unverändert.
|
||
- Der Bereich *Konto löschen* darunter bekommt dieselbe Breite, bleibt aber einspaltig.
|
||
- E2E-Test in `tests/e2e/specs/stammdaten.spec.js`: Kontoart steht rechts neben dem Namen,
|
||
und das Formular endet oberhalb der Fensterhöhe.
|
||
|
||
## Import und Export in zwei Formaten (17.08.2026)
|
||
|
||
Aus der Kachel *QIF-Import* wird **Import/Export** mit je einem Knopf für beide Richtungen.
|
||
Wer darauf drückt, wählt zuerst das **Dateiformat** – QIF oder CSV –, und erst danach öffnet
|
||
sich der Dialog, der genau dieses Format annimmt bzw. schreibt. Bisher gab es nur den einen
|
||
Weg hinein: QIF, und nur herein.
|
||
|
||
**Formatwahl** (`Dateiformat`, `Views/verwaltung/formatwahl.php`)
|
||
|
||
- Die Formate stehen an einer Stelle (`Dateiformat::ALLE`); Dialoge, Auswahlkarten und die
|
||
Prüfung der Dateiendung hängen daran.
|
||
- Auf der Verwaltungsseite ist die Auswahl ein `<dialog>`; die Karten liefert die Ansicht
|
||
mit, das Skript setzt nur die Ziele. Ohne Skript – oder ohne `<dialog>` – führt derselbe
|
||
Knopf auf dieselbe Auswahl als eigene Seite (`/verwaltung/import` bzw. `/verwaltung/export`
|
||
ohne `format`).
|
||
|
||
**CSV-Import** (`CsvBuchungsImport`, `CsvTabelle`)
|
||
|
||
- Buchungen samt Splitzeilen. Die Kopfzeile bestimmt die Spalten, nicht ihre Reihenfolge –
|
||
verglichen wird kleingeschrieben und ohne Umlaute. Unverzichtbar sind `Datum`, `Konto` und
|
||
`Betrag`; fehlt eine, bricht der Lauf ab, statt eine halbe Datei einzulesen.
|
||
- Splitzeilen folgen unmittelbar auf ihre Buchung und tragen in `Art` das Wort *Splitzeile*.
|
||
Eine verwaiste Splitzeile ist ein Fehler und wird gemeldet, nicht stillschweigend fallen
|
||
gelassen.
|
||
- Kategorien und Klassen legt der Import bei Bedarf an, **Konten nicht**: Ein Konto trägt
|
||
Kontoart, Bankverbindung und Anfangssaldo, und nichts davon steht in der Zeile.
|
||
- Dieselbe Dublettenerkennung wie beim QIF (Fingerabdruck aus Konto, Datum, Betrag, Nummer
|
||
und Text, mit laufendem Index für echte Doppelbuchungen am selben Tag). Trockenlauf und
|
||
echter Lauf wie gehabt.
|
||
- `CsvTabelle` erkennt Semikolon oder Komma als Trenner und liest Beträge in deutscher wie
|
||
englischer Schreibweise; gelesen wird über `fgetcsv()`, damit ein maskiertes Feld einen
|
||
Zeilenumbruch enthalten darf.
|
||
|
||
**Export** (`BuchungsExport`, `ExportController`)
|
||
|
||
- Filter nach **Zeitraum, Konto, Kategorie und Klasse**; ohne Häkchen zählt jeweils alles.
|
||
Über dem Formular steht, wie viele Buchungen der Filter trifft.
|
||
- Der Kontofilter greift auf beide Seiten einer Umbuchung, und Kategorie wie Klasse zählen
|
||
auch in den Splitzeilen – sonst fielen genau die Buchungen heraus, die man sucht.
|
||
- Das QIF trägt den Dialekt des FinanzManagers und lässt sich vom eigenen Import wieder
|
||
einlesen; Klassen und Kategorien kommen vollständig mit, die Konten nur, soweit der Filter
|
||
sie zulässt. Das CSV ist dasselbe Format, das der CSV-Import liest.
|
||
- **Exportdateien werden nicht vorgehalten.** Sie entstehen im Speicher und gehen unmittelbar
|
||
an den Browser; ein Exportverzeichnis gibt es bewusst nicht.
|
||
- Das Filterformular sendet als POST: Bei vielen angehakten Kategorien wächst eine GET-Adresse
|
||
über das, was nginx annimmt – die Antwort wäre „414 Request-URI Too Large“ statt der Datei.
|
||
|
||
**Housekeeping** (`Housekeeping`, `scripts/housekeeping.php`, Migration `013`)
|
||
|
||
- Hochgeladene Importdateien bleiben absichtlich liegen (erst Trockenlauf, dann Übernahme)
|
||
und sammeln sich sonst unbegrenzt an. Was älter ist als die eingestellte Frist, wird
|
||
gelöscht – Vorgabe 14 Tage, `0` schaltet es ab (*Einstellungen → Import/Export*).
|
||
- Überwacht werden das Importverzeichnis und `data/tmp`; gelöscht werden nur gewöhnliche
|
||
Dateien unmittelbar darin, keine Symlinks und keine Unterverzeichnisse.
|
||
- Angestoßen beiläufig beim Öffnen von Import und Export, höchstens einmal am Tag. Ein Fehler
|
||
dabei reißt die Seite nicht mit. Für lange ungenutzte Installationen gibt es denselben Lauf
|
||
als Skript (`--probe` zählt nur auf, was wegfiele).
|
||
|
||
**Nebenbei**
|
||
|
||
- Der Upload nimmt jetzt beide Endungen an; welcher Importeur läuft, entscheidet die Endung
|
||
der Datei und nicht ein verstecktes Formularfeld.
|
||
- `BerichtExport` (Auswertungen) nutzt die Zeilen- und Zahlenregeln aus `CsvTabelle`, statt
|
||
sie ein zweites Mal zu führen.
|
||
- `docs/qif-import.md` heißt jetzt `docs/import-export.md` und beschreibt beide Richtungen.
|
||
|
||
## Umsätze im Banking sammelweise löschen (17.08.2026)
|
||
|
||
Die Umsatzlisten des Bankings haben jetzt dieselbe Löschfunktion wie ein Kontoblatt: ein
|
||
Ankreuzfeld vor jedem Umsatz, eines in der Titelleiste für alle angezeigten und darüber den
|
||
Knopf **Auswahl löschen**. Bisher ließ sich ein abgerufener Umsatz nur übernehmen oder
|
||
verwerfen – wegräumen ging gar nicht.
|
||
|
||
- Der Knopf steht in jedem Reiter – *offen*, *übernommen* und *verworfen* –, nennt die Anzahl
|
||
der Auswahl und bleibt ohne Auswahl gesperrt. Die Rückfrage vor dem Löschen nennt sie
|
||
ebenfalls.
|
||
- Gelöscht wird allein die abgerufene Zeile. Eine daraus entstandene Buchung bleibt im
|
||
Kontoblatt stehen; mit dem Umsatz geht aber sein Fingerabdruck, sodass er beim nächsten
|
||
Abruf wieder hereinkommen kann.
|
||
- Neue Route `POST /banking/umsaetze/loeschen` auf `Bankumsatz::mehrereLoeschen()`; sie kehrt
|
||
in den Reiter zurück, aus dem gelöscht wurde.
|
||
- Die Liste ist keine Tabelle, sondern eine Folge von Karten. Die Kopfzeile, die eine Tabelle
|
||
von selbst hätte, gibt es deshalb neu als `.listenkopf`; die Mechanik dahinter ist
|
||
dieselbe wie im Kontoblatt (`.alle-waehlen` mit `data-waehlt`).
|
||
|
||
## Ruhige Buchungszeilen im Kontoblatt (17.08.2026)
|
||
|
||
Die Zeilen des Kontoblatts sind jetzt alle gleich hoch – zwei Textzeilen – und ihr Inhalt
|
||
steht senkrecht in der Mitte. Bisher wuchs eine Zeile mit ihrem Text, sodass die Liste beim
|
||
Blättern sprang; der Buchungstext war zudem nach 90 Zeichen hart abgeschnitten und der Rest
|
||
nirgends zu sehen.
|
||
|
||
- Was auf zwei Zeilen nicht passt, schneidet das Stylesheet ab. Der vollständige Text steht
|
||
im Tooltip – und zwar nur dort, wo tatsächlich etwas fehlt: Ob ein Text abgeschnitten ist,
|
||
entscheidet sich erst nach dem Umbruch, `kontoblatt.js` setzt den Tooltip deshalb nach dem
|
||
Laden und nach jeder Größenänderung des Fensters.
|
||
- Der Buchungstext wird vollständig ausgegeben statt nach 90 Zeichen gekürzt; sichtbar bleibt
|
||
dank Abschnitt gleich viel, im Tooltip steht nun aber alles.
|
||
- Über dem Kategoriefeld einer **Splitbuchung** nennt der Tooltip die Zeilen der Aufteilung:
|
||
je Zeile Kategorie (oder Gegenkonto) und Betrag. In der Spalte selbst steht nur
|
||
*Splitbuchung*; wohin das Geld geht, war bisher erst im Dialog zu sehen.
|
||
|
||
## Klasse im Kontoblatt direkt auswählen (17.08.2026)
|
||
|
||
Die Klassenspalte des Kontoblatts ist jetzt anklickbar – nach derselben Bauart wie die
|
||
Zuordnung daneben: Ein Klick öffnet die Klassenliste in der Zelle, und die gewählte Klasse
|
||
wird sofort gespeichert. Der erste Eintrag *— keine —* löscht die vorhandene Klasse. Bisher
|
||
war dafür jedes Mal der Umweg über die Buchungsmaske nötig.
|
||
|
||
- Neue Route `POST /buchungen/{id}/klasse` auf `Buchung::klasseSetzen()`. Die Methode achtet
|
||
den Monatsabschluss und weist eine Klasse ab, die es nicht gibt.
|
||
- Die Klasse steht **neben** der Zuordnung, nicht in ihr: Kategorie, Gegenkonto und
|
||
Splitzeilen bleiben unberührt, eine gesplittete Buchung bleibt gesplittet.
|
||
- Die Liste gibt es – wie die der Zuordnung – genau einmal im HTML; `kontoblatt.js` hängt sie
|
||
in die angeklickte Zelle. Ohne JavaScript führt der Klick weiterhin in die Buchungsmaske.
|
||
- Zeilen der Gegensicht bleiben unverändert Text: Sie werden auf ihrem Ursprungskonto
|
||
geändert.
|
||
|
||
## Splitbuchungen in den Abgleichregeln (13.08.2026)
|
||
|
||
Eine Regel für den Abgleich kann jetzt eine **Splitbuchung** vorgeben: mehrere Kategorien mit
|
||
einem Anteil in Prozent. Greift die Regel, wird der Umsatz in diesem Verhältnis aufgeteilt –
|
||
bei jedem Betrag neu gerechnet, sodass eine Nebenkostenabrechnung über 300 € und eine über
|
||
450 € nach demselben Schlüssel gebucht werden.
|
||
|
||
Das Schema lag bereits im Modell und in der Datenbank, war aber über die Oberfläche nicht
|
||
erreichbar; die Dokumentation beschrieb ein Feld, das es nirgends zu sehen gab.
|
||
|
||
- Unter jeder Regel klappt *Splitbuchung* die Zeilen auf: Kategorie, Klasse, Notiz, Anteil.
|
||
Dasselbe Formular steht bei *Neue Regel* (`src/Views/banking/regelsplits.php`, in beiden
|
||
Ansichten eingebunden). Zugeklappt, solange eine Regel keine Splitzeilen hat.
|
||
- Die Anteile gelten **zueinander**, nicht absolut: 60 zu 30 sind zwei Drittel zu einem
|
||
Drittel. Bisher hätte die letzte Zeile als Rest 40 statt der eingetragenen 30 Prozent
|
||
bekommen – ein Fehler, der in der fertigen Buchung nicht mehr zu sehen gewesen wäre.
|
||
Deshalb ist der Anteil auch nicht länger auf 1 gedeckelt.
|
||
- Im Abgleichdialog zeigt der Vorschlag die errechneten Zeilen mit ihren Beträgen und schickt
|
||
sie mit. Zuvor gingen sie beim Knopf *Als neue Buchung* verloren: Das Formular kannte nur
|
||
Kategorie und Klasse, sodass von Hand übernommene Umsätze ungeteilt im Kontoblatt landeten.
|
||
Der automatische Lauf war davon nie betroffen.
|
||
|
||
## Kurse von Yahoo Finanzen (13.08.2026)
|
||
|
||
Dritter Anbieter im Kursabruf: **Yahoo Finanzen**, ohne Schlüssel und ohne Tageskontingent.
|
||
Eine offene Schnittstelle gibt es dort nicht mehr – `query1.finance.yahoo.com` antwortet
|
||
ohne Sitzung mit HTTP 429, auch mit Cookie und Crumb –, gelesen wird deshalb die Kursseite
|
||
selbst.
|
||
|
||
- Auswahl *Yahoo Finanzen* im Einstellungsdialog unter *Wertpapiere*; das Feld für den
|
||
API-Schlüssel bleibt dort leer.
|
||
- Das **Kürzel muss in den Stammdaten stehen** und den Handelsplatz nennen (`ALV.DE`,
|
||
`PTX.F`). Yahoo hat keine ISIN-Suche mehr, `/lookup` und `/search` antworten mit 404.
|
||
- `Kursabruf::yahooKurs()` liest beide Ausprägungen der Seite (`<fin-streamer>` und den
|
||
angezeigten Kurs in deutscher Schreibweise) und prüft am Seitentitel, dass die Seite zum
|
||
Kürzel gehört – ein Tippfehler bringt so keinen fremden Kurs in den Bestand.
|
||
- Eine Sekunde Abstand zwischen zwei Seiten; auf schnelle Serienabrufe antwortet Yahoo mit
|
||
HTTP 503. Weil dieser 503 vorübergehend ist und Kürzel trifft, die im Browser zur selben
|
||
Zeit funktionieren, wird bis zu dreimal angefragt. Der angezeigte Kursstand geht ins
|
||
Debug-Log, weil Yahoo gelegentlich eine ältere Fassung der Seite ausliefert.
|
||
|
||
Der Weg über fremdes Markup ist der empfindlichste der drei Anbieter: Baut Yahoo die Seite
|
||
um, meldet der Abruf einen Fehler, statt einen falschen Kurs zu speichern.
|
||
|
||
## Kursabruf: Financial Modeling Prep antwortete mit HTTP 403 (13.08.2026)
|
||
|
||
Financial Modeling Prep hat die alten `/api/v3/`-Adressen zum 31. August 2025 abgeschaltet;
|
||
sie beantworten seither jede Anfrage mit HTTP 403. Der Kursabruf sprach genau diese Adressen
|
||
an und meldete daraufhin *„Der API-Schlüssel wurde abgelehnt“* – eine falsche Fährte, denn
|
||
der Schlüssel war in Ordnung.
|
||
|
||
- `Kursabruf` nutzt für Financial Modeling Prep jetzt `/stable/quote` und `/stable/search-isin`.
|
||
- Fehlermeldungen nennen den Klartext des Dienstes. HTTP 403 heißt nicht mehr pauschal
|
||
„Schlüssel abgelehnt“, und HTTP 402 („im gebuchten Tarif nicht enthalten“) wird als
|
||
eigener Fall gemeldet statt als nacktes „antwortet mit HTTP 402“.
|
||
|
||
**Hinweis zum kostenlosen Tarif von Financial Modeling Prep:** Er umfasst weder die
|
||
ISIN-Suche noch deutsche Börsenplätze (`….DE`) – beides beantwortet der Dienst mit HTTP 402.
|
||
Für ein Depot mit deutschen ISINs ist dort auch nach dieser Korrektur nichts zu holen;
|
||
dafür ist Leeway.tech vorgesehen (Einstellungen → Wertpapiere).
|
||
|
||
## Detailseite eines Wertpapiers (13.08.2026)
|
||
|
||
Bisher endete der Weg zu einem Wertpapier in einer Tabellenzeile: Stückzahl, Kurs, Wert –
|
||
und wer mehr wissen wollte, musste sich die Angaben aus Stammdaten, Depotbuchungen und
|
||
Auswertungen zusammensuchen. Der Name in der Übersicht war kein Link.
|
||
|
||
**Die neue Seite** (`/wertpapiere/papier/{id}`, `Views/wertpapiere/papier.php`,
|
||
`WertpapiereController::papier()`)
|
||
- Erreichbar über den Wertpapiernamen in der Bestandsübersicht und in der Liste der
|
||
Depotbuchungen sowie über *Details* in den Stammdaten.
|
||
- Oben die Kennzahlen über die ganze Historie: Bestand, Wert, Einstand, nicht realisierter
|
||
und realisierter Gewinn, Erträge.
|
||
- Darunter zwei Kacheln: **Stammdaten** (ISIN, WKN, Art, Währung, Kürzel, Status, erste
|
||
und letzte Buchung) und **Kurs** (letzter Kurs mit Datum und Quelle, Hoch und Tief der
|
||
letzten zwölf Monate, Einstand je Stück).
|
||
- **Bestand je Depot** – ein Papier kann in mehreren Depots liegen – und die
|
||
**Wertentwicklung** als dasselbe Diagramm wie in der Depotübersicht, auf dieses Papier
|
||
gerechnet.
|
||
- Unten die **Buchungen** eines Zeitraums, voreingestellt das laufende Jahr, mit denselben
|
||
Vorgaben wie die Auswertungen. Dazu eine Zeile mit Zu- und Abgängen und den Erträgen des
|
||
Zeitraums. Die Kennzahlen oben bleiben davon unberührt.
|
||
|
||
**Unter der Haube**
|
||
- `Depot::bestand()` und `Depot::entwicklung()` nehmen eine Wertpapiernummer entgegen. Die
|
||
Detailseite rechnet damit nicht selbst, sondern bekommt dieselben Zahlen wie die
|
||
Übersicht – beide können nicht auseinanderlaufen.
|
||
- Neu: `Wertpapier::kurseckwerte()` (Hoch, Tief, Zahl der Kurse eines Zeitraums) und
|
||
`Depotbuchung::datumsgrenzen()` (erste und letzte Buchung, wahlweise je Papier).
|
||
- `View::stueck()` formatiert Stückzahlen an einer Stelle statt in vier Ansichten.
|
||
|
||
## Wertpapiergeschäft aus der Kontobuchung (13.08.2026)
|
||
|
||
Bisher konnte eine Depotbuchung ihre Geldbuchung nur selbst anlegen. Der Weg, den der
|
||
Alltag nimmt, fehlte: Der Bankabruf bringt die Wertpapierabrechnung als Umsatz auf dem
|
||
Girokonto, und erst dort entscheidet sich, dass es ein Kauf war. Wer die Buchung dann
|
||
einem Depot zuordnet, buchte eine Umbuchung – ohne Stückzahl, ohne Kurs, ohne Einstand.
|
||
|
||
**Das Depot in der Zuordnungsliste** (`Views/buchungen/zuordnungsliste.php`,
|
||
`kontoblatt.js`)
|
||
- Unter *Kategorie / Gegenkonto* steht eine dritte Gruppe: *Wertpapiergeschäft im Depot*.
|
||
Die Auswahl speichert nicht sofort, sondern öffnet die Wertpapiermaske.
|
||
- Die schlichte Umbuchung auf ein Depot bleibt daneben stehen – auf ein Depot lässt sich
|
||
auch Geld überweisen, ohne dass etwas gekauft wird.
|
||
- Ohne JavaScript führt derselbe Weg auf `/buchungen/{id}/wertpapier` mit denselben
|
||
Feldern; auch die Buchungsmaske leitet dorthin.
|
||
|
||
**Die beiden Dialoge** (`Views/buchungen/wertpapierfelder.php`, `wertpapierkosten.php`,
|
||
`wertpapierbuchung.js`) – zugeschnitten auf den *Wertpapier kaufen*-Dialog des
|
||
FinanzManagers:
|
||
- Vorne Vorgangsart, Datum, Wertpapier, Stück und Kurs; dahinter, hinter dem Knopf
|
||
*Kosten …*, die Nebenkosten nach Arten: sechs Gebühren (Bankprovision, Maklercourtage,
|
||
Börsenplatzentgelt, Spesen, Spesen Ausland, Sonstige Kosten) und sechs Steuern
|
||
(Zinsabschlagsteuer, Kapitalertragsteuer, Solidaritätszuschlag, Ausländische
|
||
Quellensteuer, Abgeltungsteuer, Kirchensteuer).
|
||
- ISIN und WKN werden aus dem Verwendungszweck gelesen (`Wertpapier::ausText()`); ein
|
||
bekanntes Papier ist damit vorausgewählt, ein unbekanntes vorausgefüllt.
|
||
- Ein Abfluss vom Konto kann nur ein Kauf sein, ein Zufluss ein Verkauf oder eine
|
||
Dividende – was nicht passt, steht gar nicht zur Wahl.
|
||
|
||
**Was gebucht wird** (`Services\Wertpapiergeschaeft`, `Services\Wertpapierkosten`)
|
||
- Der Buchungsbetrag ist die feste Größe. Die Nebenkosten werden aus ihm
|
||
herausgerechnet; was übrig bleibt, ist der Kurswert und damit der **Einstandspreis**.
|
||
Ein Kauf über 255,00 € mit 5,00 € Kosten ergibt 250,00 € Einstand, ein Verkauf mit
|
||
3.103,17 € Gutschrift und 84,83 € Kosten einen Bruttoerlös von 3.188,00 €.
|
||
- Im Kontoblatt entsteht eine **Splitbuchung**: je belegter Kostenart eine Zeile in ihrer
|
||
Kategorie, der Rest als Umbuchung ins Depot. Die Zeilen ergeben genau den
|
||
Buchungsbetrag. Ohne Nebenkosten bleibt es bei der schlichten Umbuchung.
|
||
- Die Depotbuchung hängt sich an die vorhandene Buchung, statt eine zweite anzulegen
|
||
(neue Spalte `depot_buchungen.buchung_erzeugt`, `migrations/012_wertpapierkosten.sql`).
|
||
Wird sie gelöscht, bleibt die Buchung der Bank stehen und ist nur wieder unzugeordnet.
|
||
|
||
**Kategorien der Kostenarten** (`Views/einstellungen/index.php`, Bereich *Wertpapiere*)
|
||
- Je Kostenart einmalig eine Kategorie hinterlegen; der Kostendialog fragt dann nur noch
|
||
Beträge ab. Für *Sonstige Kosten* ist die Einstellung nur die Vorgabe – sie lässt sich
|
||
im Dialog überschreiben, wie im FinanzManager.
|
||
- Arten ohne Kategorie werden zu Splitzeilen ohne Zuordnung, nicht zu einem Fehler.
|
||
|
||
## Stichtag für den Umsatzabruf (13.08.2026)
|
||
|
||
Der Abrufzeitraum kannte bisher nur Spannen: seit dem letzten Abruf, 7, 30 oder 365 Tage.
|
||
Wer einen Bestand ab einem festen Datum aufbauen wollte – etwa ab dem Wechsel vom Lexware
|
||
FinanzManager –, hatte dafür keine Angabe. Der Stichtag steht jetzt an zwei Stellen.
|
||
|
||
**Vorgabe der Bankverbindung** (`Models\Bankzugang`, `Views/banking/formular.php`,
|
||
`migrations/011_abruf_stichtag.sql`)
|
||
- Neuer Abrufzeitraum *Ab Stichtag* und das dazugehörige Datumsfeld im Zugangsformular
|
||
(neue Spalte `bank_zugaenge.abruf_stichtag`). Gewählt, wird jeder Abruf ab diesem Tag
|
||
geholt.
|
||
- *Ab Stichtag* ohne Datum wird abgewiesen – eine Vorgabe, die nichts vorgibt, führte beim
|
||
Abruf nur zu einem anderen Zeitraum als gedacht. Ein unlesbares Datum ebenso.
|
||
- Die Übersicht der Bankverbindungen zeigt den Stichtag neben dem Zeitraum.
|
||
|
||
**Stichtag für einen einzelnen Abruf** (`Services\FinTsAbruf`, `BankingController`,
|
||
`Views/banking/index.php`)
|
||
- Neben *Umsätze abrufen* steht ein Datumsfeld. Ausgefüllt geht es dem eingestellten
|
||
Abrufzeitraum vor und gilt **nur für diesen Lauf**: Wer einmalig weiter zurückgreifen
|
||
will, muss die Einstellung des Zugangs nicht ändern und hinterher zurückstellen.
|
||
- Der Stichtag liegt im Abrufzustand, nicht in der Datenbank – nach einer TAN-Runde läuft
|
||
der Abruf im nächsten Request weiter und holt dort denselben Zeitraum wie beim Start.
|
||
- Ein Stichtag in der Zukunft wird auf heute gesetzt, statt den Abruf leer ausgehen zu
|
||
lassen; das Protokoll vermerkt es.
|
||
|
||
Angenommen werden das Format des Datumsfelds (`2026-01-01`) und die deutsche Schreibweise
|
||
(`01.01.2026`); alles andere gilt als Fehleingabe. Wie weit die Bank zurückreicht,
|
||
entscheidet sie selbst – 90 Tage sind üblich, mehr liefert nicht jede.
|
||
|
||
## Termine und Verträge wandern unter die Verwaltung (13.08.2026)
|
||
|
||
Die Seitenleiste war lang geworden. *Termine* und *Verträge* sind kein täglicher Einstieg –
|
||
die Reihen laufen von selbst, Verträge sind Stammdaten – und stehen deshalb nicht mehr im
|
||
Hauptmenü, sondern als letzte Kacheln in der Verwaltung.
|
||
|
||
**Navigation** (`Views/layout/kopf.php`)
|
||
- Die Menüpunkte *Termine* und *Verträge* entfallen.
|
||
- Ein Menüeintrag kann jetzt weitere Pfade mitführen (`auch`), unter denen er als aktiv
|
||
gilt. *Verwaltung* leuchtet damit auch auf `/termine`, `/termine/kalender`,
|
||
`/termine/erinnerungen` und unterhalb von `/vertraege` – sonst stünde die Seitenleiste
|
||
dort ohne Markierung.
|
||
|
||
**Verwaltung** (`Views/verwaltung/index.php`, `VerwaltungController`)
|
||
- Neue Kachel *Termine* mit der Zahl der aktiven Reihen, den offenen Erinnerungen und
|
||
Wegen zu regelmäßigen Buchungen, Finanzkalender und Erinnerungen.
|
||
- Neue letzte Kachel *Verträge* mit der Zahl der laufenden Verträge, den in den nächsten
|
||
90 Tagen ablaufenden Kündigungsfristen und Wegen zu Verträgen, Adressen, Sparzielen und
|
||
Krediten.
|
||
- Beide Kacheln führen mehrere Wege und nutzen deshalb kleine Knöpfe und einen knappen
|
||
Beitext.
|
||
- Alle übrigen Kacheln ziehen nach: *Kategorien*, *Klassen*, *Konten*, *QIF-Import* und
|
||
*Online-Banking* tragen jetzt dieselben kleinen Knöpfe. Vorher standen zwei Größen
|
||
nebeneinander im selben Raster.
|
||
|
||
**Knopfleisten stehen am Kachelfuß** (`static/app.css`)
|
||
- Das Raster der Verwaltung heißt `kachelraster fussbuendig`: die Kacheln werden zu
|
||
Flexspalten, der freie Platz sammelt sich über der Knopfleiste. Die Knöpfe stehen damit
|
||
in jeder Kachel einer Zeile auf gleicher Höhe unten, statt der jeweiligen Textlänge zu
|
||
folgen. Der Abstand zum Text steckt im Padding – `margin-top: auto` schluckt den
|
||
Außenabstand und ließe die Knöpfe in der höchsten Kachel sonst am Text kleben.
|
||
|
||
**Knopfleisten brechen um** (`static/app.css`)
|
||
- `.knopfleiste` bekommt `flex-wrap: wrap`. Bisher ragten Leisten mit mehreren Knöpfen bei
|
||
schmalen Kacheln über den rechten Rand hinaus, statt in die nächste Zeile zu rutschen.
|
||
Das gilt für alle Knopfleisten, nicht nur die der Verwaltung.
|
||
|
||
Alle Adressen unter `/termine` und `/vertraege` bleiben unverändert erreichbar, ebenso die
|
||
Reiter innerhalb der Seiten und die Verlinkungen aus Cockpit und Buchungsvorlagen.
|
||
|
||
**Test:** `tests/24_NavigationTest.php`
|
||
|
||
## QIF-Import: nur ausgewählte Konten übernehmen (12.08.2026)
|
||
|
||
Der QIF-Import kannte bisher nur den Zeitraum als Filter – ein Export ließ sich nur ganz
|
||
einlesen. Jetzt lassen sich einzelne Konten wählen, etwa um ein Konto nachzuziehen, ohne
|
||
die übrigen anzufassen.
|
||
|
||
**Kontoauswahl im Importeur** (`QifBuchungsImport`)
|
||
- Neue Option `konten`. Maßgeblich ist nicht allein der `!Account`-Abschnitt, sondern jedes
|
||
Konto, das der Eintrag berührt: das eigene, die Umbuchungsziele aus `L` und den
|
||
Splitzeilen und beim Wertpapiergeschäft das Verrechnungskonto. Eine Umbuchung steht nur
|
||
einmal in der Datei – sonst fehlte dem gewählten Konto die Gegenseite.
|
||
- Die Anfangssalden werden weiter über die ganze Datei gerechnet, aber nur für die
|
||
gewählten Konten geschrieben. Ein Teilimport überschreibt so keine fremden Salden.
|
||
- Die Fingerabdrücke der Buchungen entstehen vorab in einem eigenen Durchgang. Der
|
||
laufende Zähler für gleiche Fingerabdrücke hing sonst an der Auswahl, und dieselbe
|
||
Buchung wäre bei einem zweiten Lauf mit anderer Auswahl doppelt angelegt worden.
|
||
- `kontenDerDatei()` nennt die Konten eines Exports samt der Zahl der Einträge, die sie
|
||
berühren – auch die, die nur Umbuchungsziel sind und keine eigene Zeile führen.
|
||
|
||
**Dialog** (`Views/verwaltung/import.php`, `ImportController`)
|
||
- Nach dem Trockenlauf steht unter *Konten* die Ankreuzliste der Konten dieser Datei; vor
|
||
dem ersten Lesen gibt es nichts zu wählen. Von dort geht es weiter zum erneuten
|
||
Trockenlauf mit der Auswahl oder zum Übernehmen.
|
||
- Die Vorschau zählt mit, wie viele Buchungen zu keinem der gewählten Konten gehören.
|
||
- Was im Formular ankommt, wird gegen die Konten der Datei geprüft.
|
||
|
||
**Kommandozeile** (`scripts/qif-import.php`)
|
||
- `--konto=NAME` (mehrfach angebbar) grenzt den Import ein, `--konten` nennt die Konten der
|
||
Datei und beendet sich. Ein Name, den die Datei nicht führt, bricht ab – sonst liefe der
|
||
Import durch und legte nichts an.
|
||
|
||
## Umbuchungen: Gegenkonto sichtbar, Zuordnung im Kontoblatt (12.08.2026)
|
||
|
||
Eine Umbuchung war auf dem Gegenkonto nicht mehr zuzuordnen: Die Zeile trug die Marke
|
||
*Umbuchung*, die Kategoriespalte daneben blieb leer. Zu sehen etwa bei den Buchungen
|
||
zwischen DKB Girokonto und Paypal – die Daten waren richtig, die Anzeige verschwieg sie.
|
||
|
||
**Beide Sichten nennen das Gegenüber** (`Views/buchungen/kontoblatt.php`)
|
||
- In der Kategoriespalte steht jetzt auch in der Gegensicht das Konto in eckigen Klammern,
|
||
von dem die Umbuchung kommt. Die Abfrage lieferte das Konto längst mit; die Ansicht hat
|
||
es nur nicht gezeigt.
|
||
|
||
**Kategorie und Gegenkonto in einem Feld** (`Views/buchungen/zuordnungsliste.php`,
|
||
`BuchungenController`)
|
||
- Unter den Kategorien stehen die eigenen Konten in eckigen Klammern – wie im
|
||
FinanzManager, dessen Kategoriefeld `[Konto]` annimmt. Eines davon macht die Buchung zur
|
||
Umbuchung. Die Auswahl *Ausgabe / Einnahme* bestimmt seither nur noch die Richtung; das
|
||
eigene Konto fehlt in der Liste.
|
||
- Dieselbe Liste steht in den Splitzeilen. Eine Splitzeile mit Gegenkonto ließ sich bisher
|
||
nur im Import anlegen und ging beim Bearbeiten der Buchung stillschweigend verloren.
|
||
|
||
**Schnellzuordnung im Kontoblatt** (`public/static/kontoblatt.js`, `Buchung::zuordnen()`)
|
||
- Ein Klick auf die Kategoriespalte öffnet die Liste in der Zelle: leerer Eintrag,
|
||
*Splitbuchung*, Kategorien, Konten. Die Auswahl speichert sofort
|
||
(`POST /buchungen/{id}/zuordnung`) und kommt in dieselbe Ansicht zurück – Filter und
|
||
Seite hängen am Ziel.
|
||
- Die Liste gibt es einmal und wandert in die angeklickte Zelle. Eine Liste je Zeile wären
|
||
bei hundert Zeilen ebenso viele vollständige Kategoriebäume im HTML.
|
||
- Feld und Liste sind gleich breit und gleich hoch, die Zelle zeigt nur, was auch in der
|
||
Liste steht – sonst rückte beim Anklicken die halbe Zeile.
|
||
- Ohne JavaScript führt derselbe Klick in die Buchungsmaske: Die Zelle ist ein Link.
|
||
- Auch die Gegenseite einer fremden Umbuchung ist anklickbar; sie führt zu ihrer Buchung
|
||
auf dem anderen Konto, wo sie geändert wird.
|
||
|
||
**Splitdialog** (`Views/buchungen/kontoblatt.php`, `Buchung::splitsErsetzen()`)
|
||
- Der Eintrag *Splitbuchung* öffnet ein modales Fenster mit Zeilen für Kategorie oder
|
||
Gegenkonto, Klasse, Notiz und Betrag; der Restbetrag wird mitgerechnet, weitere Zeilen
|
||
kommen auf Knopfdruck. Eine bestehende Aufteilung steht schon drin.
|
||
- Ein Klick auf eine bereits gesplittete Buchung öffnet direkt den Dialog statt der
|
||
Auswahlliste: Für sie hatte die Liste nichts anzubieten außer dem Weg dorthin.
|
||
- Rechts unten im Dialog steht *Splitbuchung löschen*. Der Knopf schickt dasselbe
|
||
Formular mit `aktion=loeschen`, verwirft alle Zeilen samt ihren Zuordnungen – auch die
|
||
Gegenseite einer Splitzeile mit Gegenkonto – und die Buchung steht danach unzugeordnet
|
||
im Kontoblatt. Er erscheint nur dort, wo es etwas aufzuheben gibt, und steht abseits
|
||
von *Speichern* und *Abbrechen*.
|
||
- Bleiben alle Zeilen leer, wird die Splitbuchung ebenso aufgehoben.
|
||
- Beide Wege achten den Monatsabschluss und lassen den Rest der Buchung unberührt.
|
||
|
||
`public/static/betrag.js` bündelt das Lesen deutscher Beträge und die Restbetragsrechnung
|
||
für Maske und Kontoblatt. `tests/23_ZuordnungTest.php` prüft Zuordnung, Splitzeilen und
|
||
Gegensicht; `tests/e2e/specs/buchungen.spec.js` die drei Wege im Browser.
|
||
|
||
## Wertpapiere: Depotbuchungen sammelweise löschen (12.08.2026)
|
||
|
||
Die Liste der Depotbuchungen räumt jetzt so auf wie das Kontoblatt – nach einem Import,
|
||
der schiefging, war das Löschen Zeile für Zeile mühsam.
|
||
|
||
**Auswahl löschen** (`Views/wertpapiere/buchungen.php`, `WertpapiereController`,
|
||
`public/index.php`)
|
||
- Ankreuzfeld je Zeile, das Feld in der Titelzeile wählt alle angezeigten Buchungen. Der
|
||
Knopf steht oben in der Aktionsleiste, nennt die Anzahl und bleibt ohne Auswahl
|
||
gesperrt; die Rückfrage nennt sie ebenfalls. Ohne JavaScript funktioniert alles weiter:
|
||
Dann ist der Knopf offen und `POST /wertpapiere/buchungen/loeschen` weist eine leere
|
||
Auswahl ab.
|
||
- Mit jeder Depotbuchung geht ihre Geldbuchung – wie beim Löschen einer einzelnen.
|
||
- **Am Bestand ist nichts nachzuziehen.** Er wird bei jedem Aufruf aus den Depotbuchungen
|
||
gerechnet und gibt die Löschung von selbst wieder her, ebenso realisierter Gewinn,
|
||
Veräußerungen und Erträge.
|
||
- Der eingestellte Filter (Depot, Wertpapier, Vorgang) hängt am Ziel des Formulars,
|
||
deshalb steht danach wieder dieselbe Ansicht da. Das gilt jetzt auch für das *Löschen*
|
||
je Zeile, das bisher zur ungefilterten Liste zurücksprang.
|
||
|
||
`tests/22_DepotbuchungenLoeschenTest.php` prüft Geldseite, Bestand und Auswertungen nach
|
||
dem Löschen; `tests/e2e/specs/module.spec.js` den Ablauf im Browser.
|
||
|
||
## Wertpapiere: Depotbestand aus einer CSV-Datei übernehmen (12.08.2026)
|
||
|
||
Ein Depot, das nie im FinanzManager geführt wurde, lässt sich jetzt in einem Zug
|
||
einlesen – aus der Bestandsliste, die die Bank zum Herunterladen anbietet.
|
||
|
||
**Neuer Knopf *Import*** (`Views/wertpapiere/index.php`, `Views/wertpapiere/import.php`)
|
||
- Steht auf der Bestandsseite neben *Depotbuchung erfassen*. Der Ablauf ist zweistufig:
|
||
hochladen, Vorschau prüfen, übernehmen. Die Vorschau zeigt je Position Stück, beide
|
||
Kurse, Einstand, Wert und ob sie neu ist oder schon einmal importiert wurde.
|
||
- Vor der Übernahme lassen sich Depot, Buchungsdatum und Verrechnungskonto noch ändern.
|
||
|
||
**Der Import** (`Services\DepotbestandImport`, `WertpapiereController`)
|
||
- Erwartet die Spalten Wertpapierbezeichnung, WKN, ISIN, Einstiegskurs, Bewertungskurs,
|
||
Stückzahl und Assetklasse; Trennzeichen und Zeichensatz erkennt er selbst. Beträge wie
|
||
`"1.471,88 €"` und Bruchstückzahlen wie `55,6863` werden gelesen, wie sie kommen.
|
||
- **Die Vergangenheit fehlt im Export.** Er beschreibt nur einen Stichtag. Je Position
|
||
entsteht deshalb *eine* Depotbuchung: ein Kauf zum Stichtag über die volle Stückzahl zum
|
||
Einstiegskurs. Der Einstand stimmt damit auf den Cent; frühere Käufe, Verkäufe und
|
||
Dividenden bleiben leer.
|
||
- **Der Bewertungskurs wird als Kurs des Stichtags hinterlegt.** Ohne ihn stünde der
|
||
frisch übernommene Bestand sofort wieder ohne Bewertung da. Am echten Export
|
||
nachgerechnet: 33.176,12 € Einstand, 47.120,25 € Wert – die Bank weist zehn Cent
|
||
weniger Gewinn aus, was an ihren auf zwei Stellen gerundeten Kursen liegt.
|
||
- **Kein Geldfluss.** Ein Verrechnungskonto ist wählbar, bleibt aber vorbelegt leer: Das
|
||
Geld ist vor Jahren geflossen, eine Belastung zum heutigen Tag würde das Girokonto
|
||
verfälschen.
|
||
- Ohne ausdrückliche Wahl findet der Import das Depot über die **Kontonummer**, die zur
|
||
Depotnummer der Datei passt. Ein `import_hash` je Position verhindert, dass dieselbe
|
||
Datei zweimal denselben Bestand anlegt.
|
||
- Assetklassen werden übersetzt: *Aktien* → Aktie, *ETFs* und *Fonds* → Fonds,
|
||
*Anleihen* → Anleihe, *Zertifikate* → Zertifikat.
|
||
|
||
**Neue Herkunft „CSV-Import“** (`Models\Buchung`)
|
||
- `Buchung::QUELLEN` kennt jetzt `csv`; Depot- und Geldbuchungen aus dem Bestandsimport
|
||
sind damit als solche erkennbar und fallen nicht mehr auf „manuell erfasst“ zurück.
|
||
|
||
`tests/21_DepotbestandImportTest.php` prüft das an einem Ausschnitt des echten Exports.
|
||
|
||
## Online-Banking: Umsätze, die niemand mehr zu sehen bekam (12.08.2026)
|
||
|
||
Drei Fehler, die zusammen dazu führten, dass abgerufene Umsätze spurlos verschwanden:
|
||
Der Abruf meldete sie als schon vorhanden, im Kontoblatt stand nichts, und in der
|
||
Vorschlagsliste tauchten sie ebenfalls nicht auf.
|
||
|
||
**Eine gelöschte Buchung gibt ihren Umsatz wieder frei** (`Models\Buchung`, `Models\Bankumsatz`)
|
||
- Der Fremdschlüssel setzte beim Löschen zwar `bank_umsaetze.buchung_id` auf NULL, ließ den
|
||
Status aber auf *übernommen* stehen. Der Umsatz war damit unsichtbar: keine Buchung, kein
|
||
Eintrag in der Vorschlagsliste, und beim nächsten Abruf über den Fingerabdruck als schon
|
||
vorhanden übersprungen. `Buchung::loeschen()` setzt ihn jetzt vorher auf *offen* zurück.
|
||
- `Bankumsatz::verwaisteOeffnen()` fängt die Wege ab, die daran vorbeiführen: Wird ein Konto
|
||
gelöscht, räumt der Fremdschlüssel seine Buchungen per CASCADE ab – und Kaskaden lösen in
|
||
MariaDB keine Trigger aus. Der Aufruf steht vor dem Abruf und vor der Vorschlagsliste,
|
||
damit sich dieser Zustand von selbst wieder auflöst.
|
||
|
||
**Ein Bankkonto entsteht nicht mehr doppelt** (`Models\Bankzugang`)
|
||
- Gesucht wird über die **Kontonummer**, nicht über IBAN und Kontonummer zusammen. Manche
|
||
Banken liefern die IBAN nicht bei jedem Dialog mit – die Comdirect nennt sie beim
|
||
Kontenabruf, beim Umsatzabruf aber nicht. So entstand beim zweiten Abruf ein zweiter
|
||
Eintrag fürs selbe Konto: einmal mit IBAN, einmal ohne, mit eigener Zuordnung und eigenem
|
||
Fingerabdruck. Jeder Umsatz stand daraufhin doppelt da.
|
||
- Liefert die Bank die IBAN erst später, wird sie am vorhandenen Eintrag nachgetragen.
|
||
|
||
**Der Fingerabdruck hängt am Bankkonto, nicht an der IBAN** (`Models\Bankumsatz`)
|
||
- `Bankumsatz::externId()` nimmt die `bank_konto_id` statt IBAN oder Kontonummer. Welche
|
||
Kennung eine Bank liefert, wechselt von Dialog zu Dialog; mit ihr kippte der Fingerabdruck,
|
||
und derselbe Umsatz kam ein zweites Mal herein. Die Kennung der Zeile in `bank_konten`
|
||
steht dagegen fest, solange es den Bankzugang gibt.
|
||
|
||
## Kontoblatt: Buchungen sammelweise löschen, Meldungen ohne Sprung (12.08.2026)
|
||
|
||
**Mehrfachauswahl im Kontoblatt** (`Views/buchungen/kontoblatt.php`, `BuchungenController`)
|
||
- Links vor dem Datum steht je Buchung ein Ankreuzfeld, in der Titelzeile eines für alle
|
||
gerade angezeigten Buchungen. Es zeigt auch den Zwischenstand an, wenn nur ein Teil
|
||
ausgewählt ist.
|
||
- Neben *Kontenübersicht* löscht **Auswahl löschen** die angekreuzten Buchungen nach einer
|
||
Rückfrage, die die Anzahl nennt. Ohne Auswahl bleibt der Knopf gesperrt; ohne JavaScript
|
||
weist der Controller eine leere Auswahl ab.
|
||
- Die Gegenseite einer fremden Umbuchung bekommt kein Ankreuzfeld – sie gehört dem
|
||
Ursprungskonto und wird auch weiterhin nur dort gelöscht.
|
||
- Nach dem Löschen erscheint wieder dieselbe Ansicht: Filter und Seite hängen am Ziel des
|
||
Formulars und werden aus bekannten Feldern neu zusammengesetzt, nicht übernommen.
|
||
|
||
**Statusmeldungen liegen über der Ansicht** (`Views/layout/meldungen.php`)
|
||
- Die Meldungen schieben sich von ganz oben ins Bild, statt im Inhalt Platz zu belegen. Der
|
||
Inhalt bleibt beim Ein- und Ausblenden an Ort und Stelle – vorher rückte die halbe Seite
|
||
auf und beim Verschwinden wieder zurück.
|
||
- Eine Meldung ist so breit wie ihr Text und bricht erst bei 80 % Fensterbreite um.
|
||
- Erfolgsmeldungen gehen nach fünf Sekunden von selbst, alle Meldungen lassen sich
|
||
anklicken – eine stehenbleibende Fehlermeldung verdeckte sonst die Werkzeugleiste.
|
||
- Beide Rahmen (`kopf.php`, `schlicht.php`) binden dieselbe Teilansicht ein.
|
||
|
||
## Online-Banking: Kontenzuordnung als dritter Schritt, Umsätze buchen sich selbst (12.08.2026)
|
||
|
||
**Einrichtung in drei Schritten** (`Views/banking/konten.php`, Migration `010_kontenzuordnung.sql`)
|
||
- Nach Zugangsdaten (1) und TAN-Verfahren (2) folgt die **Kontenzuordnung** (3): Welches
|
||
Bankkonto gehört zu welchem eigenen Konto? Die Liste kommt frisch von der Bank – das
|
||
Übernehmen des Verfahrens startet die Abfrage gleich mit, samt TAN-Maske, wenn die Bank
|
||
eine verlangt.
|
||
- Fehlt ein passendes eigenes Konto, legt *neues Konto anlegen* es an: IBAN, Kontonummer
|
||
und Bank stehen schon fest, einzutragen bleiben Name, Kontoart und Anfangssaldo. Letzterer
|
||
deckt die Zeit ab, die der Abruf nicht mehr hergibt.
|
||
- Die Zuordnung ist von der Zugangsmaske dorthin umgezogen – dort stand sie in einer
|
||
Tabelle, die nur zuordnen, aber nichts anlegen konnte. Geblieben ist eine Übersicht mit
|
||
Verweis.
|
||
- `bank_umsaetze.bank_konto_id` merkt sich, von welchem Bankkonto ein Umsatz stammt. Wird
|
||
die Zuordnung erst nach einem Abruf getroffen, ziehen die **offenen** Umsätze mit;
|
||
übernommene bleiben, wo sie sind – ihre Buchung steht bereits in einem Kontoblatt.
|
||
- Der Abruf kennt dafür einen kurzen Modus (`FinTsAbruf::starten($id, 'konten')`), der nach
|
||
der Kontenabfrage anhält. Dieselbe Zustandsmaschine, dieselbe TAN-Behandlung.
|
||
- Am Ende der Einrichtung geht es zurück in die **Übersicht der Bankverbindungen** – dort
|
||
steht die neue Verbindung fertig zwischen den anderen. Die Zugangsmaske böte nur noch an,
|
||
was gerade durchlaufen wurde. Dasselbe gilt beim vorzeitigen Verlassen über *Später
|
||
einstellen* und *Später zuordnen*; einzeln aufgerufen bleibt die Zuordnung auf ihrer Seite.
|
||
|
||
**Abgerufene Umsätze buchen sich selbst** (`Services\Bankabgleich`)
|
||
- Neue Einstellung *Abgerufene Umsätze selbst buchen* (`banking_automatisch_buchen`,
|
||
Vorgabe an). Nach jedem Abruf – und nach dem Speichern der Kontenzuordnung – landen die
|
||
Umsätze im zugeordneten Konto: verbunden mit einer schon erfassten Buchung, wenn eine
|
||
passt, sonst als neue Buchung mit der Kategorie aus der Regel, sonst ohne.
|
||
- Der Lauf läuft **einmal am Ende**, nicht je Konto: So sieht er alle Umsätze und kann jeden
|
||
mit der Buchung verbinden, die zu ihm passt.
|
||
- Ein einzelner Umsatz bricht den Lauf nicht ab. Ein abgeschlossener Monat oder ein
|
||
doppelter Fingerabdruck betrifft nur ihn; er bleibt offen und steht weiter in der
|
||
Vorschlagsliste. Die Meldung nennt die ersten Fälle.
|
||
- Umsätze ohne zugeordnetes Konto bleiben liegen – ohne Konto gibt es kein Kontoblatt, in
|
||
das sie gehörten.
|
||
- *Alles übernehmen* in der Vorschlagsliste bleibt zurückhaltender und lässt liegen, was
|
||
keine Regel trifft: Wer den Dialog öffnet, will die Zuordnung selbst treffen. Beide Wege
|
||
teilen sich jetzt denselben Dienst.
|
||
|
||
**Kontenübersicht: fett heißt „hier liegt Arbeit“**
|
||
- Konten mit Buchungen aus dem laufenden Jahr ohne Kategorie stehen **fett**
|
||
(`Buchung::unkategorisierteJeKonto()`), mit der Zahl im Titel. Dieselbe Zählweise wie im
|
||
Cockpit: keine Kategorie, kein Gegenkonto, nicht gesplittet – eine Umbuchung oder eine
|
||
Splitbuchung ist zugeordnet.
|
||
- Das ist die Gegenprobe zum automatischen Buchen: Die Umsätze stehen sofort im Kontoblatt,
|
||
und die Übersicht zeigt, wo die Kategorie noch fehlt.
|
||
|
||
## 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.
|
||
|
||
**Wahl des TAN-Verfahrens** (`Services\FinTsVerfahren`, Migration `009_tan_verfahren.sql`)
|
||
- **Einrichtung in zwei Schritten**: Zugangsdaten erfassen und mit *Weiter* speichern, dann
|
||
das TAN-Verfahren. Vorher lässt sich die Bank nicht fragen – ohne gespeicherte
|
||
Zugangsdaten gibt es keine Verbindung.
|
||
- Der zweite Schritt holt die Liste beim Aufruf selbst (`getTanModes()`, ohne Anmeldung und
|
||
ohne TAN); ein Knopf davor wäre ein Schritt, dessen Ergebnis feststeht. Ein erneuter
|
||
Anlauf steht daneben. TAN-Medien werden nur für die Verfahren geholt, die eines brauchen.
|
||
- Das Ergebnis liegt in der Sitzung, auch ein gescheitertes – sonst liefe jeder
|
||
Seitenaufruf wieder in dieselbe Zeitüberschreitung. Die sind für die Abfrage kürzer
|
||
gesetzt als in der Bibliothek (8/20 statt 15/30 Sekunden), weil sie an einem
|
||
Seitenaufruf hängt.
|
||
- Scheitert die Abfrage, erklärt die Seite, wo der Fehler meist steckt, und verlinkt zurück
|
||
auf die Zugangsdaten; überspringen lässt sich der Schritt ebenfalls.
|
||
- Abgefragt statt eingetippt: Die Kennungen sind bankspezifisch, 913 heißt nicht überall
|
||
chipTAN QR. Der Name wird mitgespeichert (`tan_verfahren_name`), damit das Formular ihn
|
||
ohne Bankverbindung zeigen kann.
|
||
- Beim Übernehmen wird gegen die abgefragte Liste geprüft; ein Verfahren ohne Medienzwang
|
||
bekommt kein Medium angehängt, weil manche Banken den Dialog sonst ablehnen.
|
||
- Ohne Wahl entscheidet weiterhin das erste gemeldete Verfahren – diese Notwahl wird
|
||
festgehalten, eine ausdrückliche Wahl dagegen **nie überschrieben**.
|
||
- Behoben: Das Speichern des Zugangsformulars setzte Verfahren und TAN-Medium zurück, weil
|
||
beide Felder darin nicht vorkommen. Sie werden jetzt wie die PIN unter Vorbehalt gestellt.
|
||
- Abruf und Überweisung benutzen dieselbe Wahl; die doppelte Auswahllogik in
|
||
`FinTsUeberweisung` ist entfallen.
|
||
|
||
**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
|