docs: A14 Plan-Haken
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -46,7 +46,7 @@
|
||||
**Interfaces:**
|
||||
- Produces: `ScenarioModifier.start_date: date | None`; `ScenarioPlannedItem.category_id: int | None`; `ScenarioRecurringItem` (Tabelle `scenario_recurring_items`: `id`, `scenario_id`, `name`, `amount`, `rhythm`, `due_day`, `start_date`, `end_date`, `category_id`).
|
||||
|
||||
- [ ] **Step 1: Failing Test (SQLite-Roundtrip)** — an `finance/tests/test_models.py` anhängen:
|
||||
- [x] **Step 1: Failing Test (SQLite-Roundtrip)** — an `finance/tests/test_models.py` anhängen:
|
||||
|
||||
```python
|
||||
def test_a14_szenario_felder_roundtrip(db):
|
||||
@@ -75,9 +75,9 @@ def test_a14_szenario_felder_roundtrip(db):
|
||||
assert (w.scenario_id, w.rhythm, w.due_day, w.category_id) == (sc.id, "monthly", 14, cat.id)
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Test laufen lassen** — `cd finance && .venv/bin/python -m pytest -q tests/test_models.py::test_a14_szenario_felder_roundtrip` → FAIL (`ImportError: cannot import name 'ScenarioRecurringItem'`).
|
||||
- [x] **Step 2: Test laufen lassen** — `cd finance && .venv/bin/python -m pytest -q tests/test_models.py::test_a14_szenario_felder_roundtrip` → FAIL (`ImportError: cannot import name 'ScenarioRecurringItem'`).
|
||||
|
||||
- [ ] **Step 3: Modelle** — in `finance/app/models/tables.py`:
|
||||
- [x] **Step 3: Modelle** — in `finance/app/models/tables.py`:
|
||||
|
||||
`ScenarioModifier` (Kommentar über `end_date` ersetzen, `start_date` ergänzen):
|
||||
|
||||
@@ -123,7 +123,7 @@ class ScenarioRecurringItem(Base):
|
||||
category_id: Mapped[int | None] = mapped_column(ForeignKey("categories.id"), nullable=True)
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Migration** — `finance/alembic/versions/e7f1a9c3b214_szenario_zeitraum.py`:
|
||||
- [x] **Step 4: Migration** — `finance/alembic/versions/e7f1a9c3b214_szenario_zeitraum.py`:
|
||||
|
||||
```python
|
||||
"""szenario zeitraum, neuer betrag, szenario-eigene wiederkehrende zahlungen
|
||||
@@ -182,7 +182,7 @@ def downgrade() -> None:
|
||||
op.drop_column('scenario_modifiers', 'start_date')
|
||||
```
|
||||
|
||||
- [ ] **Step 5: Postgres-Migrationstest** — `finance/tests/test_migration_pg.py` (skip ohne `FB_TEST_PG_URL`, Muster `test_views_pg.py`):
|
||||
- [x] **Step 5: Postgres-Migrationstest** — `finance/tests/test_migration_pg.py` (skip ohne `FB_TEST_PG_URL`, Muster `test_views_pg.py`):
|
||||
|
||||
```python
|
||||
"""Postgres-only: Alembic-Revision e7f1a9c3b214 (Ausbaustufe 14) hoch/runter
|
||||
@@ -266,9 +266,9 @@ def test_migration_a14_up_down(alembic_cfg):
|
||||
assert "start_date" not in {c["name"] for c in insp.get_columns("scenario_modifiers")}
|
||||
```
|
||||
|
||||
- [ ] **Step 6: Tests laufen lassen** — `.venv/bin/python -m pytest -q` → alle grün, `test_migration_pg` skipped (Host ohne `FB_TEST_PG_URL`). Dann mit Wegwerf-Postgres (Kommando aus dem Docstring) `FB_TEST_PG_URL=… .venv/bin/python -m pytest -q tests/test_migration_pg.py tests/test_views_pg.py` → PASS; Container danach `podman stop a14-pg`.
|
||||
- [x] **Step 6: Tests laufen lassen** — `.venv/bin/python -m pytest -q` → alle grün, `test_migration_pg` skipped (Host ohne `FB_TEST_PG_URL`). Dann mit Wegwerf-Postgres (Kommando aus dem Docstring) `FB_TEST_PG_URL=… .venv/bin/python -m pytest -q tests/test_migration_pg.py tests/test_views_pg.py` → PASS; Container danach `podman stop a14-pg`.
|
||||
|
||||
- [ ] **Step 7: Commit**
|
||||
- [x] **Step 7: Commit**
|
||||
|
||||
```bash
|
||||
git add finance/app/models/tables.py finance/alembic/versions/e7f1a9c3b214_szenario_zeitraum.py \
|
||||
@@ -276,7 +276,7 @@ git add finance/app/models/tables.py finance/alembic/versions/e7f1a9c3b214_szena
|
||||
git commit -m "feat: Szenario-Zeitraum, Neuer Betrag, szenario-eigene Zahlungen – Datenmodell + Migration (A14-T1)"
|
||||
```
|
||||
|
||||
- [ ] **Step 8: Fable-Gate** — Wegwerf-Postgres selbst starten; `test_migration_pg.py` + `test_views_pg.py` grün; zusätzlich `alembic upgrade head` → `alembic downgrade -1` → `alembic upgrade head` per CLI (`FB_DATABASE_URL=… .venv/bin/alembic …`) ohne Fehler; Modell ↔ Migration abgleichen (Spaltentypen, Nullbarkeit, FK-Ziele, Index-Name) per `inspect` gegen `Base.metadata`; bestätigen, dass Live-DB unberührt ist (keine Verbindung zur Produktiv-DB).
|
||||
- [x] **Step 8: Fable-Gate** — Wegwerf-Postgres selbst starten; `test_migration_pg.py` + `test_views_pg.py` grün; zusätzlich `alembic upgrade head` → `alembic downgrade -1` → `alembic upgrade head` per CLI (`FB_DATABASE_URL=… .venv/bin/alembic …`) ohne Fehler; Modell ↔ Migration abgleichen (Spaltentypen, Nullbarkeit, FK-Ziele, Index-Name) per `inspect` gegen `Base.metadata`; bestätigen, dass Live-DB unberührt ist (keine Verbindung zur Produktiv-DB).
|
||||
|
||||
---
|
||||
|
||||
@@ -295,7 +295,7 @@ git commit -m "feat: Szenario-Zeitraum, Neuer Betrag, szenario-eigene Zahlungen
|
||||
- `modified_amount(amount, category_id, recurring_id, modifiers, *, on: date) -> Decimal | None` (`on` ist Pflicht, keyword-only).
|
||||
- `recurrence.due_in_month(month: date, due_day: int) -> date` (Fälligkeitstag im Monat von `month`, auf Monatslänge geklemmt).
|
||||
|
||||
- [ ] **Step 1: Failing Tests** — an `finance/tests/test_scenario_engine.py` anhängen:
|
||||
- [x] **Step 1: Failing Tests** — an `finance/tests/test_scenario_engine.py` anhängen:
|
||||
|
||||
```python
|
||||
D = Decimal
|
||||
@@ -407,9 +407,9 @@ In `finance/tests/test_scenario_inputs.py::test_modifikator_helfer_oeffentlich`
|
||||
assert modified_amount(Decimal("-100.00"), None, 2, mods, on=date(2030, 1, 1)) == Decimal("-100.00")
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Tests laufen lassen** — `.venv/bin/python -m pytest -q tests/test_scenario_engine.py tests/test_breakdown.py tests/test_recurrence.py tests/test_scenario_inputs.py` → FAIL (`unexpected keyword argument 'start_date'` / `'on'`, `cannot import name 'due_in_month'`).
|
||||
- [x] **Step 2: Tests laufen lassen** — `.venv/bin/python -m pytest -q tests/test_scenario_engine.py tests/test_breakdown.py tests/test_recurrence.py tests/test_scenario_inputs.py` → FAIL (`unexpected keyword argument 'start_date'` / `'on'`, `cannot import name 'due_in_month'`).
|
||||
|
||||
- [ ] **Step 3: `recurrence.py`** — nach `_clamp` ergänzen:
|
||||
- [x] **Step 3: `recurrence.py`** — nach `_clamp` ergänzen:
|
||||
|
||||
```python
|
||||
def due_in_month(month: date, due_day: int) -> date:
|
||||
@@ -418,7 +418,7 @@ def due_in_month(month: date, due_day: int) -> date:
|
||||
return _clamp(month.year, month.month, due_day)
|
||||
```
|
||||
|
||||
- [ ] **Step 4: `scenario.py`** — Dataclasses und Helfer ersetzen:
|
||||
- [x] **Step 4: `scenario.py`** — Dataclasses und Helfer ersetzen:
|
||||
|
||||
```python
|
||||
@dataclass(frozen=True)
|
||||
@@ -506,7 +506,7 @@ In `build_cashflows` die zwei Schleifen über `recurring`/`planned` ersetzen:
|
||||
flows.append((p.due, amount))
|
||||
```
|
||||
|
||||
- [ ] **Step 5: `breakdown.py`** — Import `from app.engine.recurrence import STEP, due_in_month`; im Modul-Docstring den Satz „Modifikatoren wirken exakt wie in build_cashflows (gleiche Helfer)." ersetzen durch „Modifikatoren wirken wie in build_cashflows (gleiche Helfer); Stichtag je Fixposten ist sein Fälligkeitstag im Monat, je Einmalposten das Fälligkeitsdatum." In `breakdown_rows`:
|
||||
- [x] **Step 5: `breakdown.py`** — Import `from app.engine.recurrence import STEP, due_in_month`; im Modul-Docstring den Satz „Modifikatoren wirken exakt wie in build_cashflows (gleiche Helfer)." ersetzen durch „Modifikatoren wirken wie in build_cashflows (gleiche Helfer); Stichtag je Fixposten ist sein Fälligkeitstag im Monat, je Einmalposten das Fälligkeitsdatum." In `breakdown_rows`:
|
||||
|
||||
```python
|
||||
amount = modified_amount(r.amount, r.category_id, r.id, modifiers,
|
||||
@@ -517,9 +517,9 @@ In `build_cashflows` die zwei Schleifen über `recurring`/`planned` ersetzen:
|
||||
amount = modified_amount(p.amount, p.category_id, None, modifiers, on=p.due)
|
||||
```
|
||||
|
||||
- [ ] **Step 6: Tests laufen lassen** — `.venv/bin/python -m pytest -q` → alle grün. `grep -n "^from\|^import" app/engine/scenario.py app/engine/breakdown.py` zeigt nur Stdlib und `app.engine.*`.
|
||||
- [x] **Step 6: Tests laufen lassen** — `.venv/bin/python -m pytest -q` → alle grün. `grep -n "^from\|^import" app/engine/scenario.py app/engine/breakdown.py` zeigt nur Stdlib und `app.engine.*`.
|
||||
|
||||
- [ ] **Step 7: Commit**
|
||||
- [x] **Step 7: Commit**
|
||||
|
||||
```bash
|
||||
git add finance/app/engine/scenario.py finance/app/engine/breakdown.py finance/app/engine/recurrence.py \
|
||||
@@ -528,7 +528,7 @@ git add finance/app/engine/scenario.py finance/app/engine/breakdown.py finance/a
|
||||
git commit -m "feat: Szenario-Einträge je Stichtag mit Zeitraum und Art Neuer Betrag (A14-T2)"
|
||||
```
|
||||
|
||||
- [ ] **Step 8: Fable-Gate** — unabhängige Handrechnung mit eigenen Werten: vierteljährlicher Posten mit Zeitraum, der eine Fälligkeit einschließt und eine ausschließt; `set` → `absolute` → `percent` in Folge; Kategorie- und Posten-Eintrag auf denselben Posten; Aufteilung eines vierteljährlichen Postens in einem Monat ohne Fälligkeit (Stichtag = Fälligkeitstag dieses Monats); Einträge ohne Datum liefern exakt dieselben Ergebnisse wie vor dem Commit (Projektion eines synthetischen Szenarios vor/nach dem Commit vergleichen, z. B. zwei Checkouts in einem Wegwerf-Verzeichnis); Reinheit + kein `float`.
|
||||
- [x] **Step 8: Fable-Gate** — unabhängige Handrechnung mit eigenen Werten: vierteljährlicher Posten mit Zeitraum, der eine Fälligkeit einschließt und eine ausschließt; `set` → `absolute` → `percent` in Folge; Kategorie- und Posten-Eintrag auf denselben Posten; Aufteilung eines vierteljährlichen Postens in einem Monat ohne Fälligkeit (Stichtag = Fälligkeitstag dieses Monats); Einträge ohne Datum liefern exakt dieselben Ergebnisse wie vor dem Commit (Projektion eines synthetischen Szenarios vor/nach dem Commit vergleichen, z. B. zwei Checkouts in einem Wegwerf-Verzeichnis); Reinheit + kein `float`.
|
||||
|
||||
---
|
||||
|
||||
@@ -543,7 +543,7 @@ git commit -m "feat: Szenario-Einträge je Stichtag mit Zeitraum und Art Neuer B
|
||||
- Consumes: `ScenarioRecurringItem`, `ScenarioModifier.start_date`, `ScenarioPlannedItem.category_id` (Task 1); `PlainRecurring(id=None, …)`, `PlainModifier(..., start_date=…)`, `build_cashflows` (Task 2); `app.routers.planning.RecurringIn`, `app.routers.planning._check_category` (bestehend).
|
||||
- Produces: `GET/POST /api/scenarios/{id}/recurring`, `PATCH/DELETE /api/scenarios/{id}/recurring/{item_id}` (Schema `RecurringIn`, Antwort `ScenarioRecurringOut` = `RecurringIn` + `id` + `scenario_id`); `ModifierIn.start_date`, `kind` inkl. `set`; `ScenarioPlannedIn.category_id`.
|
||||
|
||||
- [ ] **Step 1: Failing Tests (Service)** — an `finance/tests/test_scenario_inputs.py` anhängen (Imports oben um `build_cashflows` aus `app.engine.scenario` und `ScenarioRecurringItem`, `Category` aus `app.models.tables` ergänzen):
|
||||
- [x] **Step 1: Failing Tests (Service)** — an `finance/tests/test_scenario_inputs.py` anhängen (Imports oben um `build_cashflows` aus `app.engine.scenario` und `ScenarioRecurringItem`, `Category` aus `app.models.tables` ergänzen):
|
||||
|
||||
```python
|
||||
def test_szenario_zahlung_zaehlt_immer_und_id_kollision(db):
|
||||
@@ -589,7 +589,7 @@ def test_zeitraum_und_kategorie_werden_geladen(db):
|
||||
assert [(p.name, p.category_id) for p in inp.planned] == [("E", cat.id)]
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Failing Tests (API)** — in `finance/tests/test_crud_api.py`:
|
||||
- [x] **Step 2: Failing Tests (API)** — in `finance/tests/test_crud_api.py`:
|
||||
|
||||
Bestehende Erwartungen an die neue Semantik anpassen: in `test_modifier_ende_validierung` den letzten Block ersetzen durch
|
||||
|
||||
@@ -728,9 +728,9 @@ def test_scenario_planned_mit_kategorie(client, db):
|
||||
assert r.status_code == 200 and r.json()["category_id"] is None
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Tests laufen lassen** — `.venv/bin/python -m pytest -q tests/test_scenario_inputs.py tests/test_crud_api.py` → FAIL.
|
||||
- [x] **Step 3: Tests laufen lassen** — `.venv/bin/python -m pytest -q tests/test_scenario_inputs.py tests/test_crud_api.py` → FAIL.
|
||||
|
||||
- [ ] **Step 4: `load_scenario_inputs`** — Import `ScenarioRecurringItem` ergänzen; im Szenario-Zweig (nach dem `if scenario is None: … return`) den Block der Szenario-Einmalzahlungen ersetzen und Szenario-Zahlungen sowie `start_date` ergänzen:
|
||||
- [x] **Step 4: `load_scenario_inputs`** — Import `ScenarioRecurringItem` ergänzen; im Szenario-Zweig (nach dem `if scenario is None: … return`) den Block der Szenario-Einmalzahlungen ersetzen und Szenario-Zahlungen sowie `start_date` ergänzen:
|
||||
|
||||
```python
|
||||
# Szenario-eigene Zahlungen zählen IMMER für dieses Szenario -
|
||||
@@ -760,7 +760,7 @@ und die Modifikator-Liste:
|
||||
|
||||
Docstring von `load_scenario_inputs` um „keine Szenario-Zahlungen" ergänzen: „… keine Modifikatoren, keine Szenario-Einmalposten und keine szenario-eigenen wiederkehrenden Zahlungen."
|
||||
|
||||
- [ ] **Step 5: `routers/scenarios.py`** — Importe:
|
||||
- [x] **Step 5: `routers/scenarios.py`** — Importe:
|
||||
|
||||
```python
|
||||
from app.models.tables import (Loan, ProjectionPoint, ProjectionResult,
|
||||
@@ -868,9 +868,9 @@ def delete_scenario_recurring(scenario_id: int, item_id: int,
|
||||
|
||||
Prüfen, dass `app.routers.planning` `app.routers.scenarios` nicht importiert (kein Zirkelimport): `grep -n "scenarios" finance/app/routers/planning.py` → keine Import-Zeile.
|
||||
|
||||
- [ ] **Step 6: Tests laufen lassen** — `.venv/bin/python -m pytest -q` → alle grün.
|
||||
- [x] **Step 6: Tests laufen lassen** — `.venv/bin/python -m pytest -q` → alle grün.
|
||||
|
||||
- [ ] **Step 7: Commit**
|
||||
- [x] **Step 7: Commit**
|
||||
|
||||
```bash
|
||||
git add finance/app/services/projection_service.py finance/app/routers/scenarios.py \
|
||||
@@ -878,7 +878,7 @@ git add finance/app/services/projection_service.py finance/app/routers/scenarios
|
||||
git commit -m "feat: API für Szenario-Zeitraum, Neuer Betrag und szenario-eigene wiederkehrende Zahlungen (A14-T3)"
|
||||
```
|
||||
|
||||
- [ ] **Step 8: Fable-Gate** — Live-Approximation gegen lokalen uvicorn mit Wegwerf-SQLite (`FB_DATABASE_URL=sqlite:////<scratchpad>/a14.sqlite`, `FB_API_KEY=test`, `.venv/bin/alembic upgrade head`, `.venv/bin/uvicorn app.main:app --port 8199`): per curl ein Fixposten (monatlich am 14., erfundener Betrag), Szenario mit „Prozent 50 ab <16. eines Monats>" und einer szenario-eigenen wiederkehrenden Zahlung mit Kategorie; `POST /api/scenarios/{id}/project` → Tiefpunkt/Serie gegen eigene Handrechnung; alle 422/404-Fälle aus Step 2 per curl nachstellen (deutsche Texte in `detail`); `DELETE` des Szenarios hinterlässt 0 Zeilen in `scenario_recurring_items`; danach aufräumen.
|
||||
- [x] **Step 8: Fable-Gate** — Live-Approximation gegen lokalen uvicorn mit Wegwerf-SQLite (`FB_DATABASE_URL=sqlite:////<scratchpad>/a14.sqlite`, `FB_API_KEY=test`, `.venv/bin/alembic upgrade head`, `.venv/bin/uvicorn app.main:app --port 8199`): per curl ein Fixposten (monatlich am 14., erfundener Betrag), Szenario mit „Prozent 50 ab <16. eines Monats>" und einer szenario-eigenen wiederkehrenden Zahlung mit Kategorie; `POST /api/scenarios/{id}/project` → Tiefpunkt/Serie gegen eigene Handrechnung; alle 422/404-Fälle aus Step 2 per curl nachstellen (deutsche Texte in `detail`); `DELETE` des Szenarios hinterlässt 0 Zeilen in `scenario_recurring_items`; danach aufräumen.
|
||||
|
||||
---
|
||||
|
||||
@@ -893,7 +893,7 @@ git commit -m "feat: API für Szenario-Zeitraum, Neuer Betrag und szenario-eigen
|
||||
- Consumes: Endpunkte aus Task 3 (`/modifiers` mit `start_date`, `/planned` mit `category_id`, `/recurring`); `ScenarioRecurringItem` (Task 1).
|
||||
- Produces: `window.FBSzenario = {zielTyp(select), aenderungArt(select), zahlungArt(select), init(root)}`; Template-Kontext `row.recurring_items`, `rhythms`; `DE_LABELS["absolute"] = "Kürzung (€)"`, `DE_LABELS["set"] = "Neuer Betrag"`.
|
||||
|
||||
- [ ] **Step 1: Failing Tests (Python)** — `finance/tests/test_formats.py::test_de_label_bekannte_und_unbekannte_werte`: Zeile `absolute` → `assert de_label("absolute") == "Kürzung (€)"`, neue Zeile `assert de_label("set") == "Neuer Betrag"`, Schlüsselmenge um `"set"` ergänzen.
|
||||
- [x] **Step 1: Failing Tests (Python)** — `finance/tests/test_formats.py::test_de_label_bekannte_und_unbekannte_werte`: Zeile `absolute` → `assert de_label("absolute") == "Kürzung (€)"`, neue Zeile `assert de_label("set") == "Neuer Betrag"`, Schlüsselmenge um `"set"` ergänzen.
|
||||
|
||||
In `finance/tests/test_gui.py`: den bestehenden Import aus `app.models.tables` um `Category` und `ScenarioRecurringItem` ergänzen. In `test_szenario_eintraege_tabelle` die letzte Zeile ersetzen durch `assert "FBSzenario.aenderungArt" in r and "onEntryArtChange" not in r`. In `test_neuer_eintrag_formular_struktur` `assert 'class="entry-form"' in r` ersetzen durch `assert 'class="entry-form aenderung-form"' in r and 'class="entry-form zahlung-form"' in r` (beide Formulare tragen jetzt zwei Klassen). In `test_statische_dateien_mit_versionskennung` die Dateiliste um `"szenarien.js"` ergänzen. Neue Tests anhängen:
|
||||
|
||||
@@ -949,7 +949,7 @@ def test_szenario_eintraege_zeitraum_und_wiederkehrend(client, db):
|
||||
assert 'id="sri-row-' in r and 'id="sri-edit-' in r
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Failing Tests (JS)** — `finance/tests/js/szenarien.dom.test.js`:
|
||||
- [x] **Step 2: Failing Tests (JS)** — `finance/tests/js/szenarien.dom.test.js`:
|
||||
|
||||
```js
|
||||
'use strict';
|
||||
@@ -1070,7 +1070,7 @@ test('Zahlung: Einmal → Wiederkehrend → Einmal', { skip: !JSDOM }, () => {
|
||||
});
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Tests laufen lassen** — `.venv/bin/python -m pytest -q tests/test_gui.py tests/test_formats.py` → FAIL. JS per Podman (Host hat kein node):
|
||||
- [x] **Step 3: Tests laufen lassen** — `.venv/bin/python -m pytest -q tests/test_gui.py tests/test_formats.py` → FAIL. JS per Podman (Host hat kein node):
|
||||
|
||||
```bash
|
||||
podman run --rm -v "$PWD":/w:Z -w /w docker.io/library/node:22-alpine \
|
||||
@@ -1079,9 +1079,9 @@ podman run --rm -v "$PWD":/w:Z -w /w docker.io/library/node:22-alpine \
|
||||
|
||||
Erwartet: `szenarien.dom.test.js` FAIL (Datei `szenarien.js` fehlt).
|
||||
|
||||
- [ ] **Step 4: `formats.py`** — in `DE_LABELS` `"absolute": "Kürzung (€)"` und nach `"absolute"` neu `"set": "Neuer Betrag"`.
|
||||
- [x] **Step 4: `formats.py`** — in `DE_LABELS` `"absolute": "Kürzung (€)"` und nach `"absolute"` neu `"set": "Neuer Betrag"`.
|
||||
|
||||
- [ ] **Step 5: `static/szenarien.js`**
|
||||
- [x] **Step 5: `static/szenarien.js`**
|
||||
|
||||
```js
|
||||
/* Szenarien-Seite (Ausbaustufe 14): Umschaltlogik der Formulare
|
||||
@@ -1158,7 +1158,7 @@ Erwartet: `szenarien.dom.test.js` FAIL (Datei `szenarien.js` fehlt).
|
||||
})();
|
||||
```
|
||||
|
||||
- [ ] **Step 6: `base.html`** — nach der `doppelzaehlung.js`-Zeile:
|
||||
- [x] **Step 6: `base.html`** — nach der `doppelzaehlung.js`-Zeile:
|
||||
|
||||
```html
|
||||
<script src="/static/szenarien.js?v={{ app_version }}"></script>
|
||||
@@ -1170,7 +1170,7 @@ und nach `if (window.FBDoppel) { window.FBDoppel.init(document); }`:
|
||||
if (window.FBSzenario) { window.FBSzenario.init(document); }
|
||||
```
|
||||
|
||||
- [ ] **Step 7: `gui.py`** — Import `ScenarioRecurringItem` ergänzen. In `_scenario_rows` nach `planned_items`:
|
||||
- [x] **Step 7: `gui.py`** — Import `ScenarioRecurringItem` ergänzen. In `_scenario_rows` nach `planned_items`:
|
||||
|
||||
```python
|
||||
recurring_items = session.execute(
|
||||
@@ -1181,7 +1181,7 @@ und nach `if (window.FBDoppel) { window.FBDoppel.init(document); }`:
|
||||
|
||||
und `"recurring_items": recurring_items,` ins Row-Dict. In `szenarien_page`: `"modifier_kinds": ["percent", "absolute", "set", "remove", "ende"],` und neu `"rhythms": ["monthly", "quarterly", "yearly"],`.
|
||||
|
||||
- [ ] **Step 8: `szenarien.html`** — direkt nach `{% block content %}` das Makro:
|
||||
- [x] **Step 8: `szenarien.html`** — direkt nach `{% block content %}` das Makro:
|
||||
|
||||
```jinja
|
||||
{% macro zeitraum(von, bis) -%}
|
||||
@@ -1412,9 +1412,9 @@ Den gesamten Bereich von `<h3>Einträge</h3>` bis vor `<hr>` ersetzen durch:
|
||||
|
||||
Im `<script>`-Block am Ende von `szenarien.html` die Funktionen `onModTargetTypeChange` und `onEntryArtChange` löschen (ersetzt durch `FBSzenario`); `toggleScenarioLoan` bleibt.
|
||||
|
||||
- [ ] **Step 9: Tests laufen lassen** — `.venv/bin/python -m pytest -q` → alle grün; `grep -rn "onEntryArtChange\|onModTargetTypeChange" finance/app` → keine Treffer. JS-Kommando aus Step 3 → alle grün, DOM-Tests nicht geskippt.
|
||||
- [x] **Step 9: Tests laufen lassen** — `.venv/bin/python -m pytest -q` → alle grün; `grep -rn "onEntryArtChange\|onModTargetTypeChange" finance/app` → keine Treffer. JS-Kommando aus Step 3 → alle grün, DOM-Tests nicht geskippt.
|
||||
|
||||
- [ ] **Step 10: Commit**
|
||||
- [x] **Step 10: Commit**
|
||||
|
||||
```bash
|
||||
git add finance/app/static/szenarien.js finance/app/templates/szenarien.html finance/app/templates/base.html \
|
||||
@@ -1423,7 +1423,7 @@ git add finance/app/static/szenarien.js finance/app/templates/szenarien.html fin
|
||||
git commit -m "feat: Szenarien-Seite mit zwei Formularen, Zeitraum-Spalte und wiederkehrenden Szenario-Zahlungen (A14-T4)"
|
||||
```
|
||||
|
||||
- [ ] **Step 11: Fable-Gate** — Browser-Test gegen lokalen uvicorn (Wegwerf-SQLite wie Task 3, `FB_GUI_PASSWORD_HASH` per `app.auth.hash_password`, synthetische Daten: 1 Kategorie, 1 Fixposten, 1 Szenario) per Playwright-Container (`podman run --rm --network host mcr.microsoft.com/playwright/python:v1.47.0-jammy …`): Login; im Formular „Neue Änderung" jede Art durchschalten und gesperrte Felder prüfen; „Neuer Betrag ab <Datum>" für den Posten anlegen → Zeile mit „ab TT.MM.JJJJ"; „Gültig ab" > „Gültig bis" → Fehlermeldung, Formular bleibt; „Neue Zahlung" → Wiederkehrend anlegen (mit Kategorie) → Zeile „Wiederkehrend (…)"; zurück auf Einmalzahlung → anlegen landet als Einmalzahlung; Bearbeiten/Speichern einer wiederkehrenden Zahlung und eines Eintrags (Art wechseln auf Ende → „Gültig ab" gesperrt); Löschen; „Durchrechnen" → 200; Screenshots bei 1200 px und 390 px ansehen; danach aufräumen.
|
||||
- [x] **Step 11: Fable-Gate** — Browser-Test gegen lokalen uvicorn (Wegwerf-SQLite wie Task 3, `FB_GUI_PASSWORD_HASH` per `app.auth.hash_password`, synthetische Daten: 1 Kategorie, 1 Fixposten, 1 Szenario) per Playwright-Container (`podman run --rm --network host mcr.microsoft.com/playwright/python:v1.47.0-jammy …`): Login; im Formular „Neue Änderung" jede Art durchschalten und gesperrte Felder prüfen; „Neuer Betrag ab <Datum>" für den Posten anlegen → Zeile mit „ab TT.MM.JJJJ"; „Gültig ab" > „Gültig bis" → Fehlermeldung, Formular bleibt; „Neue Zahlung" → Wiederkehrend anlegen (mit Kategorie) → Zeile „Wiederkehrend (…)"; zurück auf Einmalzahlung → anlegen landet als Einmalzahlung; Bearbeiten/Speichern einer wiederkehrenden Zahlung und eines Eintrags (Art wechseln auf Ende → „Gültig ab" gesperrt); Löschen; „Durchrechnen" → 200; Screenshots bei 1200 px und 390 px ansehen; danach aufräumen.
|
||||
|
||||
---
|
||||
|
||||
@@ -1436,7 +1436,7 @@ git commit -m "feat: Szenarien-Seite mit zwei Formularen, Zeitraum-Spalte und wi
|
||||
**Interfaces:**
|
||||
- Produces: `app.engine.loans.schedule_for(loan) -> list[Installment]` für jedes Objekt mit `principal`, `annual_rate_pct`, `term_months`, `payout_date`, `repayment_type` (ORM-`Loan` oder gleichartig; Engine importiert keine Modelle).
|
||||
|
||||
- [ ] **Step 1: Failing Tests** — an `finance/tests/test_loans.py` anhängen:
|
||||
- [x] **Step 1: Failing Tests** — an `finance/tests/test_loans.py` anhängen:
|
||||
|
||||
```python
|
||||
def test_schedule_for_entspricht_loan_schedule():
|
||||
@@ -1485,9 +1485,9 @@ def test_kredit_laufzeit_grenzen_und_patch(client):
|
||||
|
||||
(`H` ist in `test_planning_api.py` definiert; `import pytest` oben ergänzen.)
|
||||
|
||||
- [ ] **Step 2: Tests laufen lassen** — `.venv/bin/python -m pytest -q tests/test_loans.py tests/test_planning_api.py` → FAIL.
|
||||
- [x] **Step 2: Tests laufen lassen** — `.venv/bin/python -m pytest -q tests/test_loans.py tests/test_planning_api.py` → FAIL.
|
||||
|
||||
- [ ] **Step 3: `engine/loans.py`** — am Dateiende:
|
||||
- [x] **Step 3: `engine/loans.py`** — am Dateiende:
|
||||
|
||||
```python
|
||||
def schedule_for(loan) -> list[Installment]:
|
||||
@@ -1498,17 +1498,17 @@ def schedule_for(loan) -> list[Installment]:
|
||||
loan.term_months, loan.payout_date, loan.repayment_type)
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Aufrufstellen umstellen** (Import jeweils `from app.engine.loans import schedule_for`; `loan_schedule`-Import entfernen, wo er danach unbenutzt ist):
|
||||
- [x] **Step 4: Aufrufstellen umstellen** (Import jeweils `from app.engine.loans import schedule_for`; `loan_schedule`-Import entfernen, wo er danach unbenutzt ist):
|
||||
- `routers/planning.py::get_loan_schedule`: `plan = schedule_for(loan)`
|
||||
- `services/breakdown.py::compute_breakdown`: `loans = [PlainLoan(l.id, l.name, schedule_for(l)) for l in inputs.loans]`
|
||||
- `services/projection_service.py::run_projection`: `schedules = [schedule_for(l) for l in inputs.loans]`
|
||||
- `services/planning_chart.py::planning_monthly_totals`: `for inst in schedule_for(loan):`
|
||||
|
||||
- [ ] **Step 5: Laufzeit-Validierung** — `routers/planning.py`: in `LoanIn` `term_months: int = Field(ge=1, le=1200)`; in `LoanPatch` `term_months: int | None = Field(None, ge=1, le=1200)`.
|
||||
- [x] **Step 5: Laufzeit-Validierung** — `routers/planning.py`: in `LoanIn` `term_months: int = Field(ge=1, le=1200)`; in `LoanPatch` `term_months: int | None = Field(None, ge=1, le=1200)`.
|
||||
|
||||
- [ ] **Step 6: Tests laufen lassen** — `.venv/bin/python -m pytest -q` → alle grün.
|
||||
- [x] **Step 6: Tests laufen lassen** — `.venv/bin/python -m pytest -q` → alle grün.
|
||||
|
||||
- [ ] **Step 7: Commit**
|
||||
- [x] **Step 7: Commit**
|
||||
|
||||
```bash
|
||||
git add finance/app/engine/loans.py finance/app/routers/planning.py finance/app/services/breakdown.py \
|
||||
@@ -1517,7 +1517,7 @@ git add finance/app/engine/loans.py finance/app/routers/planning.py finance/app/
|
||||
git commit -m "refactor: Tilgungsplan-Helfer schedule_for, Kredit-Laufzeit 1–1200 Monate (A14-T5)"
|
||||
```
|
||||
|
||||
- [ ] **Step 8: Fable-Gate** — Verhaltensgleichheit: Projektion, Aufteilung (ein Monat mit Kreditrate) und `planning_monthly_totals` für ein synthetisches Setup mit Annuitäten- und endfälligem Kredit vor/nach dem Commit identisch (zwei Checkouts in einem Wegwerf-Verzeichnis); `grep -rn "loan_schedule" finance/app` zeigt nur noch `engine/loans.py` (keine unbenutzten Importe in `routers/planning.py`, `services/*`); 422-Antworten der Laufzeit-Grenzen haben deutschen Kontext in der GUI (Kredit-Formular auf `/planung` zeigt Fehler per Alert, Formular bleibt).
|
||||
- [x] **Step 8: Fable-Gate** — Verhaltensgleichheit: Projektion, Aufteilung (ein Monat mit Kreditrate) und `planning_monthly_totals` für ein synthetisches Setup mit Annuitäten- und endfälligem Kredit vor/nach dem Commit identisch (zwei Checkouts in einem Wegwerf-Verzeichnis); `grep -rn "loan_schedule" finance/app` zeigt nur noch `engine/loans.py` (keine unbenutzten Importe in `routers/planning.py`, `services/*`); 422-Antworten der Laufzeit-Grenzen haben deutschen Kontext in der GUI (Kredit-Formular auf `/planung` zeigt Fehler per Alert, Formular bleibt).
|
||||
|
||||
---
|
||||
|
||||
@@ -1527,7 +1527,7 @@ git commit -m "refactor: Tilgungsplan-Helfer schedule_for, Kredit-Laufzeit 1–1
|
||||
- Modify: `finance/app/static/style.css`, `finance/app/templates/_torte_macros.html`
|
||||
- Test: `finance/tests/test_aufteilung_gui.py`
|
||||
|
||||
- [ ] **Step 1: Failing Tests** — an `finance/tests/test_aufteilung_gui.py` anhängen:
|
||||
- [x] **Step 1: Failing Tests** — an `finance/tests/test_aufteilung_gui.py` anhängen:
|
||||
|
||||
```python
|
||||
from pathlib import Path
|
||||
@@ -1565,9 +1565,9 @@ def test_gewaehltes_segment_zuletzt_gezeichnet():
|
||||
|
||||
(`re` und `Decimal` sind in `test_aufteilung_gui.py` bereits importiert; `Path`/`SimpleNamespace` oben bei den Imports einsortieren.)
|
||||
|
||||
- [ ] **Step 2: Tests laufen lassen** — `.venv/bin/python -m pytest -q tests/test_aufteilung_gui.py` → 3 FAIL (figcaption-Teil besteht bereits).
|
||||
- [x] **Step 2: Tests laufen lassen** — `.venv/bin/python -m pytest -q tests/test_aufteilung_gui.py` → 3 FAIL (figcaption-Teil besteht bereits).
|
||||
|
||||
- [ ] **Step 3: `style.css`** — im `nav { … }`-Block nach `gap: 1rem;`:
|
||||
- [x] **Step 3: `style.css`** — im `nav { … }`-Block nach `gap: 1rem;`:
|
||||
|
||||
```css
|
||||
flex-wrap: wrap;
|
||||
@@ -1582,7 +1582,7 @@ Nach `.legende li.gewaehlt { font-weight: bold; }`:
|
||||
|
||||
`.torte figcaption { font-weight: bold; … }` **nicht** ändern.
|
||||
|
||||
- [ ] **Step 4: `_torte_macros.html`** — im Makro `torte` die SVG-Schleife ersetzen (Legende unverändert):
|
||||
- [x] **Step 4: `_torte_macros.html`** — im Makro `torte` die SVG-Schleife ersetzen (Legende unverändert):
|
||||
|
||||
```jinja
|
||||
{# Gewähltes Segment zuletzt zeichnen, damit sein Umriss nicht von
|
||||
@@ -1590,16 +1590,16 @@ Nach `.legende li.gewaehlt { font-weight: bold; }`:
|
||||
{% for seg in (segmente|rejectattr('gewaehlt')|list) + (segmente|selectattr('gewaehlt')|list) %}{% set s = seg.s %}
|
||||
```
|
||||
|
||||
- [ ] **Step 5: Tests laufen lassen** — `.venv/bin/python -m pytest -q` → alle grün.
|
||||
- [x] **Step 5: Tests laufen lassen** — `.venv/bin/python -m pytest -q` → alle grün.
|
||||
|
||||
- [ ] **Step 6: Commit**
|
||||
- [x] **Step 6: Commit**
|
||||
|
||||
```bash
|
||||
git add finance/app/static/style.css finance/app/templates/_torte_macros.html finance/tests/test_aufteilung_gui.py
|
||||
git commit -m "fix: Navigation bricht auf schmalen Bildschirmen um, gewähltes Tortensegment oben, Legendenzusatz nicht fett (A14-T6)"
|
||||
```
|
||||
|
||||
- [ ] **Step 7: Fable-Gate** — Playwright gegen lokalen uvicorn (Wegwerf-SQLite, synthetische Daten mit ≥ 4 Kategorien): bei 390 px auf `/`, `/buchungen`, `/planung`, `/szenarien`, `/aufteilung`, `/admin`, `/hilfe` gilt `document.documentElement.scrollWidth <= 390` und alle Nav-Links sind sichtbar; in `/aufteilung` ein mittleres Segment klicken → Screenshot vergrößert ansehen: dunkler Umriss vollständig sichtbar; Legendenzeile der „Übrige"-Gruppe gewählt → Zusatz in Klammern nicht fett, Bildunterschrift fett.
|
||||
- [x] **Step 7: Fable-Gate** — Playwright gegen lokalen uvicorn (Wegwerf-SQLite, synthetische Daten mit ≥ 4 Kategorien): bei 390 px auf `/`, `/buchungen`, `/planung`, `/szenarien`, `/aufteilung`, `/admin`, `/hilfe` gilt `document.documentElement.scrollWidth <= 390` und alle Nav-Links sind sichtbar; in `/aufteilung` ein mittleres Segment klicken → Screenshot vergrößert ansehen: dunkler Umriss vollständig sichtbar; Legendenzeile der „Übrige"-Gruppe gewählt → Zusatz in Klammern nicht fett, Bildunterschrift fett.
|
||||
|
||||
---
|
||||
|
||||
@@ -1609,7 +1609,7 @@ git commit -m "fix: Navigation bricht auf schmalen Bildschirmen um, gewähltes T
|
||||
- Modify: `finance/app/templates/hilfe.html`, `docs/ARCHITEKTUR.md`, `finance/VERSION`
|
||||
- Modify (anderes Repo): `/home/wlfb/fb/.claude/skills/finanz-api/SKILL.md`, `/home/wlfb/fb/.claude/skills/finanzberatung/SKILL.md`
|
||||
|
||||
- [ ] **Step 1: `hilfe.html`** — im `<li>` „Szenarien":
|
||||
- [x] **Step 1: `hilfe.html`** — im `<li>` „Szenarien":
|
||||
- „Modifikatoren (Kategorie oder Posten prozentual/absolut kürzen oder ganz streichen)" ersetzen durch „Einträgen (Kategorie oder Posten prozentual oder um einen Betrag kürzen, auf einen neuen Betrag setzen oder ganz streichen – auch nur für einen Zeitraum)".
|
||||
- Den Text von „Varianten wie „Best Case“ pflegst du …" bis „… direkt änderbar." ersetzen durch:
|
||||
|
||||
@@ -1632,40 +1632,40 @@ git commit -m "fix: Navigation bricht auf schmalen Bildschirmen um, gewähltes T
|
||||
„Bearbeiten“ direkt änderbar.
|
||||
```
|
||||
|
||||
- [ ] **Step 2: `docs/ARCHITEKTUR.md`** —
|
||||
- [x] **Step 2: `docs/ARCHITEKTUR.md`** —
|
||||
- Komponenten-Diagramm: `M_tables`-Label um `ScenarioRecurringItem` ergänzen; `M_alembic`-Label um `e7f1a9c3b214_szenario_zeitraum`; `R_scenarios`-Label um `add_scenario_recurring`; `E_scenario`-Label zu `build_cashflows, modified_amount<br/>(Stichtag on, gilt_am)`; `E_loans`-Label um `schedule_for`.
|
||||
- Abschnitt „Szenario-Engine ist rein": `load_scenario_inputs` lädt zusätzlich `ScenarioRecurringItem` (als `PlainRecurring` mit `id=None`) — Liste der geladenen Modelle ergänzen.
|
||||
- Neuer Konzeptabsatz **„Szenario-Einträge mit Zeitraum (Ausbaustufe 14)."**: `ScenarioModifier.start_date`/`end_date` = „Gültig ab/bis" (inklusiv; bei `ende` nur `end_date`), Art `set` („Neuer Betrag", nur Posten), `engine.scenario.modified_amount(..., on=)` mit Stichtag je Fälligkeit (`build_cashflows`) bzw. `recurrence.due_in_month` (`breakdown_rows`), Anlage-Reihenfolge; `scenario_recurring_items` + `/api/scenarios/{id}/recurring`, zählt unabhängig von `include_recurring`, von Posten-Einträgen nie getroffen (`id=None`); `ScenarioPlannedItem.category_id`; Szenarien-Seite mit `static/szenarien.js` (`FBSzenario`).
|
||||
- Nur Namen verwenden, die im Code existieren.
|
||||
|
||||
- [ ] **Step 3: Version** — `finance/VERSION` → `0.14.0`; `.venv/bin/python -m pytest -q` grün.
|
||||
- [x] **Step 3: Version** — `finance/VERSION` → `0.14.0`; `.venv/bin/python -m pytest -q` grün.
|
||||
|
||||
- [ ] **Step 4: Commit (Repo `~/bin`)**
|
||||
- [x] **Step 4: Commit (Repo `~/bin`)**
|
||||
|
||||
```bash
|
||||
git add finance/app/templates/hilfe.html docs/ARCHITEKTUR.md finance/VERSION
|
||||
git commit -m "chore: Release v0.14.0 (Szenario-Zeitraum, Neuer Betrag, Szenario-Zahlungen)"
|
||||
```
|
||||
|
||||
- [ ] **Step 5: Beratungsumgebung** —
|
||||
- [x] **Step 5: Beratungsumgebung** —
|
||||
- `/home/wlfb/fb/.claude/skills/finanz-api/SKILL.md`, Abschnitt „Szenarien (`/api/scenarios`)": den Satz ab „Modifikator hinzufügen:" ersetzen durch: „Eintrag (Modifikator) hinzufügen: `POST /{id}/modifiers` mit `target_type` (`category`|`recurring`), `target_id`, `kind` (`percent`|`absolute` = Kürzung in €|`set` = neuer Betrag mit Vorzeichen, nur `recurring`|`remove`|`ende`), `value`, optional `start_date`/`end_date` (Gültig ab/bis, inklusiv; bei `ende` ist `end_date` Pflicht und das Enddatum); ändern per `PATCH` (voller Body), löschen über `DELETE /{id}/modifiers/{mod_id}`. Szenario-eigene Einmalzahlungen: `/{id}/planned` (`name`, `amount`, `due`, optional `category_id`). Szenario-eigene wiederkehrende Zahlungen: `/{id}/recurring` (`name`, `amount`, `rhythm`, `due_day`, optional `start_date`, `end_date`, `category_id`) mit `GET`/`POST`/`PATCH`/`DELETE`; zählen immer, unabhängig von `include_recurring`."
|
||||
- `/home/wlfb/fb/.claude/skills/finanzberatung/SKILL.md`, beim Punkt „Kürzungspotenzial": ergänzen „Änderungen ab einem Stichtag (z. B. Rate halbiert ab Oktober) als Eintrag mit `start_date` abbilden, nicht als Ende + neuer Posten; neue laufende Zahlungen nur für eine Variante über `/api/scenarios/{id}/recurring`."
|
||||
- Commit im Repo `/home/wlfb/fb`: `git -C /home/wlfb/fb add .claude/skills/finanz-api/SKILL.md .claude/skills/finanzberatung/SKILL.md && git -C /home/wlfb/fb commit -m "docs: Szenario-Zeitraum, Neuer Betrag, Szenario-Zahlungen (Tool v0.14.0)"` (mit Co-Authored-By-Zeile). Nicht pushen.
|
||||
|
||||
- [ ] **Step 6: Fable-Gate** — Faktencheck aller Doku-Aussagen gegen Code (Feldnamen, Arten, Fehlertexte, Endpunkte, Stichtag-Regel, Funktionsnamen), Mermaid valide (z. B. per `mermaid-cli`-Container rendern), Hilfe-Text stimmt mit dem Verhalten überein; keine echten Namen/Beträge in Doku.
|
||||
- [x] **Step 6: Fable-Gate** — Faktencheck aller Doku-Aussagen gegen Code (Feldnamen, Arten, Fehlertexte, Endpunkte, Stichtag-Regel, Funktionsnamen), Mermaid valide (z. B. per `mermaid-cli`-Container rendern), Hilfe-Text stimmt mit dem Verhalten überein; keine echten Namen/Beträge in Doku.
|
||||
|
||||
---
|
||||
|
||||
### Task 8: Backup, Migration, Redeploy, Live-Smoke (nach Whole-Branch-Review)
|
||||
|
||||
- [ ] **Step 1: Backup** — `./backup_finance_pod.sh` (vor Redeploy).
|
||||
- [ ] **Step 2: Redeploy** — `alembic current` vorher notieren (`podman exec finance-api_ctr alembic current` → `d5e8b3c7a410`); `./create_pod_finance.sh` (die Migration läuft automatisch: `finance/entrypoint.sh` führt beim Container-Start `alembic upgrade head` aus); `GET /api/version` (mit Bearer) → `0.14.0`; `podman exec finance-api_ctr alembic current` → `e7f1a9c3b214 (head)`.
|
||||
- [ ] **Step 3: Live-Smoke** (curl mit Session-Cookie gegen `127.0.0.1:8096`; nur Statuscodes, Zählwerte, Booleans — DATENSCHUTZ):
|
||||
- [x] **Step 1: Backup** — `./backup_finance_pod.sh` (vor Redeploy).
|
||||
- [x] **Step 2: Redeploy** — `alembic current` vorher notieren (`podman exec finance-api_ctr alembic current` → `d5e8b3c7a410`); `./create_pod_finance.sh` (die Migration läuft automatisch: `finance/entrypoint.sh` führt beim Container-Start `alembic upgrade head` aus); `GET /api/version` (mit Bearer) → `0.14.0`; `podman exec finance-api_ctr alembic current` → `e7f1a9c3b214 (head)`.
|
||||
- [x] **Step 3: Live-Smoke** (curl mit Session-Cookie gegen `127.0.0.1:8096`; nur Statuscodes, Zählwerte, Booleans — DATENSCHUTZ):
|
||||
- Seiten `/`, `/buchungen`, `/salden`, `/import`, `/planung`, `/szenarien`, `/aufteilung`, `/admin`, `/hilfe` → 200.
|
||||
- `/szenarien` enthält „Neue Änderung an Bestehendem", „Neue Zahlung nur in diesem Szenario", `szenarien.js?v=0.14.0` (Booleans).
|
||||
- `GET /api/scenarios/{id}/recurring` für jedes Szenario → 200, leere Liste.
|
||||
- Bestandszahlen (Szenarien, Einträge je Art, Einmalzahlungen, Buchungen, Kategorien, Fixposten, Kredite) vor = nach; vorhandene `ende`-Einträge behalten ihr Datum (Anzahl mit `end_date` vor = nach); `start_date` überall NULL.
|
||||
- Projektionen aller durchgerechneten Szenarien: Tiefpunkt-Datum und -Betrag vor = nach (per SQL verglichen, nur Boolean ausgeben) — die Rechenänderung darf Bestandsszenarien ohne Zeitraum nicht verändern.
|
||||
- Das Live-Szenario des Nutzers **nicht** ändern.
|
||||
- [ ] **Step 4: Live-Klicktest** — Playwright-Container gegen `https://fbwl.creature-go.com/` (Secure-Cookie aktiv; Passwort im Skript aus `~/.local/share/finance_pod/.env` lesen, nie ausgeben): `/szenarien` öffnen, in „Neue Änderung" Art „Ende" wählen ⇒ Boolean „Gültig ab gesperrt"; in „Neue Zahlung" „Wiederkehrend" wählen ⇒ Boolean „Rhythmus aktiv, Fällig am gesperrt". Nichts absenden.
|
||||
- [ ] **Step 5: Fable-Release-Gate** (read-only Live-Verifikation) + Ledger-Eintrag „AUSBAUSTUFE 14 ABGESCHLOSSEN" in `.superpowers/sdd/progress.md` (inkl. A15-Kandidaten aus den Reviews); Plan-Haken; Commit `docs: A14 Plan-Haken`; beide Repos pushen erst nach Rückfrage beim Nutzer.
|
||||
- [x] **Step 4: Live-Klicktest** — Playwright-Container gegen `https://fbwl.creature-go.com/` (Secure-Cookie aktiv; Passwort im Skript aus `~/.local/share/finance_pod/.env` lesen, nie ausgeben): `/szenarien` öffnen, in „Neue Änderung" Art „Ende" wählen ⇒ Boolean „Gültig ab gesperrt"; in „Neue Zahlung" „Wiederkehrend" wählen ⇒ Boolean „Rhythmus aktiv, Fällig am gesperrt". Nichts absenden.
|
||||
- [x] **Step 5: Fable-Release-Gate** (read-only Live-Verifikation) + Ledger-Eintrag „AUSBAUSTUFE 14 ABGESCHLOSSEN" in `.superpowers/sdd/progress.md` (inkl. A15-Kandidaten aus den Reviews); Plan-Haken; Commit `docs: A14 Plan-Haken`; beide Repos pushen erst nach Rückfrage beim Nutzer.
|
||||
|
||||
Reference in New Issue
Block a user