Finanzmanager/docs/auswertungen.md
fds2610-bot 233b570e76 Fix #84: Cockpit-Legendeneinträge öffnen die Buchungen dahinter
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>
2026-09-28 08:27:32 +02:00

193 lines
9.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Auswertungen
Der Bereich *Auswertungen* baut die Berichte des FinanzManagers nach: fünf Berichtstypen,
eine Filterleiste, gespeicherte Berichte in Gruppen, Drilldown in die Buchungen und
CSV-Export.
Ein Bericht wird **nie gerechnet gespeichert**, sondern immer nur seine Frage. Die Zahlen
entstehen bei jedem Aufruf neu aus den Buchungen. Ein Bericht mit der Zeitraumvorgabe
„Letzter Monat“ zeigt im August den Juli und nicht für immer den Monat seiner Entstehung.
## Was gezählt wird
Grundlage sind die **Bewegungen**: nicht gesplittete Buchungen mit ihrem Betrag,
gesplittete über ihre Splitzeilen. Sonst zählte eine Splitbuchung doppelt oder gar nicht.
**Umbuchungen bleiben außen vor.** Sie verschieben Vermögen zwischen eigenen Konten und
sind weder Einnahme noch Ausgabe – dieselbe Regel wie im Budgetplaner. Allein der
Buchungsbericht kann sie über den Schalter *Umbuchungen mitzählen* mitführen; dann weicht
seine Summe naturgemäß vom Summenbericht ab.
**Nicht kategorisierte Buchungen verschwinden nicht.** Sie stehen als eigene Zeile
*Nicht kategorisiert* am Ende ihres Abschnitts. Geld, das keiner Kategorie zugeordnet ist,
fiele sonst lautlos aus der Summe.
Ob eine Bewegung zu den Einnahmen oder den Ausgaben zählt, entscheidet die **Art ihrer
Kategorie**, nicht das Vorzeichen: Eine Erstattung ist eine negative Ausgabe und gehört
unter die Ausgaben. Nur wo keine Kategorie steht, bleibt das Vorzeichen als einziger
Anhaltspunkt.
Aggregiert wird in PHP, nicht in SQL. Die Datenbank liefert die Bewegungen des Zeitraums,
das Einsortieren in Zeilen und Spalten passiert in `Auswertung`. Bei Zehntausenden
Buchungen ist das Millisekundensache, und Baumsummen über Kategorien, Vorzeichenregeln und
Spaltenraster wären in SQL erheblich schwerer nachzulesen.
## Vorzeichen
Intern gilt durchgehend die Buchhaltung: Ausgaben negativ. In der **Anzeige** dreht der
Ausgabenabschnitt sein Vorzeichen – dort steht 636,39 statt −636,39, wie im FinanzManager;
die Überschrift sagt ja bereits, worum es geht. Die Schlusszeile *Gesamt Einnahmen −
Ausgaben* rechnet wieder mit den echten Werten.
Zwei Ausnahmen führen das Vorzeichen mit, weil sie keine Abschnitte haben: der
**Buchungsbericht** (eine Buchungsliste, kein Summenbericht) und der **Budgetbericht**
(Vorzeichen wie im Budgetplaner). Der Drilldown zeigt ebenfalls die echten Vorzeichen.
## Die fünf Berichtstypen
| Typ | Zeilen | Spalten |
|---|---|---|
| **Summenbericht** | Zeilen-Summe, nach Einnahmen und Ausgaben getrennt | Zeitraum, je nach Spalten-Aufteilung |
| **Buchungsbericht** | Einzelbuchungen, gruppiert nach Zeilen-Summe | feste Buchungsspalten |
| **Vergleichsbericht** | wie Summenbericht | Zeitraum, Vorjahreszeitraum, Differenz, Veränderung |
| **Kontostandsbericht** | Konten | Salden zu den Enden der Spaltenzeiträume |
| **Budgetbericht** | Kategoriebaum | Budget, Ist, Saldo |
### Summenbericht
Bei der Zeilen-Summe *Kategorie* entsteht der eingerückte Baum: Jede Kategorie zeigt ihre
**eigenen** Werte, Kategorien mit Nachfahren bekommen dahinter eine Zeile *Gesamt &lt;Name&gt;*
mit der Summe des Astes. Eine Elternkategorie ohne eigene Buchungen steht deshalb als
bloße Kopfzeile da – genau wie im Original.
Der Schalter *mit Einrückung* schaltet auf eine flache Liste mit vollem Kategoriepfad um.
Dort zählt jede Kategorie nur sich selbst, und es gibt keine Astsummen; sonst stünde eine
Summe wie ein gleichrangiger Posten neben ihren eigenen Bestandteilen.
Bei *Klasse*, *Konto* und *Empfänger* ist die Liste flach und alphabetisch. Die Aufteilung
in Einnahmen und Ausgaben bleibt: Eine Klasse mit beidem erscheint in beiden Abschnitten.
### Vergleichsbericht
Verglichen wird gegen **denselben Zeitraum ein Jahr früher**. Die Spalten-Aufteilung
spielt hier keine Rolle – die Spalten gibt der Vergleich vor.
Ohne Vorjahreswert bleibt die Prozentspalte leer: „unendlich viel mehr“ ist keine Aussage,
die in eine Spalte gehört. Ein Monatsende bleibt beim Rückrechnen ein Monatsende, damit
der 28.02. nicht auf einen 29.02. trifft und der Vergleichszeitraum einen Tag zu lang wird.
### Kontostandsbericht
Anders als die übrigen Berichte zählt hier nicht die Bewegung im Zeitraum, sondern der
**Stand**: alle Buchungen bis zum Stichtag, auch die vor dem Berichtszeitraum, und die
Gegenseiten von Umbuchungen. Konten, die im ganzen Zeitraum auf null standen, fallen weg –
dieselbe Regel wie in der Cockpit-Kachel. Die Summe zählt alle gezeigten Konten,
unabhängig vom Σ-Kennzeichen der Kontenübersicht.
Die Null wird hier ausgeschrieben: Ein Konto, das auf null steht, ist eine Aussage.
### Budgetbericht
Gerechnet wird über die **vollen Monate**, die der Zeitraum berührt. Ein Budget ist ein
Monatswert, und ein halber Monat hat kein halbes Budget; anders wären Budget und Ist nicht
vergleichbar und die Saldospalte bedeutungslos. Der Berichtskopf nennt deshalb den
Monatsbereich.
Geht der Zeitraum über den Jahreswechsel, gilt je Jahr dessen eigenes aktives Budget
(`Budget::aktivesFuerJahr()`). Vorzeichen und Saldo folgen dem Budgetplaner: Ausgaben
negativ, Saldo ist `ist − budget`, positiv heißt besser als geplant. Übergeordnete
Kategorien zeigen die Summe ihres Astes.
## Filterleiste
| Feld | Wirkung |
|---|---|
| Zeitraum | Vorgabe oder *Benutzerdefiniert*; von/bis sind immer gefüllt und zeigen, was die Vorgabe gerade bedeutet |
| Bericht anzeigen als | wechselt den Berichtstyp, ohne den übrigen Filter zu verlieren |
| Zeilen-Summe | Kategorie, Klasse, Konto oder Empfänger |
| Spalten-Aufteilung | Keine, Monat, Quartal, Jahr |
| mit Einrückung | Kategoriebaum oder flache Liste mit Pfad |
| Suche | Empfänger oder Buchungstext |
| Einschränken | Konten, Kategorien, Klassen (Mehrfachauswahl) |
Eine gewählte Kategorie **schließt ihre Unterkategorien ein** – wer „400 - Leben“ filtert,
meint den ganzen Ast. Nichts ausgewählt heißt alles.
Zwei Berichte bestimmen ihre Zeilen selbst und sperren das Feld: der Kontostandsbericht
führt immer Konten, der Budgetbericht immer Kategorien. Zwei bestimmen ihre Spalten
selbst: Vergleichs- und Budgetbericht.
Die Filterleiste sendet per **GET**. Jeder Zwischenstand ist damit eine Adresse, die man
sich merken, weitergeben und mit *Speichern unter* festhalten kann.
## Gespeicherte Berichte und Gruppen
Ein gespeicherter Bericht ist Name, Typ und Filter – mehr nicht. Der Filter liegt als JSON
in einer Spalte und wird beim Lesen immer durch `Auswertung::normalisiere()` geschickt:
Was dort nicht vorgesehen ist, fällt weg. Ein alter Bericht kann nach einer
Programmänderung höchstens eine Einstellung verlieren, aber nichts Unerwartetes in die
Abfrage tragen.
*Speichern unter* legt unter neuem Namen an und kann die Berichtsgruppe gleich mit
anlegen – wer sie erst anderswo erzeugen müsste, verlöre den eingestellten Filter.
*Speichern* überschreibt den geöffneten Bericht.
**Berichtsgruppen** sind die Kacheln der Übersicht, wie „Bankgeschäfte“ oder „Steuern“ im
Original. Wird eine Gruppe gelöscht, bleiben ihre Berichte erhalten und rücken unter
*Nicht gruppiert*: Ein Bericht ist Arbeit, eine Gruppe nicht.
Leere Gruppen bleiben in der Übersicht stehen. Sie sind angelegt worden, um befüllt zu
werden, und ohne Kachel gäbe es keinen Ort, an den ein Bericht könnte.
## Drilldown
Jeder Zeilenname und jede Zahl im Summen-, Vergleichs- und Budgetbericht ist ein Link auf
die Buchungen dahinter. Eine Zahl schränkt zusätzlich auf ihre Spalte ein.
Gefiltert wird nicht, indem die Zeile in einen neuen Filter übersetzt wird, sondern indem
**dieselben Bewegungen nachgesiebt** werden, aus denen die Zelle entstanden ist. Nur so
trifft der Drilldown auch das, was ein Filter gar nicht ausdrücken könnte: die Zeile
*Nicht kategorisiert* etwa, oder eine Astsumme über mehrere Unterkategorien.
Die Legenden der Cockpit-Kacheln nutzen denselben Weg (`/auswertungen/buchungen`) mit drei
Zusätzen: `art=einnahme|ausgabe` siebt zusätzlich nach Einnahme oder Ausgabe (entschieden
wie im Bericht über die Art der Kategorie, ohne Kategorie über das Vorzeichen), eine leere
`zeile` nimmt alle Bewegungen, und `zeile=kategorie:12,15,0` fasst mehrere Kategorien
zusammen (die `0` steht für *Nicht kategorisiert*). `herkunft=cockpit` lenkt den Weg
zurück ins Cockpit statt in einen Bericht.
## CSV-Export
Dieselben Zeilen, dieselben Zahlen, dieselben Vorzeichen wie am Bildschirm – wer den
Export mit der Anzeige vergleicht, soll nicht rechnen müssen.
Deutsches Tabellenkalkulations-CSV: Semikolon als Trenner, Komma als Dezimalzeichen, kein
Tausenderpunkt (der machte aus der Zahl einen Text), CRLF als Zeilenende und ein
vorangestelltes BOM – sonst zeigen Excel und LibreOffice beim Doppelklick „Übrige“ statt
„Übrige“. Die Einrückung des Kategoriebaums steht als führende Leerzeichen im Namensfeld,
sonst wäre die Hierarchie verloren.
PDF gibt es bewusst nicht (siehe `PLAN.md`); Papier läuft über die Druckfunktion des
Browsers.
## Dateien
| Datei | Inhalt |
|---|---|
| `src/Services/Auswertung.php` | die Rechenmaschine: Bewegungen, die fünf Berichtstypen, Drilldown |
| `src/Services/Zeitraum.php` | Zeitraumvorgaben und Spaltenraster, reine Kalenderrechnung ohne Datenbank |
| `src/Services/BerichtExport.php` | CSV |
| `src/Models/Bericht.php` | gespeicherte Berichte, Filter-JSON |
| `src/Models/Berichtsgruppe.php` | Gruppen |
| `src/Controllers/AuswertungenController.php` | Übersicht, Berichtsansicht, Drilldown, Export |
| `src/Views/auswertungen/` | Übersicht, Filterleiste, ein Teiltemplate je Darstellung |
| `migrations/005_berichte.sql` | `berichte`, `berichtsgruppen` |
## Tests
* `tests/14_AuswertungTest.php` – Zeitraumvorgaben und Spaltenraster, Filterprüfung,
alle fünf Berichtstypen, Astsummen, Splitbuchungen, Ausschluss der Umbuchungen,
unkategorisierte Buchungen, Einschränkungen, Drilldown, CSV, gespeicherte Berichte
* `tests/e2e/specs/auswertungen.spec.js` – Übersicht und Standardvorlagen, die fünf
Tabellen, Spalten-Aufteilung, Drilldown und zurück, Speichern/Wiederfinden/Löschen,
CSV-Download