Unter den Kategorien stehen überall die aktiven Umbuchungskonten, je Kontengruppe gruppiert und in deren Reihenfolge (Konto::umbuchungsgruppen()): Regeln samt Splitzeilen, Umsatzabgleich, Buchungsvorlagen, Regelmäßige Buchungen, Zahlungsverkehr, Verträge sowie die Filter von Buchungsbericht und Export. Die Masken nutzen dafür die gemeinsame Liste Kategorie / Gegenkonto der Buchungsmaske; das getrennte Gegenkonto-Feld der Regel entfällt. Regel-Splitzeilen können auf ein Umbuchungskonto zeigen. Zahlungsaufträge und Verträge bekommen ein Gegenkonto (Migration 030). Ein schon gewähltes inaktives Konto bleibt sichtbar, damit keine Zuordnung verloren geht. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
255 lines
14 KiB
Markdown
255 lines
14 KiB
Markdown
# Nebenmodule und Zahlungsverkehr
|
||
|
||
Was in Phase 7 dazugekommen ist: Buchungsvorlagen, regelmäßige Buchungen mit
|
||
Finanzkalender, Erinnerungen, Adressen, Verträge, Sparziele, Kredite und der
|
||
Zahlungsverkehr.
|
||
|
||
## Buchungsvorlagen
|
||
|
||
Eine Vorlage ist eine fertige Buchung ohne Datum. Sie kommt aus dem QIF
|
||
(`!Type:Memorized` – im Beispielexport 579 Stück, vom FinanzManager automatisch gemerkt)
|
||
oder entsteht aus einer vorhandenen Buchung.
|
||
|
||
**Splitzeilen werden mitskaliert.** Weicht der Betrag beim Buchen von dem der Vorlage ab,
|
||
ziehen die Splitzeilen im gleichen Verhältnis mit; die letzte bekommt den Rest, damit die
|
||
Summe auf den Cent zum Buchungsbetrag passt. Ohne das ließe sich eine Vorlage mit Splits
|
||
nur zu ihrem ursprünglichen Betrag buchen.
|
||
|
||
Die Liste sortiert nach Häufigkeit: Wer eine Vorlage sucht, sucht meist eine, die er schon
|
||
oft gebraucht hat.
|
||
|
||
Beim Import entsteht der **Name** aus Empfänger und Betrag – die QIF-Sektion führt keinen.
|
||
Kommt derselbe Name mehrfach vor, zählt ein Zähler hoch, statt Vorlagen stillschweigend zu
|
||
verschlucken.
|
||
|
||
## Regelmäßige Buchungen
|
||
|
||
Jede Reihe führt ihre **nächste Fälligkeit** mit sich. Der Fälligkeitslauf holt, was
|
||
ansteht, bucht es und schreibt das Datum fort. Eine Tabelle vorausberechneter Termine gibt
|
||
es bewusst nicht: Sie wäre bei jeder Änderung neu zu erzeugen.
|
||
|
||
Zwei Betriebsarten:
|
||
|
||
* **automatisch** – wird ohne Rückfrage gebucht. Für Miete und Gehalt, wo Betrag und
|
||
Termin feststehen.
|
||
* **auf Bestätigung** – wird nur gemeldet und wartet auf Knopfdruck. Für alles, dessen
|
||
Betrag schwankt.
|
||
|
||
Unabhängig davon legt **automatisch abgleichen** (Vorgabe: eingeschaltet) fest, ob die
|
||
entstandene Buchung gleich als abgeglichen markiert wird. Die Übersicht zeigt den Schalter
|
||
in der Spalte „Abgleichen“.
|
||
|
||
Der Lauf ist gutmütig gegenüber langen Pausen: Liegen mehrere Termine zurück, wird **jeder
|
||
einzeln** gebucht, nicht einer für alle – nur so stimmen die Monatssummen. Nach dem Buchen
|
||
rückt die Fälligkeit so weit vor, bis sie in der Zukunft liegt.
|
||
|
||
In einen **abgeschlossenen Monat** wird nicht hineingebucht: Die Reihe bleibt stehen und
|
||
meldet sich, statt den ganzen Lauf mit einer Ausnahme abzubrechen.
|
||
|
||
*Überspringen* rückt vor, ohne zu buchen – für den Monat, in dem die Zahlung einmal
|
||
ausgesetzt wurde.
|
||
|
||
## Finanzkalender
|
||
|
||
Zusammengetragen aus vier Quellen: fällige regelmäßige Buchungen, offene Erinnerungen,
|
||
ablaufende Kündigungsfristen und terminierte Überweisungen. Alles gerechnet, nichts
|
||
gespeichert – eine Änderung an einer Reihe wirkt sich sofort aus.
|
||
|
||
Überfälliges bleibt sichtbar und wird markiert, statt aus der Liste zu fallen.
|
||
|
||
## Adressen und IBAN-Prüfung
|
||
|
||
Das Adressbuch speist die Empfängerauswahl im Zahlungsverkehr. **IBANs werden geprüft**
|
||
(ISO 13616: Länge je Land, Prüfziffer modulo 97). Das fängt Zahlendreher ab, bevor eine
|
||
Überweisung hinausgeht – die häufigste und ärgerlichste Fehlerquelle im Zahlungsverkehr.
|
||
|
||
Gerechnet wird stückweise mit `%` statt mit `bcmod`: Die BC-Erweiterung ist keine
|
||
Standardausstattung, und ein Modulo über sieben Ziffern lange Blöcke kommt ohne sie aus.
|
||
|
||
**Adressverwaltung.** Eine eigene Kachel in der Verwaltung führt zur Adressliste
|
||
(`/vertraege/adressen`, weiter über `VertraegeController` gepflegt) – neben, nicht nur
|
||
innerhalb der Verträge-Kachel: Das Adressbuch bedient auch den Zahlungsverkehr, der mit
|
||
Verträgen nichts zu tun hat. Zu jeder Adresse klappt ein `<details>`-Element ihre letzten
|
||
Buchungen auf, kontenübergreifend nach dem exakten Empfängernamen gesucht
|
||
(`Buchung::letzteFuerEmpfaenger()`) – die Buchung führt kein Fremdschlüsselfeld auf die
|
||
Adresse, nur den Freitext-Empfänger.
|
||
|
||
**Typeahead im Zahlungsverkehr.** Die Empfängerauswahl (`<select data-adressauswahl>`) trägt
|
||
zusätzlich `data-kombobox` und wird wie die Kategorie im Kontoblatt (`kombobox.js`, siehe
|
||
`docs/`-Test 35) zu einem Eingabefeld mit gefilterter Liste. Die Auswahl füllt Name, IBAN und
|
||
BIC; geändert werden darf danach alles.
|
||
|
||
**Der Empfänger landet wirklich im Adressbuch**, wenn beim Erfassen einer Überweisung
|
||
„Empfänger ins Adressbuch übernehmen“ angekreuzt ist – `ZahlungsverkehrController::anlegen()`
|
||
ruft dafür `Adresse::anlegen()` auf, nachdem der Zahlungsauftrag erfolgreich angelegt wurde;
|
||
gibt es die Adresse schon, bleibt der Fehler von `Adresse::anlegen()` (Duplikat) ohne Wirkung.
|
||
|
||
## Verträge
|
||
|
||
Der Nutzen liegt in der **Kündigungsfrist**: Die Übersicht zeigt, bis wann gekündigt sein
|
||
muss, damit sich ein Vertrag nicht stillschweigend verlängert. Verträge, deren Frist in den
|
||
nächsten 90 Tagen abläuft, stehen oben auf der Seite und im Finanzkalender.
|
||
|
||
Der **Jahresbetrag** rechnet verschieden getaktete Verträge vergleichbar: monatlich × 12,
|
||
vierteljährlich × 4 und so fort.
|
||
|
||
Ein Vertrag kann mit einer regelmäßigen Buchung verbunden sein, die seine Zahlung erledigt.
|
||
|
||
Statt einer Kategorie kann ein Vertrag auf ein Umbuchungskonto zeigen – ein Bauspar- oder
|
||
Sparvertrag etwa (#124, Spalte `vertraege.gegenkonto_id`). Die Auswahl ist dieselbe Liste
|
||
*Kategorie / Gegenkonto* wie in der Buchungsmaske.
|
||
|
||
## Sparziele
|
||
|
||
Der erreichte Stand kommt **aus dem Kontosaldo**, nicht aus einem eigenen Zähler – ein
|
||
zweiter, gepflegter Wert liefe früher oder später von den Buchungen weg. Der Startbetrag
|
||
zieht ab, was zu Beginn schon auf dem Konto lag.
|
||
|
||
Zum Zieldatum rechnet die Anwendung aus, welche Monatsrate nötig wäre, um den Termin noch
|
||
zu halten.
|
||
|
||
Der Fortschrittsbalken ist ein `<progress>`-Element: Ein eigener Balken bräuchte seine
|
||
Breite als `style`-Attribut, und das verbietet die Content-Security-Policy.
|
||
|
||
## Kredite
|
||
|
||
Gerechnet wird als **Annuitätendarlehen**: gleichbleibende Rate, sinkender Zinsanteil,
|
||
wachsende Tilgung. Zinsen fallen je Periode auf die Restschuld an.
|
||
|
||
Der Tilgungsplan wird **gerechnet, nicht gespeichert** – er ergibt sich vollständig aus
|
||
Betrag, Zins, Rate und Beginn. Eine jährliche Sondertilgung wird im Dezember verrechnet.
|
||
|
||
Deckt die Rate nicht einmal die Zinsen der ersten Periode, entsteht kein Plan, sondern ein
|
||
Hinweis: So ein Kredit würde wachsen statt zu schrumpfen, und eine Endlosschleife wäre die
|
||
schlechtere Antwort darauf.
|
||
|
||
Bei langen Laufzeiten zeigt die Tabelle die nächsten zwei Jahre und das Ende; dazwischen
|
||
steht, wie viele Raten ausgelassen wurden.
|
||
|
||
## Zahlungsverkehr
|
||
|
||
**Erst die Bank, dann die Buchung.** Ein Zahlungsauftrag ist zunächst ein Entwurf. Die
|
||
Buchung auf dem Auftragskonto entsteht erst, wenn die Bank den Auftrag angenommen hat –
|
||
vorher wäre sie eine Behauptung, die man nach einer abgelehnten TAN wieder einsammeln
|
||
müsste.
|
||
|
||
Ein gesendeter Auftrag lässt sich weder ändern noch löschen: Er bleibt als Beleg stehen.
|
||
Fehlgeschlagene tragen die Meldung der Bank daneben.
|
||
|
||
Ohne Ausführungstermin geht die Überweisung sofort hinaus, mit Termin als
|
||
Terminüberweisung; sie erscheint dann im Finanzkalender.
|
||
|
||
**Kategorie / Gegenkonto.** Wie in der Buchungsmaske stehen unter den Kategorien die
|
||
aktiven Umbuchungskonten (#124, Spalte `zahlungsauftraege.gegenkonto_id`). Geht die
|
||
Überweisung auf ein eigenes Konto – etwa aufs Tagesgeld –, wird sie beim Senden gleich als
|
||
Umbuchung gebucht. Das Auftragskonto selbst als Gegenkonto weist schon das Erfassen ab;
|
||
erst beim Senden fiele es sonst auf, wenn die Bank das Geld längst hat.
|
||
|
||
**Echtzeitüberweisung.** Das Häkchen „Echtzeitüberweisung“ im Überweisungsdialog
|
||
(Spalte `zahlungsauftraege.echtzeit`) macht aus dem Auftrag eine SEPA-Echtzeitüberweisung
|
||
(SCT Inst): Das Geld ist binnen Sekunden beim Empfänger. SEPA behandelt sie als eigenes
|
||
Verfahren – über FinTS geht sie als Geschäftsvorfall **HKIPZ** hinaus statt als HKCCS, in
|
||
der pain.001-Nachricht steht zusätzlich die Zahlungsart `PmtTpInf/LclInstrm/Cd = INST`.
|
||
Einen Ausführungstermin gibt es dafür nicht; der Dialog sperrt das Feld, das Modell weist
|
||
einen Termin in der Zukunft ab. Bietet die Bank keine Echtzeitüberweisung an (kein
|
||
Segment HIIPZS in den Bankparametern), scheitert der Auftrag mit einer klaren Meldung.
|
||
Eine Umwandlung in eine gewöhnliche Überweisung erlaubt der Finanzmanager der Bank
|
||
ausdrücklich nicht – wer Echtzeit wählt, soll erfahren, wenn es nicht klappt.
|
||
|
||
**Ausgang offen (3045).** Antwortet die Bank des Empfängers nicht rechtzeitig, quittiert
|
||
die eigene Bank die TAN mit 3045 („noch keine Rückmeldung zum Status“) und nennt in HIIPZ
|
||
eine Auftragsidentifikation. Der Auftrag liegt dann bei der Bank: Er gilt als gesendet und
|
||
wird gebucht – erneutes Senden wäre eine Doppelzahlung. Den Ausgang fragt der Finanzmanager
|
||
mit **HKIPS** nach (php-fints-Fork ab v4.1.7, `GetSEPARealtimeTransferStatus`). Die Bank
|
||
nennt dafür in HIIPSS eine Mindestwartezeit (DKB 5 s, comdirect 99 s). Die Nachfrage läuft
|
||
in einem eigenen Hintergrundprozess (`scripts/echtzeit-status.php <benutzer> <auftrag>`):
|
||
Er wartet die Mindestwartezeit ab und fragt höchstens fünfmal; eine Sperrdatei je Auftrag
|
||
verhindert doppelte Läufe. Bis dahin steht der Auftrag in der Liste mit „Ausgang offen“.
|
||
Verlangt die Bank für die Abfrage eine TAN oder bleibt der Ausgang offen, wird die
|
||
automatische Abfrage eingestellt (`status_abfrage_ab` leer). Nach einem Neustart des
|
||
Containers stößt die Zahlungsverkehrsliste fällige Abfragen erneut an.
|
||
Status 4 und 7 gelten als ausgeführt, 2, 6, 8 und 9 als abgelehnt – dann verschwindet die
|
||
Buchung wieder und der Auftrag lässt sich korrigieren und erneut senden. Wird beim
|
||
Umsatzabruf ein Umsatz mit der Buchung verbunden, ist der Ausgang ebenfalls geklärt.
|
||
Spalten: `bank_auftrag_id`, `echtzeit_status`, `status_abfrage_ab` (Migration 026).
|
||
|
||
**Welches Konto bei der Bank gemeint ist**, entscheidet dieselbe Zuordnung wie beim
|
||
Umsatzabruf: die Zeile in `bank_konten`, die dem Konto zugewiesen ist (IBAN, ersatzweise
|
||
Kontonummer). Die IBAN am Konto selbst ist optional und wird nur herangezogen, wenn keine
|
||
Zuordnung besteht – sonst müsste man dieselbe Angabe zweimal pflegen.
|
||
|
||
### SEPA-XML von Hand
|
||
|
||
php-fints erwartet die fertige **pain.001**-Nachricht und erzeugt sie nicht. Die üblichen
|
||
Erzeuger wären zwei weitere Abhängigkeiten für ein Dokument von sechzig Zeilen –
|
||
`Services\SepaXml` schreibt es deshalb selbst.
|
||
|
||
**Welches Format, sagt die Bank.** Die einen nehmen das ISO-Format `pain.001.001.03`, die
|
||
anderen nur das ältere DK-Format `pain.001.003.03` – und weisen alles andere schon beim
|
||
Einreichen ab. Welche Formate eine Bank führt, steht in den Bankparametern (Segment
|
||
HISPAS); `SepaXml::schemaWaehlen()` sucht daraus das passende aus, ISO bevorzugt. Für die
|
||
Einzelüberweisung sind beide Fassungen im Aufbau gleich, es genügt der andere Namensraum.
|
||
Die Echtzeitüberweisung gibt es nur im ISO-Format: Das DK-Format kennt die Zahlungsart
|
||
`LclInstrm` nicht. Ihre Formate nennt die Bank in HIIPZS, ersatzweise gelten die aus HISPAS.
|
||
|
||
Der **SEPA-Zeichenvorrat** ist eng: `a–z A–Z 0–9 / - ? : ( ) . , ' +` und Leerzeichen.
|
||
Umlaute werden umgeschrieben (ä → ae), alles Übrige fällt weg. Ohne diese Umschrift weist
|
||
die Bank den Auftrag zurück – und zwar erst nach dem Absenden.
|
||
|
||
Der Verwendungszweck endet bei 140 Zeichen; das ist das SEPA-Maß, alles darüber würde die
|
||
Bank ohnehin abschneiden.
|
||
|
||
### Namensabgleich (Verification of Payee)
|
||
|
||
Seit Oktober 2025 gleicht die Bank vor der Ausführung den Empfängernamen mit dem
|
||
Kontoinhaber ab. Der Ablauf ist eine Stufe vor der TAN:
|
||
|
||
1. Der Auftrag geht zusammen mit dem Prüfauftrag (Segment HKVPP) hinaus.
|
||
2. Braucht die Bank für den Abgleich einen Moment, wird nachgefragt, bis das Ergebnis da
|
||
ist – wie lange sie braucht, sagt sie selbst.
|
||
3. **Stimmt der Name überein, bestätigt der Finanzmanager selbst** und geht direkt zur
|
||
TAN. Eine Rückfrage ohne Erkenntnis erspart das dem Nutzer.
|
||
4. **Weicht der Name ab**, ist er nur nahezu gleich oder konnte die Bank nicht prüfen,
|
||
kommt die Maske `zahlungsverkehr/vop.php`: das Ergebnis in einem deutschen Satz, der
|
||
Aufklärungstext der Bank als Klartext (ihr HTML wird abgeräumt), Empfänger und IBAN aus
|
||
dem Auftrag, und die Wahl zwischen *Trotzdem senden* und *Abbrechen*.
|
||
|
||
Stimmt der Name nur **nahezu** überein, nennt die Bank den bei ihr hinterlegten
|
||
Kontoinhaber – er steht dann in der Maske neben dem angegebenen Namen. Weicht der Name
|
||
ganz ab, bleibt er geheim: Sonst ließen sich über beliebige IBANs Kontoinhaber ausspähen.
|
||
Die Spezifikation erlaubt die Angabe ausdrücklich nur beim Close Match.
|
||
|
||
Wer trotz Abweichung sendet, verliert den Erstattungsanspruch – deshalb steht das auch in
|
||
der Maske. Beim Abbrechen bleibt der Auftrag als Entwurf stehen, der Empfängername lässt
|
||
sich korrigieren und der Auftrag erneut senden.
|
||
|
||
Meldet die Bank die **schrittweise Lieferung** des Prüfberichts (BPD-Parameter „Art der
|
||
Lieferung Payment Status Report" = `S`, etwa bei comdirect), betrifft das nur sehr große
|
||
Sammler, bei denen die Bank den Bericht über mehrere Runden schickt. php-fints lehnte
|
||
solche Banken bisher pauschal ab; der Fork auf Codeberg prüft das erst, wenn wirklich eine
|
||
Teillieferung kommt (siehe `docs/banking.md`).
|
||
|
||
### TAN
|
||
|
||
Für Überweisungen verlangt jede Bank eine TAN. Der Dialog wird genauso über die Sitzung
|
||
fortgesetzt wie beim Abruf (siehe `docs/banking.md`): Zustand sichern, Maske zeigen, nach
|
||
der Eingabe weitermachen.
|
||
|
||
Beim Abbrechen bleibt der Auftrag als Entwurf stehen und lässt sich erneut senden.
|
||
|
||
## Tests
|
||
|
||
`tests/18_NebenmoduleTest.php` – Vorlagen mit skalierten Splits und aus Buchungen,
|
||
QIF-Vorlagenimport samt Idempotenz, Terminrechnung und Fortschreiben der Fälligkeit,
|
||
Fälligkeitslauf (nur automatische Reihen, wartende rücken nicht vor), Erinnerungen,
|
||
IBAN-Prüfung mit gültigen und falschen Nummern, Kündigungsfristen und Jahresbeträge,
|
||
Sparfortschritt aus dem Konto, Tilgungsplan (Annuität, letzte Rate, Sondertilgung,
|
||
zu kleine Rate), Zahlungsaufträge (Buchung erst nach Annahme, Unveränderlichkeit,
|
||
Eingabeprüfungen), SEPA-XML (Schema, Zeichensatz, Wohlgeformtheit) und der Finanzkalender.
|
||
|
||
`tests/39_SepaSchemaTest.php` – Formatwahl nach den Angaben der Bank; die erzeugte
|
||
Nachricht wird gegen das beiliegende DK-Schema `tests/schemata/pain.001.003.03.xsd`
|
||
validiert.
|
||
|
||
`tests/40_NamensabgleichTest.php` – deutsche Sätze zu jedem Prüfergebnis, Aufbereitung des
|
||
Banktexts und die Absicherung, dass ohne wartenden Vorgang nichts bestätigt werden kann.
|