docs: Design-Spec Ausbaustufe 5 (deutsche Formate, Posten-Bearbeitung, Szenario-Ende)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-20 10:45:39 +02:00
parent 8bbabf6657
commit 8e425840e8

View File

@@ -0,0 +1,157 @@
# Design — Ausbaustufe 5: Deutsche Formate, Posten-Bearbeitung, Szenario-Ende (v0.6.0)
> Status: vom Nutzer freigegeben (Chat 2026-07-20). Umsetzungsplan folgt unter
> `docs/superpowers/plans/`. Datenschutz-Regel aus `CLAUDE.md` gilt: konkrete
> Empfängernamen/Beträge des Demo-Szenarios stehen nur im Chat und in der
> Live-Datenbank, nicht in diesem Dokument, nicht in Commits, nicht im Ledger.
## Ziel
Drei Nutzeranforderungen an die Web-GUI des Finanzberatungs-Tools:
1. **Deutsche Formate** — Beträge werden deutsch angezeigt (`1.234,56 €`)
und deutsch eingegeben (Komma-Dezimaltrenner); englische GUI-Begriffe
(`monthly`, `annuity`, `percent`, …) werden übersetzt.
2. **Wiederkehrende Posten** — Felder `Start`/`Ende` in der GUI sichtbar und
pflegbar (Modell/API/Engine können das bereits); jeder Posten bekommt
einen `Bearbeiten`-Button. Inline-Bearbeitung zusätzlich für
Einmalposten, Kredite und den Szenario-Kopf (Nutzerentscheidung).
3. **Szenarien** — Modifikator-Art `Ende` (Posten endet innerhalb des
Szenarios an einem Datum, ohne den Posten selbst zu ändern) und
szenario-eigene Einmalzahlungen. Demo-Szenario „Best Case" wird nach dem
Release live angelegt und durchgerechnet.
## Nutzerentscheidungen (Chat 2026-07-20)
- Szenario-Ende als **neue Modifikator-Art `ende`** mit Datumsfeld (nicht
als Gültigkeitszeitraum aller Modifikatoren).
- Einmalzahlung im Demo-Szenario ist **szenario-spezifisch** → neues Feature
„Szenario-Einmalzahlungen".
- Formate: **Zahlen + deutsche Begriffe** (API-Werte bleiben englisch).
- Bearbeiten-Buttons für: wiederkehrende Posten, Einmalposten, Kredite,
Szenario-Kopf.
## Teil 1 — Deutsche Formate
**Anzeige.** Neuer Jinja-Filter `eur` in `app/routers/gui.py`
(`templates.env.filters["eur"]`), reine `Decimal`-Formatierung ohne
`locale`-Modul (Container-Locale unzuverlässig): Vorzeichen, Tausenderpunkt,
Komma, zwei Nachkommastellen. Ersetzt alle `'%.2f'|format(...)`-Stellen in
`index.html`, `salden.html`, `transactions.html`, `planning.html`,
`_preview_table.html`. Prozentwerte (Kredit-Zins) analog mit Komma.
Der per JavaScript nachgeladene Tilgungsplan (`planning.html`,
`loadLoanSchedule`) formatiert mit
`toLocaleString('de-DE', {minimumFractionDigits: 2, maximumFractionDigits: 2})`.
**Eingabe.** Die json-form-Extension in `base.html` lernt
`data-type="amount"`: Whitespace/€ entfernen; enthält der Wert ein Komma →
Tausenderpunkte entfernen, Komma→Punkt; ohne Komma bleibt der Wert
unverändert (Punkt-Eingaben funktionieren weiter). Alle
Betrags-`<input>`-Felder (Fixposten, Einmalposten, Kredit, Modifikator-Wert,
Szenario-Einmalzahlung) bekommen `data-type="amount"` und deutsche
Platzhalter (`-49,99`). Versteckte Felder der Vorschlags-Übernahme bleiben
ohne `data-type="amount"` (Server liefert Punktformat). Die JSON-API bleibt
strikt Punkt-Dezimal — Tests, Skripte und Grafana sind nicht betroffen.
**Begriffe.** Zentrales Mapping als Jinja-Global `de_label` (Definition in
`gui.py`): `monthly→monatlich`, `quarterly→vierteljährlich`,
`yearly→jährlich`, `annuity→Annuität`, `bullet→endfällig`,
`percent→Prozent`, `absolute→Absolut`, `remove→Entfällt`, `ende→Ende`.
Dropdown-`<option>`-Texte und Tabellenzellen zeigen deutsche Labels;
`value`-Attribute, API-Literale und DB-Werte bleiben englisch (keine
Migration, keine API-Änderung). Unbekannte Werte fallen auf den Rohwert
zurück.
## Teil 2 — Posten-Bearbeitung + Start/Ende
`RecurringItem.start_date/end_date` existieren in Modell, API
(`RecurringIn`/`RecurringPatch`) und Engine (`recurrence.occurrences`
klammert das Fenster bereits). Es fehlt nur GUI:
- Tabelle „Wiederkehrende Posten": neue Spalten **Start** und **Ende**
(TT.MM.JJJJ, leer = „–").
- Anlege-Formular: zwei optionale `<input type="date">`-Felder.
- **Inline-Bearbeiten** je Zeile (Muster für alle vier Bereiche):
`Bearbeiten`-Button blendet eine vorbefüllte Formularzeile ein
(Anzeigezeile wird versteckt, nicht entfernt — UX-Regel aus `CLAUDE.md`
bleibt gewahrt: Bedienelemente sichtbar/disabled, Fehler via bestehendem
`htmx:responseError`-Handler), `Speichern` sendet `PATCH` über die
json-form-Extension, `Abbrechen` klappt zurück. Leeres Datumsfeld wird als
`null` gesendet (Extension tut das bereits) und löscht den Wert.
- Bereiche: `PATCH /api/recurring/{id}` (alle Felder),
`PATCH /api/planned/{id}` (Name, Betrag, Fälligkeit, Kategorie),
`PATCH /api/loans/{id}` (alle Felder),
`PATCH /api/scenarios/{id}` (Name, Beschreibung, include-Flags).
- Neue Validierung in `RecurringIn`/`RecurringPatch` (Pydantic
model_validator): `end_date ≥ start_date`, wenn beide gesetzt (beim PATCH
gegen den resultierenden Zustand geprüft), sonst 422 mit deutscher
Meldung.
## Teil 3 — Szenarien: Art `ende` + Szenario-Einmalzahlungen
Eine Alembic-Migration (`scenario_ende`) mit zwei Änderungen:
1. `scenario_modifiers.end_date``Date`, nullable.
2. Neue Tabelle `scenario_planned_items`: `id`, `scenario_id`
(FK `scenarios.id`), `name` (String 200), `amount` (`MONEY`), `due`
(`Date`).
**Modifikator-Art `ende`.**
- API: `ModifierIn.kind` um Literal `ende` erweitert; neues optionales Feld
`end_date: date | None`. Validator: `ende` erfordert `end_date`; andere
Arten ignorieren es (wird genullt). `ModifierOut` liefert `end_date` mit.
- Engine: `PlainModifier` bekommt `end_date: date | None = None`.
`build_cashflows` bestimmt vor `occurrences` je Posten das effektive Ende:
Minimum aus eigenem `end_date` und allen treffenden `ende`-Modifikatoren
(Ziel `recurring` oder `category` — bei Kategorie enden alle Posten der
Kategorie). `_modified` behandelt weiterhin nur die Betrags-Arten und
überspringt `ende`.
- GUI (`planning.html`): Art-Dropdown bekommt „Ende"; ein Datumsfeld wird
nur bei dieser Art aktiviert (disabled-Umschalter analog
`onModTargetTypeChange`), das Wert-Feld ist bei `ende` disabled.
Modifikator-Tabelle zeigt das Datum.
**Szenario-Einmalzahlungen.**
- Endpunkte in `routers/scenarios.py`:
`GET/POST /api/scenarios/{id}/planned` (201, Name/Betrag/Datum),
`DELETE /api/scenarios/{id}/planned/{item_id}` (404 bei fremder
scenario_id, Muster `delete_modifier`).
- `run_projection` lädt `scenario_planned_items` des Szenarios und hängt sie
als `PlainPlanned` an — **unabhängig** von `include_planned` (das steuert
nur globale Einmalposten; szenario-eigene Zahlungen sind bewusst diesem
Szenario zugeordnet).
- `delete_scenario` löscht zusätzlich `scenario_planned_items` (kein
DB-Cascade vorhanden, Muster der bestehenden Deletes).
- GUI: je Szenario ein `<details>` „Einmalzahlungen in diesem Szenario"
(Tabelle Name/Betrag/Datum + Löschen, Anlege-Formular).
## Teil 4 — Demo „Best Case", Release, Tests
**Demo (live, nach Redeploy):** Szenario „Best Case" anlegen; für die beiden
bestehenden Miet-Fixposten (falls nicht vorhanden: vorher anlegen bzw. aus
Vorschlägen übernehmen) je einen `ende`-Modifikator mit den im Chat
genannten Terminen setzen; szenario-eigene Einmalzahlung mit dem im Chat
genannten Betrag/Datum anlegen; Projektion durchrechnen. Anschließend
Schritt-für-Schritt-Erklärung der Eingabe im Chat. `hilfe.html` bekommt
einen kurzen Abschnitt zu Szenario-Varianten (generisch, ohne echte Daten).
**Release:** `finance/VERSION``0.6.0` vor `./create_pod_finance.sh`
(Migration läuft im Entrypoint via `alembic upgrade head`).
**Tests (TDD, Suite bleibt komplett grün):**
- `eur`-Filter: Rundung, Tausenderpunkte, negative Werte, `Decimal`-Input.
- Engine: `ende`-Modifikator (Ziel Posten/Kategorie, Zusammenspiel mit
eigenem `end_date`, Minimum-Regel), Szenario-Einmalzahlung im Fenster.
- API: Modifier-Validierung (`ende` ohne Datum → 422), CRUD
Szenario-Einmalzahlungen, Cleanup bei `delete_scenario`,
`end ≥ start`-Validierung bei Fixposten.
- GUI-Tests: deutsche Betragsformatierung in gerenderten Seiten, neue
Spalten/Formulare vorhanden, deutsche Labels statt englischer Rohwerte.
- Migration: Upgrade-Pfad in Wegwerf-DB (Muster Konto-Anker-Task).
**Außerhalb des Scopes:** Grafana-Dashboards (formatiert selbst),
PDF/CSV-Parser (parsen deutsche Formate bereits), Übersetzung von
API-Fehlertexten Dritter, Mehrsprachigkeit.