Finanzmanager/docs/planung.md
fds2610-bot 851e8204e9 Phase 3: Budgetplaner und Monatsabschluss
Budgetplaner mit Monatswerten je Kategorie, Monatsabschluss mit Prüflauf,
Snapshot und Buchungssperre. Dazu drei Vorarbeiten in der Oberfläche.

Budgetplaner
- Migration 004: budgets, budget_positionen, monatsabschluesse
- Zeilen sind der Kategoriebaum, Spalten die Monate; die ganze Tabelle wird in
  einem Formular gespeichert, damit mehrere Änderungen zusammen ankommen
- Zwei Ansichten: Verlauf (Jahressumme, abgelaufene Monate zusammengefasst,
  laufender Monat mit Budget/Ist/Saldo, danach Folgemonate) und alle Monate
- Ausgaben ohne Vorzeichen eingeben, es kommt aus der Kategorieart. Saldo ist
  Ist minus Budget: positiv heißt besser als geplant, negativ ist die
  Überschreitung und wird rot ausgewiesen
- Übergeordnete Kategorien summieren ihre Nachfahren und sind nicht beplanbar
- Ein Monat ohne Position ist "nicht geplant" und damit etwas anderes als
  "mit 0,00 geplant"
- Wert auf Folgemonate übertragen, Budget als Kopie ins neue Jahr

Monatsabschluss
- Prüflauf über nicht kategorisierte Buchungen, nicht abgeglichene Umsätze und
  Splitsummen-Differenzen; er hält niemanden auf, sondern zeigt den Stand
- Snapshot der Kontosalden und Kategoriesummen als JSON, bleibt auch nach einer
  Wiedereröffnung stehen
- Abgeschlossene Monate sperren Buchungen in beide Richtungen: weder hinein noch
  heraus verschieben. Abschaltbar über monatsabschluss_sperrt_buchungen

Vorarbeiten
- .knopf.primaer trug einen eigenen margin-top und stand dadurch in jeder
  Knopfleiste tiefer als seine Nachbarn - sichtbar an "Abbrechen" neben
  "Konto anlegen". Der Abstand liegt jetzt allein bei der Knopfleiste
- Kontengruppen werden wie Konten und Kategorien auf einer eigenen
  Formularseite gepflegt statt inline; dort hängt auch die Zuordnung der
  Konten (ankreuzen statt jedes Konto einzeln zu öffnen)
- Der QIF-Export enthält keine Kontengruppen: kein Feld im !Account-Eintrag,
  nichts in der QSD-Datei. Import-Oberfläche und Doku sagen das jetzt
- Die Baum-Einrückung lief über ein style-Attribut und wurde von der
  Content-Security-Policy verworfen; sie steht jetzt als data-ebene im Markup.
  Aus demselben Grund blendete app.js Erfolgsmeldungen nie aus

Tests: 11_BudgetTest, 12_MonatsabschlussTest (76 Prüfungen), E2E planung.spec.js.
Einzelheiten in docs/planung.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 07:31:25 +02:00

111 lines
5.1 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.

