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>
893 lines
54 KiB
Markdown
893 lines
54 KiB
Markdown
# Änderungen
|
||
|
||
## 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
|