# 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