docs: Ledger Ausbaustufe 5 Tasks 1-8 + Plan-Haekchen
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# Ausbaustufe 5 Implementation Plan — Deutsche Formate, Posten-Bearbeitung, Szenario-Ende (v0.6.0)
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [x]`) syntax for tracking.
|
||||
|
||||
**Goal:** Deutsche Zahlenformate in der gesamten Web-GUI, Inline-Bearbeitung aller Planungs-Objekte inkl. Start/Ende bei Fixposten, Szenario-Modifikator-Art »Ende« plus szenario-eigene Einmalzahlungen; Release v0.6.0 mit Live-Demo-Szenario »Best Case«.
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
**Interfaces:**
|
||||
- Produces: `app.formats.eur(value) -> str` (Decimal/str/int → `"−"`-lose deutsche Darstellung, z.B. `Decimal("-2474.5")` → `"-2.474,50"`, ohne €-Zeichen), `app.formats.de_label(value: str) -> str` (englischer API-Wert → deutsches Label, unbekannt → Rohwert), `app.formats.DE_LABELS: dict[str, str]`. Jinja-Filter `|eur` und `|de_label` in allen Templates verfügbar. Spätere Tasks (2, 4, 7) nutzen beide Filter.
|
||||
|
||||
- [ ] **Step 1: Failing Tests schreiben** — `tests/test_formats.py`:
|
||||
- [x] **Step 1: Failing Tests schreiben** — `tests/test_formats.py`:
|
||||
|
||||
```python
|
||||
from decimal import Decimal
|
||||
@@ -84,12 +84,12 @@ def test_de_label_bekannte_und_unbekannte_werte():
|
||||
"bullet", "percent", "absolute", "remove", "ende"}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Fehlschlag verifizieren**
|
||||
- [x] **Step 2: Fehlschlag verifizieren**
|
||||
|
||||
Run: `.venv/bin/python -m pytest tests/test_formats.py -q`
|
||||
Expected: FAIL/ERROR mit `ModuleNotFoundError: No module named 'app.formats'`
|
||||
|
||||
- [ ] **Step 3: `app/formats.py` implementieren**
|
||||
- [x] **Step 3: `app/formats.py` implementieren**
|
||||
|
||||
```python
|
||||
"""Deutsche Anzeige-Formate fuer die GUI (Ausbaustufe 5).
|
||||
@@ -124,12 +124,12 @@ def de_label(value: str) -> str:
|
||||
return DE_LABELS.get(value, value)
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Tests grün verifizieren**
|
||||
- [x] **Step 4: Tests grün verifizieren**
|
||||
|
||||
Run: `.venv/bin/python -m pytest tests/test_formats.py -q`
|
||||
Expected: alle Tests PASS
|
||||
|
||||
- [ ] **Step 5: Filter registrieren** — `app/routers/gui.py`, direkt nach `templates.env.globals["app_version"] = get_version()`:
|
||||
- [x] **Step 5: Filter registrieren** — `app/routers/gui.py`, direkt nach `templates.env.globals["app_version"] = get_version()`:
|
||||
|
||||
```python
|
||||
from app.formats import de_label, eur # (oben bei den Imports einsortieren)
|
||||
@@ -138,7 +138,7 @@ templates.env.filters["eur"] = eur
|
||||
templates.env.filters["de_label"] = de_label
|
||||
```
|
||||
|
||||
- [ ] **Step 6: Failing GUI-Test für deutsche Anzeige** — in `tests/test_gui.py` ergänzen:
|
||||
- [x] **Step 6: Failing GUI-Test für deutsche Anzeige** — in `tests/test_gui.py` ergänzen:
|
||||
|
||||
```python
|
||||
def test_gui_zeigt_deutsche_betragsformate(client, db):
|
||||
@@ -168,7 +168,7 @@ def test_planung_zeigt_deutsche_labels(client, db):
|
||||
|
||||
Run: `.venv/bin/python -m pytest tests/test_gui.py -q` → die zwei neuen Tests FAIL (Punktformat/englische Labels im HTML).
|
||||
|
||||
- [ ] **Step 7: Templates umstellen** — alle `{{ '%.2f'|format(X) }}`-Vorkommen ersetzen durch `{{ X|eur }}` (Fundstellen: `index.html` Zeilen 14/29/32/71, `salden.html` 20/27/51/53, `transactions.html` 56, `_preview_table.html` 11/28/29, `planning.html` 16/67/101/146/147/231/284). In `planning.html` zusätzlich Labels:
|
||||
- [x] **Step 7: Templates umstellen** — alle `{{ '%.2f'|format(X) }}`-Vorkommen ersetzen durch `{{ X|eur }}` (Fundstellen: `index.html` Zeilen 14/29/32/71, `salden.html` 20/27/51/53, `transactions.html` 56, `_preview_table.html` 11/28/29, `planning.html` 16/67/101/146/147/231/284). In `planning.html` zusätzlich Labels:
|
||||
- Zeile 17 `{{ r.rhythm }}` → `{{ r.rhythm|de_label }}`; Zeile 68 (Vorschläge) `{{ s.rhythm }}` → `{{ s.rhythm|de_label }}`; Zeile 148 `{{ l.repayment_type }}` → `{{ l.repayment_type|de_label }}`; Zeile 230 `{{ m.kind }}` → `{{ m.kind|de_label }}`.
|
||||
- Dropdown-Optionen: `<option value="{{ rh }}">{{ rh|de_label }}</option>`, `<option value="{{ rt }}">{{ rt|de_label }}</option>`, `<option value="{{ k }}">{{ k|de_label }}</option>`.
|
||||
- Kredit-Zins Zeile 147: `{{ l.annual_rate_pct|eur }} %`.
|
||||
@@ -185,18 +185,18 @@ function fmtEur(n) {
|
||||
+ '<td>' + fmtEur(r.remaining) + ' €</td></tr>';
|
||||
```
|
||||
|
||||
- [ ] **Step 8: Bestehende Punktformat-Assertions anpassen**
|
||||
- [x] **Step 8: Bestehende Punktformat-Assertions anpassen**
|
||||
|
||||
Run: `grep -rn '\.00\|\.50\|\.99' tests/test_gui.py` und jede Assertion prüfen, die gerendertes HTML gegen Punktformat testet. Bekannt: `test_index_shows_account_anchor` erwartet `"Anker: 321.00"` → ändern in `"Anker: 321,00"`. Weitere Treffer analog auf Komma-Format umstellen (NUR Assertions gegen `r.text`/HTML — JSON-API-Assertions bleiben Punktformat!).
|
||||
|
||||
- [ ] **Step 9: Gesamte Suite grün**
|
||||
- [x] **Step 9: Gesamte Suite grün**
|
||||
|
||||
Run: `.venv/bin/python -m pytest -q`
|
||||
Expected: alle Tests passed (154 alt + 8 neu)
|
||||
|
||||
- [ ] **Step 10: Fable-Testagent-Abnahme einholen** (Faktencheck: Filter-Korrektheit inkl. Rundung/negativer Werte, Vollständigkeit der Template-Umstellung via grep `'%.2f'` → 0 Treffer in `app/templates/`, keine API-Format-Änderung). Erst nach VERIFIED weiter.
|
||||
- [x] **Step 10: Fable-Testagent-Abnahme einholen** (Faktencheck: Filter-Korrektheit inkl. Rundung/negativer Werte, Vollständigkeit der Template-Umstellung via grep `'%.2f'` → 0 Treffer in `app/templates/`, keine API-Format-Änderung). Erst nach VERIFIED weiter.
|
||||
|
||||
- [ ] **Step 11: Commit**
|
||||
- [x] **Step 11: Commit**
|
||||
|
||||
```bash
|
||||
cd /home/wlfb/bin && git add finance/app/formats.py finance/tests/test_formats.py finance/app/routers/gui.py finance/app/templates/ finance/tests/test_gui.py
|
||||
@@ -216,7 +216,7 @@ git commit -m "feat: deutsche Betragsanzeige (eur-Filter) und deutsche GUI-Label
|
||||
- Consumes: Filter `|eur` aus Task 1 (Vorbefüllung des Anker-Inputs).
|
||||
- Produces: json-form-Konvention `data-type="amount"`: Eingabewert wird vor dem JSON-Versand normalisiert (Leerzeichen/`€` entfernen; enthält der Wert ein Komma → Tausenderpunkte entfernen, Komma→Punkt; ohne Komma unverändert). Tasks 4 und 7 verwenden `data-type="amount"` in ihren Formularen.
|
||||
|
||||
- [ ] **Step 1: Failing Test — Attribute im gerenderten HTML** (die JS-Konvertierung selbst ist serverseitig nicht testbar; sie wird im Fable-Live-Test geprüft). In `tests/test_gui.py`:
|
||||
- [x] **Step 1: Failing Test — Attribute im gerenderten HTML** (die JS-Konvertierung selbst ist serverseitig nicht testbar; sie wird im Fable-Live-Test geprüft). In `tests/test_gui.py`:
|
||||
|
||||
```python
|
||||
def test_betragsfelder_haben_amount_typ_und_deutsche_platzhalter(client):
|
||||
@@ -232,7 +232,7 @@ def test_betragsfelder_haben_amount_typ_und_deutsche_platzhalter(client):
|
||||
|
||||
Run: `.venv/bin/python -m pytest tests/test_gui.py::test_betragsfelder_haben_amount_typ_und_deutsche_platzhalter -q` → FAIL
|
||||
|
||||
- [ ] **Step 2: json-form-Extension erweitern** — `base.html`, in `encodeParameters` vor dem `else`-Zweig:
|
||||
- [x] **Step 2: json-form-Extension erweitern** — `base.html`, in `encodeParameters` vor dem `else`-Zweig:
|
||||
|
||||
```javascript
|
||||
} else if (kind === 'int') {
|
||||
@@ -248,7 +248,7 @@ Run: `.venv/bin/python -m pytest tests/test_gui.py::test_betragsfelder_haben_amo
|
||||
} else {
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Eingabefelder umstellen**
|
||||
- [x] **Step 3: Eingabefelder umstellen**
|
||||
- `planning.html` Fixposten-Formular: `<input type="text" name="amount" data-type="amount" placeholder="-49,99" required>`
|
||||
- `planning.html` Einmalposten-Formular: `placeholder="-2000,00"`, `data-type="amount"`
|
||||
- `planning.html` Kredit-Formular: `principal` → `data-type="amount" placeholder="10000,00"`; `annual_rate_pct` → `data-type="amount" placeholder="4,5"`
|
||||
@@ -256,14 +256,14 @@ Run: `.venv/bin/python -m pytest tests/test_gui.py::test_betragsfelder_haben_amo
|
||||
- `index.html` Anker-Formular: `<input type="text" name="anchor_balance" data-type="amount" placeholder="500,00" value="{{ account.anchor_balance|eur if account.anchor_balance is not none else '' }}" required>`
|
||||
- Die versteckten Felder der Vorschlags-Übernahme (`<input type="hidden" name="amount" value="{{ s.amount }}">`) bekommen KEIN `data-type="amount"` — der Server liefert dort Punktformat, und die amount-Normalisierung ließe es unverändert; ohne Attribut bleibt die Semantik explizit.
|
||||
|
||||
- [ ] **Step 4: Tests grün + Suite**
|
||||
- [x] **Step 4: Tests grün + Suite**
|
||||
|
||||
Run: `.venv/bin/python -m pytest tests/test_gui.py -q && .venv/bin/python -m pytest -q`
|
||||
Expected: alles PASS
|
||||
|
||||
- [ ] **Step 5: Fable-Testagent-Abnahme** — diesmal MIT Live-Anteil: gegen die laufende Instanz (oder lokal gestartete App) im Browser/per curl prüfen, dass ein Fixposten mit Eingabe `1.234,56` als `1234.56` gespeichert wird und `-49,99` korrekt ankommt. Erst nach VERIFIED weiter. (Hinweis: Wenn erst beim Release-Task deployt wird, den Live-Anteil per `uvicorn`-Teststart gegen SQLite erledigen: `cd finance && FB_DATABASE_URL=sqlite:///./demo.sqlite FB_GUI_PASSWORD_HASH=$(.venv/bin/python -c "from app.auth import hash_password; print(hash_password('demo'))") .venv/bin/uvicorn app.main:app --port 8123` — Wegwerf-DB danach löschen.)
|
||||
- [x] **Step 5: Fable-Testagent-Abnahme** — diesmal MIT Live-Anteil: gegen die laufende Instanz (oder lokal gestartete App) im Browser/per curl prüfen, dass ein Fixposten mit Eingabe `1.234,56` als `1234.56` gespeichert wird und `-49,99` korrekt ankommt. Erst nach VERIFIED weiter. (Hinweis: Wenn erst beim Release-Task deployt wird, den Live-Anteil per `uvicorn`-Teststart gegen SQLite erledigen: `cd finance && FB_DATABASE_URL=sqlite:///./demo.sqlite FB_GUI_PASSWORD_HASH=$(.venv/bin/python -c "from app.auth import hash_password; print(hash_password('demo'))") .venv/bin/uvicorn app.main:app --port 8123` — Wegwerf-DB danach löschen.)
|
||||
|
||||
- [ ] **Step 6: Commit**
|
||||
- [x] **Step 6: Commit**
|
||||
|
||||
```bash
|
||||
cd /home/wlfb/bin && git add finance/app/templates/ finance/tests/test_gui.py
|
||||
@@ -283,7 +283,7 @@ git commit -m "feat: Betragseingabe mit Komma (data-type=amount in json-form)"
|
||||
- Consumes: bestehende Felder `RecurringItem.start_date/end_date`, `RecurringIn/RecurringPatch.start_date/end_date` (existieren schon), Filter `|eur`/`|de_label`.
|
||||
- Produces: Validierungsregel »`end_date >= start_date`, sonst 422 mit Meldung „Ende darf nicht vor Start liegen"« bei POST und PATCH `/api/recurring`. GUI-Spalten »Start«/»Ende«. Task 4 baut das Bearbeiten-Formular auf dieselben Felder.
|
||||
|
||||
- [ ] **Step 1: Failing API-Tests** — in `tests/test_planning_api.py`:
|
||||
- [x] **Step 1: Failing API-Tests** — in `tests/test_planning_api.py`:
|
||||
|
||||
```python
|
||||
def test_recurring_ende_vor_start_wird_abgelehnt(client):
|
||||
@@ -312,7 +312,7 @@ def test_recurring_start_ende_roundtrip_und_patch_validierung(client):
|
||||
|
||||
Run: `.venv/bin/python -m pytest tests/test_planning_api.py -q` → die neuen Tests FAIL (POST liefert 201 statt 422 bzw. PATCH 200 statt 422)
|
||||
|
||||
- [ ] **Step 2: Validierung implementieren** — `app/routers/planning.py`:
|
||||
- [x] **Step 2: Validierung implementieren** — `app/routers/planning.py`:
|
||||
|
||||
Import ergänzen: `from pydantic import BaseModel, ConfigDict, Field, model_validator`
|
||||
|
||||
@@ -345,11 +345,11 @@ In `patch_recurring` nach der `setattr`-Schleife, vor `session.commit()`:
|
||||
raise HTTPException(422, "Ende darf nicht vor Start liegen")
|
||||
```
|
||||
|
||||
- [ ] **Step 3: API-Tests grün**
|
||||
- [x] **Step 3: API-Tests grün**
|
||||
|
||||
Run: `.venv/bin/python -m pytest tests/test_planning_api.py -q` → PASS
|
||||
|
||||
- [ ] **Step 4: Failing GUI-Test** — in `tests/test_gui.py`:
|
||||
- [x] **Step 4: Failing GUI-Test** — in `tests/test_gui.py`:
|
||||
|
||||
```python
|
||||
def test_planung_fixposten_zeigt_start_und_ende(client, db):
|
||||
@@ -366,7 +366,7 @@ def test_planung_fixposten_zeigt_start_und_ende(client, db):
|
||||
|
||||
Run: → FAIL
|
||||
|
||||
- [ ] **Step 5: Template** — `planning.html`, Fixposten-Tabelle:
|
||||
- [x] **Step 5: Template** — `planning.html`, Fixposten-Tabelle:
|
||||
|
||||
Kopfzeile: `<tr><th>Name</th><th>Betrag</th><th>Rhythmus</th><th>Fälligkeitstag</th><th>Start</th><th>Ende</th><th>Kategorie</th><th></th></tr>`
|
||||
|
||||
@@ -386,13 +386,13 @@ Datenzeile (nach der Fälligkeitstag-Zelle):
|
||||
|
||||
(Leere Datumsfelder sendet die json-form-Extension bereits als `null`.)
|
||||
|
||||
- [ ] **Step 6: Suite grün**
|
||||
- [x] **Step 6: Suite grün**
|
||||
|
||||
Run: `.venv/bin/python -m pytest -q` → PASS
|
||||
|
||||
- [ ] **Step 7: Fable-Testagent-Abnahme** (Faktencheck: Validierung POST+PATCH inkl. Null-Löschen; GUI-Spalten; `occurrences` klammert Start/Ende korrekt — bestehende Engine-Tests referenzieren). Erst nach VERIFIED weiter.
|
||||
- [x] **Step 7: Fable-Testagent-Abnahme** (Faktencheck: Validierung POST+PATCH inkl. Null-Löschen; GUI-Spalten; `occurrences` klammert Start/Ende korrekt — bestehende Engine-Tests referenzieren). Erst nach VERIFIED weiter.
|
||||
|
||||
- [ ] **Step 8: Commit**
|
||||
- [x] **Step 8: Commit**
|
||||
|
||||
```bash
|
||||
cd /home/wlfb/bin && git add finance/app/routers/planning.py finance/app/templates/planning.html finance/tests/
|
||||
@@ -411,7 +411,7 @@ git commit -m "feat: Start/Ende fuer Fixposten in der GUI + Datumsvalidierung"
|
||||
- Consumes: `PATCH /api/recurring/{id}`, `/api/planned/{id}`, `/api/loans/{id}`, `/api/scenarios/{id}` (existieren alle); json-form inkl. `data-type="amount"` (Task 2); Filter `|eur`/`|de_label` (Task 1); Start/Ende-Felder (Task 3).
|
||||
- Produces: JS-Helfer `toggleEdit(prefix, id, editing)` in `planning.html`; Formularzeilen-IDs `rec-edit-{id}`, `pln-edit-{id}`, `loan-edit-{id}`, `sc-edit-{id}` und Anzeige-IDs `rec-row-{id}`, `pln-row-{id}`, `loan-row-{id}`, `sc-head-{id}`. Task 7 folgt demselben Muster nicht (Szenario-Unterobjekte haben eigene Formulare), benötigt aber `toggleEdit` nicht.
|
||||
|
||||
- [ ] **Step 1: Failing GUI-Test**
|
||||
- [x] **Step 1: Failing GUI-Test**
|
||||
|
||||
```python
|
||||
def test_planung_hat_bearbeiten_formulare(client, db):
|
||||
@@ -434,7 +434,7 @@ Imports in `tests/test_gui.py` ergänzen: `from app.models.tables import Account
|
||||
|
||||
Run: `.venv/bin/python -m pytest tests/test_gui.py::test_planung_hat_bearbeiten_formulare -q` → FAIL
|
||||
|
||||
- [ ] **Step 2: JS-Helfer in `planning.html`** (in den bestehenden `<script>`-Block):
|
||||
- [x] **Step 2: JS-Helfer in `planning.html`** (in den bestehenden `<script>`-Block):
|
||||
|
||||
```javascript
|
||||
function toggleEdit(prefix, id, editing) {
|
||||
@@ -444,7 +444,7 @@ function toggleEdit(prefix, id, editing) {
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Fixposten-Zeile umbauen** — Anzeige-`<tr>` bekommt `id="rec-row-{{ r.id }}"`, in der Aktions-Zelle vor dem Löschen-Formular:
|
||||
- [x] **Step 3: Fixposten-Zeile umbauen** — Anzeige-`<tr>` bekommt `id="rec-row-{{ r.id }}"`, in der Aktions-Zelle vor dem Löschen-Formular:
|
||||
|
||||
```html
|
||||
<button type="button" onclick="toggleEdit('rec', {{ r.id }}, true)">Bearbeiten</button>
|
||||
@@ -480,7 +480,7 @@ Direkt nach der Anzeige-Zeile die Bearbeitungszeile:
|
||||
</tr>
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Einmalposten analog** — Anzeige-`<tr id="pln-row-{{ p.id }}">`, Button `toggleEdit('pln', {{ p.id }}, true)`, Bearbeitungszeile:
|
||||
- [x] **Step 4: Einmalposten analog** — Anzeige-`<tr id="pln-row-{{ p.id }}">`, Button `toggleEdit('pln', {{ p.id }}, true)`, Bearbeitungszeile:
|
||||
|
||||
```html
|
||||
<tr id="pln-edit-{{ p.id }}" hidden>
|
||||
@@ -503,7 +503,7 @@ Direkt nach der Anzeige-Zeile die Bearbeitungszeile:
|
||||
</tr>
|
||||
```
|
||||
|
||||
- [ ] **Step 5: Kredite analog** — Anzeige-`<tr id="loan-row-{{ l.id }}">`, Button `toggleEdit('loan', {{ l.id }}, true)`, Bearbeitungszeile (`colspan="7"`):
|
||||
- [x] **Step 5: Kredite analog** — Anzeige-`<tr id="loan-row-{{ l.id }}">`, Button `toggleEdit('loan', {{ l.id }}, true)`, Bearbeitungszeile (`colspan="7"`):
|
||||
|
||||
```html
|
||||
<tr id="loan-edit-{{ l.id }}" hidden>
|
||||
@@ -529,7 +529,7 @@ Direkt nach der Anzeige-Zeile die Bearbeitungszeile:
|
||||
|
||||
Hinweis im Template als Kommentar: eine Kredit-Änderung ändert den Tilgungsplan; Szenarien müssen danach neu durchgerechnet werden.
|
||||
|
||||
- [ ] **Step 6: Szenario-Kopf** — der beschreibende `<p>` im Szenario-Fieldset bekommt `id="sc-head-{{ sc.id }}"` und daneben einen Button `<button type="button" onclick="toggleEdit('sc', sc_id, true)">Bearbeiten</button>` (mit `{{ sc.id }}`); direkt danach ein verstecktes `<div id="sc-edit-{{ sc.id }}" hidden>`:
|
||||
- [x] **Step 6: Szenario-Kopf** — der beschreibende `<p>` im Szenario-Fieldset bekommt `id="sc-head-{{ sc.id }}"` und daneben einen Button `<button type="button" onclick="toggleEdit('sc', sc_id, true)">Bearbeiten</button>` (mit `{{ sc.id }}`); direkt danach ein verstecktes `<div id="sc-edit-{{ sc.id }}" hidden>`:
|
||||
|
||||
```html
|
||||
<div id="sc-edit-{{ sc.id }}" hidden>
|
||||
@@ -547,13 +547,13 @@ Hinweis im Template als Kommentar: eine Kredit-Änderung ändert den Tilgungspla
|
||||
|
||||
Achtung `toggleEdit`-Prefix `sc` erwartet `sc-row-{id}` — hier heißt das Anzeige-Element `sc-head-{id}`. Deshalb `toggleEdit` generisch halten: die Anzeige-ID als `prefix + '-row-' + id` ODER Sonderfall vermeiden, indem der `<p>` die ID `sc-row-{{ sc.id }}` bekommt (einfachste Lösung — so umsetzen, `sc-head` nicht verwenden).
|
||||
|
||||
- [ ] **Step 7: Tests + Suite grün**
|
||||
- [x] **Step 7: Tests + Suite grün**
|
||||
|
||||
Run: `.venv/bin/python -m pytest -q` → PASS
|
||||
|
||||
- [ ] **Step 8: Fable-Testagent-Abnahme** — Live-Test (uvicorn-Teststart wie in Task 2 oder deployte Instanz): je Bereich einmal Bearbeiten→Ändern→Speichern→Reload prüfen, inkl. deutscher Betragseingabe im Edit-Formular (vorbefüllt `1.234,56` muss nach Speichern unverändert bleiben) und Abbrechen. Erst nach VERIFIED weiter.
|
||||
- [x] **Step 8: Fable-Testagent-Abnahme** — Live-Test (uvicorn-Teststart wie in Task 2 oder deployte Instanz): je Bereich einmal Bearbeiten→Ändern→Speichern→Reload prüfen, inkl. deutscher Betragseingabe im Edit-Formular (vorbefüllt `1.234,56` muss nach Speichern unverändert bleiben) und Abbrechen. Erst nach VERIFIED weiter.
|
||||
|
||||
- [ ] **Step 9: Commit**
|
||||
- [x] **Step 9: Commit**
|
||||
|
||||
```bash
|
||||
cd /home/wlfb/bin && git add finance/app/templates/planning.html finance/tests/test_gui.py
|
||||
@@ -574,7 +574,7 @@ git commit -m "feat: Inline-Bearbeiten fuer Fixposten, Einmalposten, Kredite und
|
||||
- Consumes: `engine.recurrence.occurrences(rhythm, due_day, window_start, window_end, item_start, item_end)` (unverändert).
|
||||
- Produces: `ScenarioModifier.end_date: date | None`; ORM-Klasse `ScenarioPlannedItem(id, scenario_id, name, amount, due)` (Tabelle `scenario_planned_items`); `PlainModifier(target_type, target_id, kind, value, end_date: date | None = None)` — Task 6 baut API/Service darauf. Modifikator-Art-String: `"ende"`.
|
||||
|
||||
- [ ] **Step 1: Failing Engine-Tests** — in `tests/test_scenario_engine.py` (vorhandene Imports: `build_cashflows`, `PlainRecurring`, `PlainPlanned`, `PlainModifier`, `date`, `Decimal` — prüfen und ggf. ergänzen):
|
||||
- [x] **Step 1: Failing Engine-Tests** — in `tests/test_scenario_engine.py` (vorhandene Imports: `build_cashflows`, `PlainRecurring`, `PlainPlanned`, `PlainModifier`, `date`, `Decimal` — prüfen und ggf. ergänzen):
|
||||
|
||||
```python
|
||||
def test_ende_modifier_beendet_posten_im_szenario():
|
||||
@@ -627,7 +627,7 @@ def test_ende_modifier_aendert_betraege_nicht():
|
||||
|
||||
Run: `.venv/bin/python -m pytest tests/test_scenario_engine.py -q` → FAIL (`PlainModifier` nimmt kein 5. Argument)
|
||||
|
||||
- [ ] **Step 2: Engine implementieren** — `app/engine/scenario.py`:
|
||||
- [x] **Step 2: Engine implementieren** — `app/engine/scenario.py`:
|
||||
|
||||
`PlainModifier` erweitern:
|
||||
|
||||
@@ -671,11 +671,11 @@ In `build_cashflows`:
|
||||
|
||||
`_modified` braucht KEINE Änderung (`ende` trifft keinen der kind-Zweige und lässt den Betrag unverändert) — der Test aus Step 1 sichert das ab.
|
||||
|
||||
- [ ] **Step 3: Engine-Tests grün**
|
||||
- [x] **Step 3: Engine-Tests grün**
|
||||
|
||||
Run: `.venv/bin/python -m pytest tests/test_scenario_engine.py -q` → PASS
|
||||
|
||||
- [ ] **Step 4: Failing Modell-Test** — in `tests/test_models.py`:
|
||||
- [x] **Step 4: Failing Modell-Test** — in `tests/test_models.py`:
|
||||
|
||||
```python
|
||||
def test_scenario_planned_item_roundtrip(db):
|
||||
@@ -692,7 +692,7 @@ def test_scenario_planned_item_roundtrip(db):
|
||||
|
||||
Run: → FAIL (`ImportError`)
|
||||
|
||||
- [ ] **Step 5: Modelle** — `app/models/tables.py`:
|
||||
- [x] **Step 5: Modelle** — `app/models/tables.py`:
|
||||
|
||||
In `ScenarioModifier` ergänzen:
|
||||
|
||||
@@ -719,7 +719,7 @@ class ScenarioPlannedItem(Base):
|
||||
|
||||
Run: `.venv/bin/python -m pytest tests/test_models.py -q` → PASS
|
||||
|
||||
- [ ] **Step 6: Alembic-Migration** — Create `alembic/versions/c4d7e2a91b53_szenario_ende.py`:
|
||||
- [x] **Step 6: Alembic-Migration** — Create `alembic/versions/c4d7e2a91b53_szenario_ende.py`:
|
||||
|
||||
```python
|
||||
"""szenario ende + scenario_planned_items
|
||||
@@ -761,7 +761,7 @@ def downgrade() -> None:
|
||||
op.drop_column('scenario_modifiers', 'end_date')
|
||||
```
|
||||
|
||||
- [ ] **Step 7: Migration in Wegwerf-DB verifizieren** (env.py liest `FB_DATABASE_URL`):
|
||||
- [x] **Step 7: Migration in Wegwerf-DB verifizieren** (env.py liest `FB_DATABASE_URL`):
|
||||
|
||||
```bash
|
||||
cd /home/wlfb/bin/finance
|
||||
@@ -777,13 +777,13 @@ print('Migration OK')"
|
||||
|
||||
Expected: `Migration OK`. Danach Wegwerf-Datei löschen.
|
||||
|
||||
- [ ] **Step 8: Suite grün**
|
||||
- [x] **Step 8: Suite grün**
|
||||
|
||||
Run: `.venv/bin/python -m pytest -q` → PASS
|
||||
|
||||
- [ ] **Step 9: Fable-Testagent-Abnahme** (Faktencheck: Minimum-Regel, Kategorie-Treffer, Migration up/down konsistent zu `tables.py`, `_modified` unangetastet für `ende`). Erst nach VERIFIED weiter.
|
||||
- [x] **Step 9: Fable-Testagent-Abnahme** (Faktencheck: Minimum-Regel, Kategorie-Treffer, Migration up/down konsistent zu `tables.py`, `_modified` unangetastet für `ende`). Erst nach VERIFIED weiter.
|
||||
|
||||
- [ ] **Step 10: Commit**
|
||||
- [x] **Step 10: Commit**
|
||||
|
||||
```bash
|
||||
cd /home/wlfb/bin && git add finance/app/models/tables.py finance/app/engine/scenario.py finance/alembic/versions/c4d7e2a91b53_szenario_ende.py finance/tests/
|
||||
@@ -803,7 +803,7 @@ git commit -m "feat: Engine+Schema fuer Szenario-Ende und szenario-eigene Einmal
|
||||
- Consumes: `ScenarioPlannedItem`, `ScenarioModifier.end_date`, `PlainModifier(..., end_date)` aus Task 5.
|
||||
- Produces: `ModifierIn`/`ModifierOut` mit `kind`-Literal `"ende"` und Feld `end_date: date | None`; Endpunkte `GET/POST /api/scenarios/{id}/planned` (`ScenarioPlannedOut(id, scenario_id, name, amount, due)`), `DELETE /api/scenarios/{id}/planned/{item_id}`. Task 7 (GUI) und Task 9 (Demo) rufen genau diese Endpunkte.
|
||||
|
||||
- [ ] **Step 1: Failing API-Tests** — in `tests/test_crud_api.py`:
|
||||
- [x] **Step 1: Failing API-Tests** — in `tests/test_crud_api.py`:
|
||||
|
||||
```python
|
||||
def test_modifier_ende_validierung(client, db):
|
||||
@@ -871,7 +871,7 @@ def test_projektion_mit_ende_modifier_und_szenario_einmalzahlung(client, db):
|
||||
|
||||
Run: `.venv/bin/python -m pytest tests/test_crud_api.py tests/test_planning_api.py -q` → neue Tests FAIL
|
||||
|
||||
- [ ] **Step 2: `ModifierIn` erweitern** — `app/routers/scenarios.py`:
|
||||
- [x] **Step 2: `ModifierIn` erweitern** — `app/routers/scenarios.py`:
|
||||
|
||||
Imports: `from pydantic import BaseModel, ConfigDict, model_validator` und `ScenarioPlannedItem` in den `app.models.tables`-Import aufnehmen.
|
||||
|
||||
@@ -894,7 +894,7 @@ class ModifierIn(BaseModel):
|
||||
|
||||
(`ModifierOut` erbt `end_date` automatisch; `add_modifier` übergibt es via `**data.model_dump()` bereits.)
|
||||
|
||||
- [ ] **Step 3: Szenario-Einmalzahlungen-Endpunkte** — `app/routers/scenarios.py` (vor `project_scenario` einfügen):
|
||||
- [x] **Step 3: Szenario-Einmalzahlungen-Endpunkte** — `app/routers/scenarios.py` (vor `project_scenario` einfügen):
|
||||
|
||||
```python
|
||||
class ScenarioPlannedIn(BaseModel):
|
||||
@@ -948,7 +948,7 @@ In `delete_scenario` zusätzlich (bei den bestehenden Cleanup-Deletes):
|
||||
ScenarioPlannedItem.scenario_id == scenario_id))
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Projektions-Service** — `app/services/projection_service.py`:
|
||||
- [x] **Step 4: Projektions-Service** — `app/services/projection_service.py`:
|
||||
|
||||
`ScenarioPlannedItem` in den `app.models.tables`-Import aufnehmen. Nach dem `planned`-Block:
|
||||
|
||||
@@ -969,13 +969,13 @@ Modifier-Konstruktion um `end_date` ergänzen:
|
||||
ScenarioModifier.scenario_id == scenario.id)).scalars()]
|
||||
```
|
||||
|
||||
- [ ] **Step 5: Tests + Suite grün**
|
||||
- [x] **Step 5: Tests + Suite grün**
|
||||
|
||||
Run: `.venv/bin/python -m pytest -q` → PASS
|
||||
|
||||
- [ ] **Step 6: Fable-Testagent-Abnahme** (Faktencheck + Handrechnung der Projektions-Zahlen aus Step 1; 404-Pfade; Cleanup bei delete_scenario). Erst nach VERIFIED weiter.
|
||||
- [x] **Step 6: Fable-Testagent-Abnahme** (Faktencheck + Handrechnung der Projektions-Zahlen aus Step 1; 404-Pfade; Cleanup bei delete_scenario). Erst nach VERIFIED weiter.
|
||||
|
||||
- [ ] **Step 7: Commit**
|
||||
- [x] **Step 7: Commit**
|
||||
|
||||
```bash
|
||||
cd /home/wlfb/bin && git add finance/app/routers/scenarios.py finance/app/services/projection_service.py finance/tests/
|
||||
@@ -996,7 +996,7 @@ git commit -m "feat: API fuer Szenario-Ende-Modifikator und szenario-eigene Einm
|
||||
- Consumes: Endpunkte und Schemata aus Task 6; Filter `|eur`/`|de_label`; `ScenarioPlannedItem`.
|
||||
- Produces: Template-Kontext `row.planned_items` je Szenario; `modifier_kinds = ["percent", "absolute", "remove", "ende"]`; JS `onModKindChange(select)`.
|
||||
|
||||
- [ ] **Step 1: Failing GUI-Test**
|
||||
- [x] **Step 1: Failing GUI-Test**
|
||||
|
||||
```python
|
||||
def test_szenario_gui_ende_und_einmalzahlungen(client, db):
|
||||
@@ -1023,7 +1023,7 @@ Imports ergänzen: `ScenarioModifier, ScenarioPlannedItem` aus `app.models.table
|
||||
|
||||
Run: → FAIL
|
||||
|
||||
- [ ] **Step 2: GUI-Router** — `app/routers/gui.py`: `ScenarioPlannedItem` importieren. In `planung_page`: `"modifier_kinds": ["percent", "absolute", "remove", "ende"],`. In `_scenario_rows` je Szenario ergänzen:
|
||||
- [x] **Step 2: GUI-Router** — `app/routers/gui.py`: `ScenarioPlannedItem` importieren. In `planung_page`: `"modifier_kinds": ["percent", "absolute", "remove", "ende"],`. In `_scenario_rows` je Szenario ergänzen:
|
||||
|
||||
```python
|
||||
planned_items = session.execute(
|
||||
@@ -1034,7 +1034,7 @@ Run: → FAIL
|
||||
|
||||
und `"planned_items": planned_items,` ins `rows.append`-Dict.
|
||||
|
||||
- [ ] **Step 3: Modifikator-Formular + -Tabelle** — `planning.html`:
|
||||
- [x] **Step 3: Modifikator-Formular + -Tabelle** — `planning.html`:
|
||||
|
||||
Tabelle: Wert-Zelle ersetzen durch
|
||||
|
||||
@@ -1065,7 +1065,7 @@ function onModKindChange(select) {
|
||||
|
||||
(disabled-Felder fehlen in der Serialisierung → `value` fällt auf Default 0, `end_date` auf null — passt zur Validierung aus Task 6. UX-Regel eingehalten: Felder bleiben sichtbar, nur `disabled`.)
|
||||
|
||||
- [ ] **Step 4: Einmalzahlungen-Fieldset** — in `planning.html` im Szenario-Loop nach dem Modifikatoren-`</details>`:
|
||||
- [x] **Step 4: Einmalzahlungen-Fieldset** — in `planning.html` im Szenario-Loop nach dem Modifikatoren-`</details>`:
|
||||
|
||||
```html
|
||||
<details>
|
||||
@@ -1102,7 +1102,7 @@ function onModKindChange(select) {
|
||||
</details>
|
||||
```
|
||||
|
||||
- [ ] **Step 5: Hilfe-Abschnitt** — `hilfe.html`, im Szenarien-`<li>` (Zeile ~68) ergänzen (generisch, KEINE echten Daten):
|
||||
- [x] **Step 5: Hilfe-Abschnitt** — `hilfe.html`, im Szenarien-`<li>` (Zeile ~68) ergänzen (generisch, KEINE echten Daten):
|
||||
|
||||
```html
|
||||
Varianten wie „Best Case“ entstehen über Modifikatoren: Art „Ende“ lässt
|
||||
@@ -1112,13 +1112,13 @@ Szenarios zu einem Datum enden, ohne den Posten selbst zu ändern; „Prozent“
|
||||
Einmalzahlungen (z. B. ein erwarteter Zufluss) zählen nur in ihrem Szenario.
|
||||
```
|
||||
|
||||
- [ ] **Step 6: Suite grün**
|
||||
- [x] **Step 6: Suite grün**
|
||||
|
||||
Run: `.venv/bin/python -m pytest -q` → PASS
|
||||
|
||||
- [ ] **Step 7: Fable-Testagent-Abnahme** — Live-Test (uvicorn-Teststart, Muster Task 2): Modifikator »Ende« mit Datumsfeld-Umschaltung anlegen, Einmalzahlung anlegen/löschen, Szenario durchrechnen, Ergebnis plausibel. Erst nach VERIFIED weiter.
|
||||
- [x] **Step 7: Fable-Testagent-Abnahme** — Live-Test (uvicorn-Teststart, Muster Task 2): Modifikator »Ende« mit Datumsfeld-Umschaltung anlegen, Einmalzahlung anlegen/löschen, Szenario durchrechnen, Ergebnis plausibel. Erst nach VERIFIED weiter.
|
||||
|
||||
- [ ] **Step 8: Commit**
|
||||
- [x] **Step 8: Commit**
|
||||
|
||||
```bash
|
||||
cd /home/wlfb/bin && git add finance/app/routers/gui.py finance/app/templates/ finance/tests/test_gui.py
|
||||
@@ -1137,19 +1137,19 @@ git commit -m "feat: Szenario-GUI mit Ende-Modifikator und Einmalzahlungen je Sz
|
||||
- Consumes: alle Tasks 1–7 committet, Suite grün.
|
||||
- Produces: laufender Pod v0.6.0 mit angewendeter Migration `c4d7e2a91b53` — Voraussetzung für Task 9.
|
||||
|
||||
- [ ] **Step 1: Gesamte Suite final**
|
||||
- [x] **Step 1: Gesamte Suite final**
|
||||
|
||||
Run: `cd /home/wlfb/bin/finance && .venv/bin/python -m pytest -q`
|
||||
Expected: alle Tests passed, 0 failed
|
||||
|
||||
- [ ] **Step 2: Version hochzählen**
|
||||
- [x] **Step 2: Version hochzählen**
|
||||
|
||||
```bash
|
||||
echo "0.6.0" > /home/wlfb/bin/finance/VERSION
|
||||
cd /home/wlfb/bin && git add finance/VERSION && git commit -m "chore: Version 0.6.0"
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Redeploy** (baut Image mit Tag 0.6.0, Entrypoint führt `alembic upgrade head` aus):
|
||||
- [x] **Step 3: Redeploy** (baut Image mit Tag 0.6.0, Entrypoint führt `alembic upgrade head` aus):
|
||||
|
||||
```bash
|
||||
cd /home/wlfb/bin && ./create_pod_finance.sh
|
||||
@@ -1157,7 +1157,7 @@ cd /home/wlfb/bin && ./create_pod_finance.sh
|
||||
|
||||
Expected: Skript endet erfolgreich, Service aktiv.
|
||||
|
||||
- [ ] **Step 4: Live-Smoke**
|
||||
- [x] **Step 4: Live-Smoke**
|
||||
|
||||
```bash
|
||||
systemctl --user is-active pod-finance_pod.service # -> active
|
||||
@@ -1169,9 +1169,9 @@ podman exec finance-db_ctr psql -U finance -d finance -c "SELECT column_name FRO
|
||||
|
||||
Expected: Tabelle existiert, Spalte `end_date` vorhanden. (DB-Name/User ggf. aus `~/.local/share/finance_pod/.env` ablesen, falls abweichend.)
|
||||
|
||||
- [ ] **Step 5: Fable-Testagent-Abnahme** — Live: Login in die GUI, `/planung` rendert deutsche Formate, ein Test-Fixposten mit Komma-Eingabe anlegen und wieder löschen, Migration-Status wie Step 4. Erst nach VERIFIED weiter.
|
||||
- [x] **Step 5: Fable-Testagent-Abnahme** — Live: Login in die GUI, `/planung` rendert deutsche Formate, ein Test-Fixposten mit Komma-Eingabe anlegen und wieder löschen, Migration-Status wie Step 4. Erst nach VERIFIED weiter.
|
||||
|
||||
- [ ] **Step 6: Ledger + Push**
|
||||
- [x] **Step 6: Ledger + Push**
|
||||
|
||||
`.superpowers/sdd/progress.md`: je Task 1–8 eine Zeile (Commit-Range, Fable-Befund) im Stil der bestehenden Einträge. Dann:
|
||||
|
||||
|
||||
Reference in New Issue
Block a user