Files
bin/docs/superpowers/specs/2026-07-20-ausbaustufe-5-design.md

8.1 KiB

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_dateDate, 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/VERSION0.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.