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:
157
docs/superpowers/specs/2026-07-20-ausbaustufe-5-design.md
Normal file
157
docs/superpowers/specs/2026-07-20-ausbaustufe-5-design.md
Normal 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.
|
||||
Reference in New Issue
Block a user