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>
111 lines
5.1 KiB
Markdown
111 lines
5.1 KiB
Markdown
# 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
|