SepaXml erzeugt jetzt zusätzlich pain.001.001.09 (Dt-Element für den
Ausführungstermin, BICFI statt BIC, UETR je Zahlung) für Banken, die das
ältere pain.001.001.03 nicht mehr führen. FinTsUeberweisung fängt außerdem
die UnsupportedException ab, die php-fints wirft, wenn die Bank
Namensabgleich-Volltreffer und TAN-Anfrage in einem Antwortsatz kombiniert,
und lässt den vorhandenen automatischen Volltreffer-Namensabgleich den Rest
erledigen.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Über der Tabelle der Bankverbindungen ruft ein neuer Knopf jede aktive
Bankverbindung einzeln nacheinander ab. Die Warteschlange (Services\Sammelabruf)
übersteht TAN-Runden in der Sitzung; nach der Freigabe geht es mit der nächsten
Bank weiter. Die TAN-Maske zeigt den Stand und erlaubt, eine Bank zu
überspringen. Am Ende steht je Bankverbindung eine eigene Meldung.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Jeder Legendeneintrag der Cockpit-Kacheln führt zu den Buchungen, aus denen
sich sein Wert zusammensetzt: Kategorien, „Nicht kategorisiert“ und „Übrige“
in „Meine Ausgaben“, Einnahmen/Ausgaben/Saldo in „Einnahmen und Ausgaben“,
Konten der Vermögensentwicklung ins Kontoblatt. Ziel ist der Drilldown der
Auswertungen mit dem Filter der Kachel; zeilenbuchungen() kennt dafür einen
Art-Filter, „alle Bewegungen“ und mehrere Kategorien.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Bei Gruppierung nach Konto oder Empfänger landen alle Zeilen einer
Splitbuchung ohnehin in derselben Gruppe, erschienen dort aber trotzdem
einzeln mit ihrem Teilbetrag statt dem Gesamtbetrag der Buchung – wer nur
eine Zeile sah, hielt sie für den vollständigen Betrag. Bei Kategorie oder
Klasse bleibt es beim bisherigen Verhalten, weil eine Splitbuchung dort auf
mehrere Gruppen verteilt ist und die Gruppensummen sonst nicht mehr zum
Summenbericht passten.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Der Überweisungsdialog bietet das Häkchen „Echtzeitüberweisung“. SEPA
behandelt sie als eigenes Verfahren: Über FinTS geht der Auftrag als HKIPZ
(SendSEPARealtimeTransfer) statt als HKCCS hinaus, die pain.001-Nachricht
trägt die Zahlungsart LclInstrm/Cd = INST (nur im ISO-Format
pain.001.001.03). Eine Echtzeitüberweisung hat keinen Ausführungstermin;
bietet die Bank sie nicht an, scheitert der Auftrag mit klarer Meldung,
eine stille Umwandlung in eine gewöhnliche Überweisung ist abgeschaltet.
Neue Spalte zahlungsauftraege.echtzeit (Migration 019), Test 43 und ein
E2E-Test für das Sperren des Termins.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Der gesamte FinTS-Dialog (gesendete und empfangene Meldungen) wird je Durchlauf
in bank_protokolle gespeichert, im Erfolgs- wie im Fehlerfall. Im Zahlungsverkehr
öffnet der Status das Protokoll der Überweisung als modalen Dialog; unter
Online-Banking › Protokoll gibt es das Gesamtprotokoll mit Filter nach Bank,
Datum und Freitext.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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, und kein Bildschirmfoto eines fehlgeschlagenen Tests war mehr
zu gebrauchen.
scripts/e2e-aufraeumen.php räumt weg, 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. Der Nutzer selbst bleibt stehen, und er muss 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.
Aufgerufen wird es vor dem Lauf, nicht danach – ein abgebrochener Lauf
hinterlässt sonst einen Bestand, an den niemand mehr denkt, und so bleiben die
Daten des letzten Laufs bis zum nächsten für die Fehlersuche stehen.
Das 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. scripts/e2e-grundbestand.php stellt deshalb 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.
Test 41 prüft jede dieser Grenzen einzeln und nennt den Test, der daran hängt.
87 E2E-Tests grün, in 1,3 statt 2,7 Minuten – die Listen rendern keine
Tausenden Altzeilen mehr.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AQ57wQWZ4xKHcKmp26bzgr
Beim Senden suchte FinTsUeberweisung unter den Konten, die die Bank meldet,
nach der IBAN aus dem Konto selbst. Die ist in der Kontenverwaltung 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 dem Konto zugewiesen ist. Dort steht die IBAN, die die Bank
selbst gemeldet hat, und die Kontonummer für die Häuser, die keine IBAN
liefern. Die IBAN am Konto bleibt als Weg bestehen, ebenso der Fall, dass die
Bank nur ein einziges Konto führt.
passendesKonto() ist dafür public static geworden und nimmt den Zugang als
Parameter – so lässt sich die Wahl mit echten SEPAAccount-Objekten prüfen,
ohne eine Bank zu brauchen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013iURiWYZJQYVGQNXFFGyWu
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 wartet jetzt auf das Ergebnis, solange die Bank es braucht.
Stimmt der Name überein, wird selbst bestätigt und direkt zur TAN gegangen –
eine Rückfrage ohne Erkenntnis erspart das dem Nutzer. Weicht der Name ab, ist
er nur nahezu gleich oder konnte die Bank nicht prüfen, kommt die neue Maske
zahlungsverkehr/vop.php: Ergebnis in einem deutschen Satz, Aufklärungstext der
Bank als Klartext, Empfänger und IBAN aus dem Auftrag und – sofern die Bank ihn
nennt – der bei ihr hinterlegte Kontoinhaber.
Ohne Ausführungstermin steht jetzt das Platzhalterdatum 1999-01-01 in der
Nachricht. Mit dem heutigen Datum wurde daraus eine terminierte Überweisung
(HKCSE statt HKCCS), die die Bank mit "Die minimale Vorlaufzeit beträgt 1 Tage"
zurückwies.
Der FinTS-Fork steht auf v4.1.3 mit zwei Eingriffen: Die schrittweise Lieferung
des Prüfberichts wird erst abgelehnt, wenn sie wirklich eintritt, und der beim
Close Match gemeldete Kontoinhaber wird an die Anwendung durchgereicht. Beide
liegen zusätzlich auf eigenen Branches für PRs an das Upstream-Projekt.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013iURiWYZJQYVGQNXFFGyWu
Die Nachricht wurde fest als pain.001.001.03 erzeugt. Banken, die nur das
ältere DK-Format pain.001.003.03 führen, weisen die Überweisung damit schon
beim Einreichen ab, noch vor der TAN.
FinTsUeberweisung liest die unterstützten Formate nach der Anmeldung aus dem
Segment HISPAS; SepaXml::schemaWaehlen() bestimmt daraus den Namensraum –
bevorzugt ISO, sonst DK. Für die Einzelüberweisung sind beide Fassungen im
Aufbau gleich, es genügt der andere Namensraum. Das DK-Schema liegt der
Testsuite bei, damit die Nachricht wirklich dagegen geprüft wird.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013iURiWYZJQYVGQNXFFGyWu
Der Verbindungstest meldete „Das Bargeld-Tool kennt die Schnittstelle nicht
(HTTP 404)“, sobald die hinterlegte Adresse den Schnittstellenpfad schon
enthielt: Der Finanzmanager hängt ihn selbst an und fragte damit unter
/api/v1/api/v1/status an.
Die Adresse aus der Browserzeile zu kopieren ist der naheliegende Weg, und
der Pfad ist dort mit dabei. Ein abschließendes /api oder /api/v1 kürzt
BargeldTool::basisadresse() deshalb weg – beim Speichern und noch einmal im
Konstruktor, damit auch schon gespeicherte Adressen greifen. Ein
Unterverzeichnis davor bleibt erhalten, ein Hostname wie apianbieter.example
bleibt unangetastet.
Bereits gespeicherte Adressen räumt Migration 017 auf.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Beim Zuweisen einer Kategorie fragt der Finanzmanager beim Bargeld-Tool
(FSCash) nach, ob es zum Betrag dort schon eine Buchung gibt.
Eingerichtet wird das unter Verwaltung → Bargeld-Tool: Adresse,
Zugangsdaten (Passwort AES-verschlüsselt), Suchzeitraum und die Auswahl
der abzugleichenden Kategorien. Die Vormerkung steht als
`bargeld_abgleich` an der Kategorie selbst, nicht als Nummernliste in den
Einstellungen – so verschwindet sie mit der Kategorie.
Geprüft wird nur, was geprüft werden soll: Kategorie an der Buchung,
Kategorie vorgemerkt, Adresse konfiguriert. Fällt eines weg, verschwindet
der Status. Gesucht wird rückwärts ab dem Buchungsdatum, weil Bargeld
ausgegeben wird, bevor der Umsatz auf dem Konto ankommt.
Im Kontoblatt steht das Ergebnis zwischen Klasse und Betrag: grauer Kreis,
grüner Haken, rotes X, Ausrufezeichen. Das Ausrufezeichen behält die
Textfarbe des Farbschemas – der Fehler liegt beim Abruf, nicht bei der
Buchung. Ohne hinterlegte Adresse entfällt die Spalte.
Ein Fehlschlag ist ein Ergebnis, kein Abbruch: Die Kategorie wird auch
dann gespeichert, wenn das Bargeld-Tool schweigt.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Zwei Features an derselben Stelle, die im Arbeitsverzeichnis
nebeneinander lagen und sich dieselben Dateien teilen (Routen,
BankingController, Bankzugang, Zugangsformular, Dokumentation) –
getrennte Commits ließen sich daraus nur mit einem kaputten
Zwischenstand schneiden.
PayPal als zweite Art von Bankverbindung
----------------------------------------
„Bankverbindung anlegen“ führt zur Wahl der Art: HBCI/FinTS oder
PayPal (bank_zugaenge.typ, Migration 014). Die Abfrage läuft über die
Classic-NVP-API (Services\PaypalNvp, TransactionSearch und GetBalance).
Kein Dreischritt, da PayPal genau ein Guthabenkonto führt –
Zugangsdaten und Kontenzuordnung stehen in einem Formular. Konten,
Umsätze, Protokoll, Regeln und Abgleich bleiben dieselben.
Bankleitzahlen-Verzeichnis
--------------------------
Verwaltung → Bankleitzahlen (/verwaltung/bankinstitute, Migration 015)
nimmt die CSV-Institutsliste der Deutschen Kreditwirtschaft auf und
beantwortet damit die eine Angabe, die beim Anlegen einer
FinTS-Verbindung regelmäßig fehlt: den PIN/TAN-Zugangspunkt.
Reine Nachschlagedaten ohne benutzer_id; ein Import ersetzt den
gesamten Bestand, statt ihn zu ergänzen. „Nur prüfen“ und „Übernehmen“
sind zwei Knöpfe an derselben Datei – abgelegt wird sie nicht, ihr
Inhalt steht ja in der Tabelle.
Services\FintsInstituteImport nimmt der Datei ihre Eigenheiten ab:
ISO-8859-1 statt UTF-8, ein Institutsname über zwei Zeilen in
Anführungszeichen, dieselbe Bankleitzahl vielfach (eine Zeile je
Filiale), Hunderte leerer Zeilen am Ende, Text statt Adresse, wo es
keinen PIN/TAN-Zugang gibt. Spalten werden über ihre Überschrift
angesprochen; fehlen BLZ und Institut, bricht der Import ab.
Gesucht wird über Bankleitzahl, BIC, Name und Ort in einem Zug: „1822“
steht bei der 1822direkt im Namen und bei der Frankfurter Sparkasse im
BIC, in keiner der beiden Bankleitzahlen.
Aus der Trefferliste führt „Verbindung anlegen“ ins gefüllte
FinTS-Formular. Die FinTS-Adresse ist damit kein Pflichtfeld mehr –
bleibt sie leer, wird sie zur Bankleitzahl nachgeschlagen. Findet auch
das nichts, weist Bankzugang den Zugang ab, statt ihn ohne Gegenstelle
zu speichern.
Tests: 31_PaypalZugangTest, 32_BankinstituteTest,
e2e/specs/banking.spec.js, e2e/specs/bankinstitute.spec.js.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Aus der Kachel QIF-Import wird Import/Export mit je einem Knopf für beide
Richtungen; davor steht die Wahl des Dateiformats (QIF oder CSV), die als
Dialog und ohne Skript als eigene Seite funktioniert.
- CsvBuchungsImport/CsvTabelle: Buchungen samt Splitzeilen, Spalten über die
Kopfzeile, dieselbe Dublettenerkennung und der Trockenlauf wie beim QIF.
- BuchungsExport/ExportController: Filter nach Zeitraum, Konto, Kategorie und
Klasse; die Datei entsteht im Speicher, ein Exportverzeichnis gibt es nicht.
- Housekeeping: Hochgeladene Importdateien und data/tmp werden nach der
eingestellten Frist gelöscht (Vorgabe 14 Tage, 0 schaltet ab).
Migration 013, Test 29 sowie E2E-Spec importexport.spec.js.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Financial Modeling Prep hat die /api/v3/-Adressen zum 31.08.2025 abgeschaltet;
sie antworten mit HTTP 403. Der Abruf nutzt jetzt /stable/quote und
/stable/search-isin und meldet den Klartext des Dienstes, statt jeden 403 als
abgelehnten Schlüssel auszugeben. HTTP 402 ist ein eigener Fall – im
Gratistarif fehlen dort ISIN-Suche und deutsche Börsenplätze.
Neu ist Yahoo Finanzen: ohne Schlüssel, ohne Kontingent, dafür muss das Kürzel
samt Handelsplatz in den Stammdaten stehen. Eine offene Schnittstelle gibt es
nicht mehr (query1.finance.yahoo.com antwortet ohne Sitzung mit HTTP 429, auch
mit Cookie und Crumb), gelesen wird deshalb die Kursseite selbst.
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. Das eingebettete JSON bleibt ungenutzt: Es gehört zu
einem Fremd-Widget und liefert auf jeder Seite denselben papierfremden Wert.
Eine Sekunde Abstand zwischen zwei Seiten, bis zu drei Versuche bei HTTP 503.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Der Name in Bestandsübersicht, Depotbuchungen und Stammdaten führt jetzt auf
/wertpapiere/papier/{id}: Kennzahlen über die ganze Historie, Stammdaten und
Kurs, Bestand je Depot, Wertentwicklung und die Buchungen eines Zeitraums.
Depot::bestand() und Depot::entwicklung() nehmen dafür eine Wertpapiernummer
entgegen – die Detailseite rechnet nicht selbst, sondern bekommt dieselben
Zahlen wie die Übersicht. Neu sind Wertpapier::kurseckwerte() und
Depotbuchung::datumsgrenzen().
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Der Bankabruf bringt die Wertpapierabrechnung als Umsatz auf dem Girokonto, und
erst dort entscheidet sich, dass es ein Kauf war. Wer die Buchung einem Depot
zuordnete, buchte bisher eine Umbuchung – ohne Stückzahl, ohne Kurs, ohne
Einstand. Die Zuordnungsliste im Kontoblatt kennt jetzt neben Kategorie und
Gegenkonto eine dritte Gruppe "Wertpapiergeschäft im Depot", die statt zu
speichern die Wertpapiermaske öffnet; ohne JavaScript führt derselbe Weg auf
/buchungen/{id}/wertpapier.
Die Maske ist auf den "Wertpapier kaufen"-Dialog des FinanzManagers
zugeschnitten: vorne Vorgangsart, Datum, Wertpapier, Stück und Kurs, dahinter
hinter dem Knopf "Kosten …" die Nebenkosten nach Arten – sechs Gebühren und
sechs Steuern. ISIN und WKN werden aus dem Verwendungszweck gelesen, ein
bekanntes Papier ist damit vorausgewählt. Ein Abfluss vom Konto kann nur ein
Kauf sein, ein Zufluss ein Verkauf oder eine Dividende.
Der Buchungsbetrag ist die feste Größe: Die Nebenkosten werden aus ihm
herausgerechnet, was übrig bleibt ist der Kurswert und damit der
Einstandspreis. Im Kontoblatt entsteht eine Splitbuchung mit je einer Zeile pro
belegter Kostenart und dem Rest als Umbuchung ins Depot; 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) – wird sie gelöscht, bleibt die Buchung der
Bank stehen und ist nur wieder unzugeordnet.
Die Kategorien der Kostenarten liegen im Einstellungsdialog unter "Wertpapiere",
einmalig je Art; für "Sonstige Kosten" ist die Einstellung nur die Vorgabe und
im Dialog überschreibbar. Arten ohne Kategorie werden zu Splitzeilen ohne
Zuordnung, nicht zu einem Fehler.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Der Abrufzeitraum kannte bisher nur Spannen. Der Stichtag steht jetzt an zwei
Stellen: als Vorgabe der Bankverbindung (neuer Abrufzeitraum "Ab Stichtag" mit
Datum in bank_zugaenge.abruf_stichtag) und als einmalige Angabe neben "Umsätze
abrufen", die der Vorgabe vorgeht und nichts speichert.
Der einmalige Stichtag liegt im Abrufzustand, nicht in der Datenbank: Nach einer
TAN-Runde läuft der Abruf im nächsten Request weiter und muss dort denselben
Zeitraum holen wie beim Start.
"Ab Stichtag" ohne Datum und unlesbare Datumsangaben werden abgewiesen; ein
Stichtag in der Zukunft wird auf heute gesetzt, statt den Abruf leer ausgehen zu
lassen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Der Import kannte bisher nur den Zeitraum als Filter – ein Export ließ sich
nur ganz einlesen. Neu ist die Option "konten": im Dialog eine Ankreuzliste,
die erst nach dem Trockenlauf erscheint (vorher ist die Datei ungelesen), auf
der Kommandozeile --konto=NAME und --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 sonst fremde
Salden. Die Fingerabdrücke entstehen jetzt vorab in einem eigenen Durchgang:
Der laufende Zähler für gleiche Fingerabdrücke hing sonst an der Auswahl, und
dieselbe Buchung wäre beim zweiten Lauf mit anderer Auswahl doppelt angelegt
worden.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ein Depot, das nie im FinanzManager geführt wurde, ließ sich bisher nur Buchung
für Buchung nachtragen. `Services\DepotbestandImport` liest stattdessen die
Bestandsliste, die die Bank zum Herunterladen anbietet (erprobt mit der DKB):
Kopfzeile mit 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 liest er, wie sie
kommen.
Der Export kennt keine Historie – er beschreibt 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 und lassen sich nachtragen.
Der Bewertungskurs wird als Kurs des Stichtags hinterlegt, sonst 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.
Der Ablauf ist zweistufig – hochladen, Vorschau prüfen, übernehmen. 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. `Buchung::QUELLEN` kennt dafür
jetzt die Herkunft `csv`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Kontenzuordnung als dritter Einrichtungsschritt: Welches Bankkonto gehört zu
welchem eigenen Konto? Umsätze merken sich über `bank_konto_id`, woher sie
stammen, und ziehen mit, wenn die Zuordnung erst nach einem Abruf fällt.
Abgerufene Umsätze buchen sich auf Wunsch selbst (`Services\Bankabgleich`).
Im Kontoblatt lassen sich Buchungen sammelweise löschen; Statusmeldungen liegen
jetzt über der Ansicht, statt den Inhalt springen zu lassen.
Dazu drei Fehler, die zusammen dafür sorgten, dass abgerufene Umsätze spurlos
verschwanden – der Abruf meldete sie als vorhanden, das Kontoblatt zeigte
nichts, und in der Vorschlagsliste standen sie ebenfalls nicht:
- Eine gelöschte Buchung ließ ihren Umsatz auf „uebernommen“ stehen. Der
Fremdschlüssel setzte nur `buchung_id` auf NULL. `Buchung::loeschen()` gibt
den Umsatz jetzt vorher frei, `Bankumsatz::verwaisteOeffnen()` fängt die Wege
ab, die daran vorbeiführen – Kaskaden lösen in MariaDB keine Trigger aus.
- Ein Bankkonto entstand doppelt, wenn die Bank die IBAN nicht bei jedem Dialog
mitliefert. Gesucht wird jetzt über die Kontonummer; eine nachgelieferte IBAN
wird am vorhandenen Eintrag nachgetragen.
- Der Fingerabdruck hing an der IBAN und kippte mit ihr. Er nimmt jetzt die
`bank_konto_id`, die fest steht, solange es den Bankzugang gibt.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Die Bank lässt sich erst nach ihren TAN-Verfahren fragen, wenn die
Zugangsdaten gespeichert sind. Das Anlegen führt deshalb über „Weiter“
direkt auf die Verfahrensseite, und die holt die Liste selbst – ein
Knopf davor wäre ein Schritt, dessen Ergebnis feststeht.
Scheitert die Abfrage, bleibt das Ergebnis in der Sitzung, damit nicht
jeder Seitenaufruf wieder in dieselbe Zeitüberschreitung läuft; die ist
für die Abfrage kürzer gesetzt als in der Bibliothek.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Bisher entschied das erste Verfahren, das die Bank meldet – wer chipTAN
nutzt, kam über eine App-Freigabe nicht weiter. Die Wahl steht jetzt auf
einer eigenen Seite am Zugang.
Abgefragt statt eingetippt: Die Kennungen sind bankspezifisch, und
getTanModes() kommt ohne Anmeldung aus, verlangt also selbst keine TAN.
Behoben: Das Speichern des Zugangsformulars setzte Verfahren und
TAN-Medium zurück, weil beide Felder darin nicht vorkommen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Damit ist der geplante Funktionsumfang beisammen.
Phase 4 abgeschlossen
- Konfigurierbare Cockpit-Kacheln; abgewählte werden nicht berechnet
- Einnahmen/Ausgaben je Monat als Balkenpaare mit Saldolinie, aufgesetzt auf
den Summenbericht statt auf eine eigene Abfrage
- Meine Ausgaben als Ring auf der obersten Kategorieebene
- Offene Vorgänge aus allen Modulen
Phase 5 – Online-Banking
- nemiah/php-fints als Fork auf Codeberg, psr/log direkt eingebaut: die
Anwendung hängt an keinem fremden Update-Zyklus
- PIN AES-256-GCM-verschlüsselt; Schlüssel aus APP_KEY oder aus
data/schluessel/app_key, den der Entrypoint einmalig erzeugt
- TAN-Dialog über mehrere Requests (FinTs::persist plus serialisierte Aktion
in der Sitzung) – ein wartender Prozess wäre mit php-fpm der falsche Weg
- Abgleich statt Direktbuchung: verbinden, übernehmen oder verwerfen
- Regeln mit vier Vergleichsarten, Betragsgrenzen und Split-Schema
Phase 6 – Wertpapiere
- Depotbuchung und Geldbuchung als zwei Seiten eines Geschäfts; betrag ist
der Bruttowert, Nebenkosten stehen daneben
- Einstand nach gleitendem Durchschnitt, realisierte Gewinne, Erträge
- Kursabruf über Leeway oder FMP, täglicher Lauf per Cron
- QIF-Import für !Type:Invst
Dabei behoben: Der Import buchte den Geldfluss jedes Wertpapiergeschäfts als
Abgang – auch bei Verkäufen und Dividenden; und er erkannte das
Verrechnungskonto nicht, wenn dem Konto im L-Feld eine Kategorie vorangeht
(bei Verkäufen also immer).
Phase 7 – Nebenmodule und Zahlungsverkehr
- Buchungsvorlagen inklusive QIF-Import, Splits werden mitskaliert
- Regelmäßige Buchungen mit Fälligkeitslauf, Finanzkalender, Erinnerungen
- Adressen mit IBAN-Prüfung, Verträge mit Kündigungsfrist, Sparziele
- Kredite mit gerechnetem Tilgungsplan
- Überweisungen über FinTS; die Buchung entsteht erst nach Annahme durch die
Bank. Die pain.001-Nachricht erzeugt SepaXml selbst
Nebenbei: .dockerignore ergänzt (ohne sie brach der Build an data/mariadb ab),
PlatzhalterController entfernt, zwei Inline-Handler beseitigt, die die
Content-Security-Policy ohnehin blockierte.
888 PHP-Prüfungen und 45 E2E-Tests grün.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Fünf Berichtstypen wie im FinanzManager – Summen-, Buchungs-, Vergleichs-,
Kontostands- und Budgetbericht – mit Filterleiste, gespeicherten Berichten in
Berichtsgruppen, Drilldown in die Buchungen und CSV-Export.
Ein Bericht speichert nur seine Frage; die Zahlen entstehen bei jedem Aufruf neu
aus den Buchungen. Gezählt werden Bewegungen (gesplittete Buchungen über ihre
Splitzeilen), Umbuchungen bleiben außen vor, Unkategorisiertes bekommt eine
eigene Zeile. Im Ausgabenabschnitt steht der Betrag ohne Minus wie im Original,
die Schlusszeile rechnet mit den echten Vorzeichen.
Einzelheiten in docs/auswertungen.md.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Der Schalter „nur aktive“ änderte kaum etwas: Neben ihm filterte noch das
Σ-Kennzeichen, und stillgelegte Konten tragen es meist nicht – von 17
inaktiven Konten kam beim Ausschalten genau eines dazu. Das Kennzeichen
filtert hier nicht mehr mit, der Schalter ist das einzige Kriterium.
Balken jetzt 18 Bildpunkte breit mit 3 Punkten Abstand. Damit das auf dem
Schirm ankommt, trägt das SVG seine Maße als Attribute und wird nicht mehr
auf die Kachel gestreckt. Die Legende steht neben der Zeichnung, die sonst
die halbe Kachel leer ließe.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Die Kachel war ein Platzhalter und zeigt jetzt die Kontostände der
letzten zwölf Monate je Monatsende – ein Balken pro Monat, ein Farbton
pro Konto. Gezählt wird, was auch in der Kontenübersicht in die Summe
zählt: Konten mit Σ-Kennzeichen.
Der Schalter „nur aktive“ oben rechts blendet stillgelegte Konten aus
und merkt sich seinen Zustand über die Einstellungen. Mehr Konten als
Farbtöne fasst „Übrige Konten (n)“ zusammen – 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.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Abgelaufene Monate werden nicht mehr zusammengefasst – alle zwölf Monate
stehen einzeln da und bleiben beplanbar. Statt Verlauf und alle Monate
gibt es Alles, Nur Budget und Nur IST; die Budgetspalte ist überall dort
editierbar, wo sie steht.
Das Zahnrad vor jedem Feld verteilt dessen Wert auf das ganze oder das
restliche Jahr. Es arbeitet auf den Eingabefeldern statt auf der
Datenbank: Das Ergebnis ist vor dem Speichern zu sehen und geht mit
demselben Knopf weg wie jede andere Änderung.
Nebenbei: app.js war doppelt eingebunden, jeder Handler lief zweimal.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Budgetplaner mit Monatswerten je Kategorie, Monatsabschluss mit Prüflauf,
Snapshot und Buchungssperre. Dazu drei Vorarbeiten in der Oberfläche.
Budgetplaner
- Migration 004: budgets, budget_positionen, monatsabschluesse
- Zeilen sind der Kategoriebaum, 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
- Ausgaben ohne Vorzeichen eingeben, es 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 nicht beplanbar
- Ein Monat ohne Position ist "nicht geplant" und damit etwas anderes als
"mit 0,00 geplant"
- Wert auf 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, bleibt auch nach einer
Wiedereröffnung stehen
- Abgeschlossene Monate sperren Buchungen in beide Richtungen: weder hinein noch
heraus verschieben. Abschaltbar über monatsabschluss_sperrt_buchungen
Vorarbeiten
- .knopf.primaer trug einen eigenen margin-top und stand dadurch in jeder
Knopfleiste tiefer als seine Nachbarn - sichtbar 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; dort hängt auch die Zuordnung der
Konten (ankreuzen statt jedes Konto einzeln zu öffnen)
- Der QIF-Export enthält keine Kontengruppen: kein Feld im !Account-Eintrag,
nichts in der QSD-Datei. Import-Oberfläche und Doku sagen das jetzt
- Die Baum-Einrückung lief über ein style-Attribut und wurde von der
Content-Security-Policy verworfen; sie steht jetzt als data-ebene im Markup.
Aus demselben Grund blendete app.js Erfolgsmeldungen nie aus
Tests: 11_BudgetTest, 12_MonatsabschlussTest (76 Prüfungen), E2E planung.spec.js.
Einzelheiten in docs/planung.md.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Der Plan ging davon aus, Umbuchungen stünden zweimal im Export und müssten
zusammengeführt werden. Der Vollexport zeigt das Gegenteil: 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 hat keine ein zweites Vorkommen. Mit erzeugter Gegenseite stimmen
acht gegen den FinanzManager geprüfte Kontosalden auf den Cent.
Datenmodell: Migration 003 mit buchungen, buchung_splits und buchungsadressen.
Eine Umbuchung ist ein Datensatz mit gegenkonto_id, kein gespiegeltes Paar -
zwei Zeilen laufen früher oder später auseinander. Das Kontoblatt des
Gegenkontos zeigt sie mit umgekehrtem Vorzeichen. Auch einzelne Splitzeilen
dürfen ein Gegenkonto tragen. Konten führen zusätzlich in_summe, die
Sigma-Spalte der Kontenübersicht, und saldo_online für Phase 5.
Oberfläche: 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, mit Splitzeilen und
Restbetragsanzeige.
Import: QifBuchungsImport arbeitet mit Stichtag. Alles davor wird je Konto zum
Anfangssaldo aufsummiert, Gegenbuchungen eingerechnet; alles danach bis zum
Importtag kommt einzeln in die Datenbank. Spätere Buchungen sind
Vorausberechnungen des FinanzManagers und bleiben draußen. Wertpapiergeschäfte
schlagen vorerst nur mit ihrem Geldfluss auf dem Verrechnungskonto zu Buche.
Am Vollexport geprüft: 833 Buchungen ab 01.01.2026, 7946 in die Anfangssalden
von 35 Konten verrechnet.
Behoben: Die confirm()-Rückfragen vor dem Löschen waren wirkungslos.
public/static/app.js brachte den Mechanismus mit, war aber nie im Layout
eingebunden; die ersatzweise gesetzten onsubmit-Attribute verstoßen gegen die
eigene Content-Security-Policy und wurden vom Browser verworfen - gelöscht
wurde ohne Rückfrage. Ein E2E-Test besteht jetzt auf dem Dialog.
Tests: 307 PHP-Prüfungen und 21 Playwright-Tests, alle grün.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Konten, Kontengruppen, Kategorien und Klassen samt Übernahme aus dem
QIF-Export des Lexware FinanzManagers.
Kontenübersicht nach Kontengruppen gebündelt, mit Gruppensummen und
Gesamtsumme; die drei Saldospalten (Online-Saldo, aktueller und erwarteter
Saldo) sind angelegt und werden bis Phase 2 aus dem Anfangssaldo gespeist.
Kategorien sind hierarchisch mit ":" als Trenner. Der vollständige Pfad
steht redundant in der Tabelle, weil jede Anzeige und der gesamte Import
darüber laufen; Umbenennen und Verschieben schreiben den Teilbaum neu. Die
Art (Einnahme/Ausgabe) gilt für den ganzen Ast. Register wie im Original:
Alle, Einnahmen, Ausgaben, Umbuchungskonten.
QifParser liest zustandsbehaftet und sequenziell, wandelt ISO-8859-1 nach
UTF-8 und versteht US-Beträge sowie das Datumsformat aus der Kopfzeile.
QifStammdatenImport ist idempotent und bietet Trockenlauf und Protokoll -
im Dialog unter Verwaltung sowie über scripts/qif-import.php. Am echten
Export geprüft: 17 Klassen, 105 Kategorien, 37 Konten.
Enthält außerdem die Korrektur der englischen Monatsnamen im
Einstellungsdialog: date('F') ist sprachlich nicht umschaltbar, deshalb
liegen die Namen jetzt zentral als View::MONATE vor. Die Änderung ließ
sich nicht als eigener Commit trennen, weil sie dieselben Dateien berührt
wie Phase 1.
Tests: 227 PHP-Prüfungen und 14 Playwright-Tests, alle grün.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Docker Compose mit drei Services (nginx auf Port 8000), Migrationsmechanik,
Update- und Backup-Skript. Anwendung: Router mit CSRF-Prüfung, Anmeldung und
Ersteinrichtung, zentraler Einstellungsdialog mit Reitern, ein- und
ausschaltbares Debug-Log mit Maskierung von Zugangsdaten.
Layout nach Vorbild des FinanzManagers: feste Navigation links,
Werkzeugleiste oben, Tab-Leiste; Farbschema hell/dunkel/System.
Tests: 51 PHP-Prüfungen und 7 Playwright-Tests, alle grün.