Das Überweisungsformular fragt nur noch das Auftragskonto ab; die passende
Bankverbindung ermittelt Bankzugang::zugangJeKonto() aus der Kontenzuordnung.
Konten ohne konfigurierte Bank stehen in der Auswahl nicht mehr zur Verfügung.
Co-Authored-By: Claude Sonnet 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>
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>
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>
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>