# Planung
Budgetplaner und Monatsabschluss – die beiden Module des Bereichs *Planung*.
## Budgetplaner
Ein **Budget** gilt für genau ein Jahr und hält je Kategorie zwölf Monatswerte
(`budgets`, `budget_positionen`). Mehrere Budgets pro Jahr sind möglich, genau eines ist
das aktive; es öffnet sich beim Aufruf des Planers.
### Vorzeichen und Saldo
Budgetwerte werden mit dem Vorzeichen der Buchhaltung gespeichert: **Ausgaben negativ,
Einnahmen positiv**. Eingegeben wird trotzdem der nackte Betrag – „400“ bei Lebensmitteln
meint 400 € Ausgabe. `Budget::positionSetzen()` dreht das Vorzeichen anhand der
Kategorieart; eine ausdrücklich negative Eingabe bei einer Ausgabe bleibt eine Ausgabe.
Daraus folgt eine Saldodefinition, die für Einnahmen und Ausgaben gleich funktioniert:
```
saldo = ist − budget
```
**Positiv heißt besser als geplant** (weniger ausgegeben oder mehr eingenommen), negativ
ist die Überschreitung. Die Oberfläche färbt negative Salden rot – das ist der Alarm bei
Budgetüberschreitung.
### Nicht geplant ist nicht null
Ein Monat ohne Zeile in `budget_positionen` ist **nicht geplant**, ein Monat mit dem Wert
`0.00` ist **mit null geplant**. Der Planer zeigt das eine als leeres Feld, das andere als
`0,00`; ein geleertes Feld löscht die Position wieder. Deshalb füllt `Budgetplaner` die
Reihe `eigenBudget` nicht mit Nullen auf, während `budget` und `ist` als vollständige
Monatsreihen vorliegen.
### Ist-Werte
`Budgetplaner::istWerte()` summiert je Kategorie und Monat:
* Buchungen ohne Splitzeilen über `buchungen.kategorie_id`
* gesplittete Buchungen über `buchung_splits.kategorie_id`
Beides zusammen zählte doppelt, deshalb die Trennung über `gesplittet`. Umbuchungen tragen
keine Kategorie und bleiben außen vor – wie im FinanzManager.
### Baum und Summen
Zeilen sind der Kategoriebaum in Baumreihenfolge. Eine übergeordnete Kategorie zeigt die
**Summe ihrer Nachfahren** (aufaddiert von unten nach oben) und ist deshalb selbst nicht
beplanbar: Zwei Werte in einer Zelle – eigener und aggregierter – wären nicht lesbar.
Geplant wird auf den Blattkategorien. Die Gesamtzeile summiert nur die Wurzeln, sonst
zählten Unterkategorien mehrfach.
### Ansichten
| Ansicht | Spalten |
|---|---|
| **Verlauf** (Standard) | Jahressumme, abgelaufene Monate zusammengefasst, laufender Monat mit Budget/Ist/Saldo, danach je Folgemonat eine Budgetspalte |
| **Alle Monate** | Jahressumme und zwölf Budgetspalten – zum Planen des nächsten Jahres oder Nachbessern eines vergangenen |
Was als abgelaufen gilt, entscheidet `Budgetplaner::abgelaufenBis()`: im laufenden Jahr der
Vormonat, in vergangenen Jahren der Dezember, in künftigen nichts.
Die ganze Tabelle steht in **einem** Formular und wird mit einem Knopf gespeichert – ohne
JavaScript, damit mehrere geänderte Zeilen zusammen ankommen. Der Knopf **↦** in jeder
Zeile überträgt den Wert des ersten Eingabemonats auf alle Folgemonate
(`Budget::aufFolgemonateUebertragen()`).
`Budget::kopieren()` legt ein Budget samt aller Monatswerte im neuen Jahr an – der Weg zum
Jahresbudget, ohne 105 Kategorien noch einmal zu tippen.
## Monatsabschluss
Der Abschluss friert einen Monat ein (`monatsabschluesse`).
**Prüflauf** (`Monatsabschluss::pruefen()`) zeigt, was im Zeitraum noch offen ist:
* Buchungen ohne Kategorie und ohne Gegenkonto
* nicht abgeglichene Buchungen
* Splitbuchungen, deren Zeilen nicht auf den Buchungsbetrag aufgehen
Keiner dieser Punkte hindert am Abschluss – sie sind Hinweise, der FinanzManager hält es
genauso.
**Snapshot**: Beim Abschluss werden Kontosalden zum Monatsende (inklusive der
Σ-Kennzeichen und der Summe der Kontostände) sowie die Kategoriesummen des Monats als JSON
gespeichert. Der Snapshot bleibt auch nach einer Wiedereröffnung stehen: Er zeigt, mit
welchen Zahlen einmal abgeschlossen wurde.
**Sperre**: `Monatsabschluss::verlangeOffen()` greift in `Buchung::anlegen()`,
`speichern()` und `loeschen()`. Eine Buchung darf weder in einen abgeschlossenen Monat
hinein noch aus einem heraus verschoben werden. Die Sperre lässt sich über die Einstellung
`monatsabschluss_sperrt_buchungen` abschalten – etwa für einen Import, der ältere
Zeiträume nachträgt. Der QIF-Import bricht daran ohnehin nicht ab: Er fängt den Fehler je
Buchung ab und schreibt ihn ins Protokoll.
**Wiedereröffnen** setzt den Status auf `wiedereroeffnet` und vermerkt den Zeitpunkt. Ein
erneuter Abschluss überschreibt den Snapshot mit dem neuen Stand.
`Monatsabschluss::naechsterOffener()` schlägt den ältesten Monat mit Buchungen vor, der
noch offen ist: Abgeschlossen wird der Reihe nach, deshalb läuft die Suche vom Vormonat
rückwärts und endet am ersten bereits abgeschlossenen Monat.
## Tests
* `tests/11_BudgetTest.php` – Vorzeichen, „nicht geplant“, Ist-Werte samt Splits,
Baumsummen, Saldo, Folgemonate, Kopie
* `tests/12_MonatsabschlussTest.php` – Zeiträume, Prüflauf, Snapshot, Sperre,
Wiedereröffnung, Vorschlag
* `tests/e2e/specs/planung.spec.js` – Budget anlegen, Wert eintragen und wiederfinden,
Übertragen auf Folgemonate, Abschluss mit Snapshot und Wiedereröffnung