Abgelaufene Monate werden nicht mehr zusammengefasst – alle zwölf Monate stehen einzeln da und bleiben beplanbar. Statt Verlauf und alle Monate gibt es Alles, Nur Budget und Nur IST; die Budgetspalte ist überall dort editierbar, wo sie steht. Das Zahnrad vor jedem Feld verteilt dessen Wert auf das ganze oder das restliche Jahr. Es arbeitet auf den Eingabefeldern statt auf der Datenbank: Das Ergebnis ist vor dem Speichern zu sehen und geht mit demselben Knopf weg wie jede andere Änderung. Nebenbei: app.js war doppelt eingebunden, jeder Handler lief zweimal. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
130 lines
5.8 KiB
Markdown
130 lines
5.8 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
|
||
|
||
Alle zwölf Monate stehen immer einzeln da – auch die abgelaufenen. Zusammengefasst wird
|
||
nichts: Ein vergangener Monat ließe sich sonst weder nachlesen noch nachplanen. Die Ansicht
|
||
entscheidet nur, welche der drei Wertspalten je Monat erscheinen:
|
||
|
||
| Ansicht | Spalten je Monat (und für das Jahr) |
|
||
|---|---|
|
||
| **Alles** (Standard) | Budget, Ist, Saldo |
|
||
| **Nur Budget** | Budget |
|
||
| **Nur IST** | Ist |
|
||
|
||
Wo die Budgetspalte steht, ist sie **immer editierbar**, im Januar wie im Dezember.
|
||
`BudgetController::ansicht()` nimmt nur `alles`, `budget` und `ist` an; alles andere fällt
|
||
auf `alles` zurück, damit ein altes Lesezeichen nicht ins Leere zeigt. Den laufenden Monat
|
||
hebt `Budgetplaner::laufenderMonat()` farblich hervor.
|
||
|
||
Die ganze Tabelle steht in **einem** Formular und wird mit einem Knopf gespeichert – so
|
||
kommen mehrere geänderte Zeilen zusammen an.
|
||
|
||
### Werte über die Zeile verteilen
|
||
|
||
Vor jedem Eingabefeld sitzt ein **⚙**. Es öffnet zwei Einträge:
|
||
|
||
* **Ganzes Jahr** – der Wert des Feldes geht in alle zwölf Monate der Zeile
|
||
* **Restliches Jahr** – nur in die Monate danach
|
||
|
||
Beides überschreibt vorhandene Werte und arbeitet in `app.js` auf den Eingabefeldern, nicht
|
||
auf der Datenbank: Das Ergebnis ist vor dem Speichern zu sehen und geht mit demselben Knopf
|
||
weg wie jede andere Änderung. Das Menü gibt es nur einmal im Dokument und wandert in die
|
||
angeklickte Zelle – ein frei positioniertes bräuchte ein `style`-Attribut, das die
|
||
Content-Security-Policy verwirft.
|
||
|
||
`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, Kopie, Ansichtswahl
|
||
* `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,
|
||
Spaltenbild der drei Ansichten, Verteilen über das Zahnrad, Abschluss mit Snapshot und
|
||
Wiedereröffnung
|