diff --git a/docs/superpowers/plans/2026-09-28-ausbaustufe-12.md b/docs/superpowers/plans/2026-09-28-ausbaustufe-12.md index d61fd3d..f6b68e2 100644 --- a/docs/superpowers/plans/2026-09-28-ausbaustufe-12.md +++ b/docs/superpowers/plans/2026-09-28-ausbaustufe-12.md @@ -47,7 +47,7 @@ **Interfaces:** - Produces: `app.reserved.SYSTEM_PREFIX: str`, `app.reserved.RESERVED_MESSAGE: str`, `app.reserved.is_reserved_name(name: str) -> bool`, `app.reserved.check_reserved_name(name: str | None) -> None` (wirft `HTTPException(422, RESERVED_MESSAGE)`). -- [ ] **Step 1: Failing Tests** — `finance/tests/test_reserved.py`: +- [x] **Step 1: Failing Tests** — `finance/tests/test_reserved.py`: ```python import pytest @@ -102,12 +102,12 @@ def test_szenario_praefix_reserviert(client, name): assert "reserviert" in r.json()["detail"] ``` -- [ ] **Step 2: Tests laufen lassen — müssen fehlschlagen** +- [x] **Step 2: Tests laufen lassen — müssen fehlschlagen** Run: `cd finance && .venv/bin/python -m pytest -q tests/test_reserved.py` Expected: FAIL (`ModuleNotFoundError: app.reserved`). -- [ ] **Step 3: `finance/app/reserved.py`** +- [x] **Step 3: `finance/app/reserved.py`** ```python """Reservierte Namen (Ausbaustufe 12). @@ -136,7 +136,7 @@ def check_reserved_name(name: str | None) -> None: raise HTTPException(422, RESERVED_MESSAGE) ``` -- [ ] **Step 4: Konstanten ableiten** +- [x] **Step 4: Konstanten ableiten** `finance/app/models/views.py`: Import `from app.reserved import SYSTEM_PREFIX` und ```python @@ -146,20 +146,20 @@ RESERVED_SCENARIO_NAME = f"{SYSTEM_PREFIX}Ist" `finance/app/services/planning_chart.py`: Import `from app.reserved import SYSTEM_PREFIX`, `KREDITRATEN = f"{SYSTEM_PREFIX}Kreditraten"`; im Modul-Docstring „Kreditraten" → „System-Kreditraten". -- [ ] **Step 5: Router** +- [x] **Step 5: Router** `finance/app/routers/scenarios.py`: Import `RESERVED_SCENARIO_NAME` und die Funktion `_check_reserved_name` entfernen; `from app.reserved import check_reserved_name`; die beiden Aufrufe `_check_reserved_name(...)` → `check_reserved_name(...)` (gleiche Stellen: erste Zeile `create_scenario`, nach `model_dump` in `patch_scenario`). `finance/app/routers/categories.py`: `from app.reserved import check_reserved_name`; als erste Zeile in `create_category` und `patch_category`: `check_reserved_name(data.name)`. -- [ ] **Step 6: Tests grün + Gesamtsuite** +- [x] **Step 6: Tests grün + Gesamtsuite** Run: `cd finance && .venv/bin/python -m pytest -q` Expected: 260 + 17 = **277 passed, 3 skipped** (bestehende System-Ist-Tests in `test_crud_api.py` bleiben grün, ihre Meldung enthält „reserviert"). -- [ ] **Step 7: Fable-Gate** — Faktencheck: Präfix nur in `reserved.py` als Literal (`grep -rn '"System-' app/` → nur `reserved.py`), View-SQL rendert weiterhin `'System-Ist'`, alle Namens-Schreibwege für Kategorien/Szenarien geprüft, keine Bestandsdaten betroffen. +- [x] **Step 7: Fable-Gate** — Faktencheck: Präfix nur in `reserved.py` als Literal (`grep -rn '"System-' app/` → nur `reserved.py`), View-SQL rendert weiterhin `'System-Ist'`, alle Namens-Schreibwege für Kategorien/Szenarien geprüft, keine Bestandsdaten betroffen. -- [ ] **Step 8: Commit** +- [x] **Step 8: Commit** ```bash git add finance/app/reserved.py finance/app/models/views.py finance/app/services/planning_chart.py \ @@ -190,7 +190,7 @@ git commit -m "feat: Präfix System- für Tool-Serien reserviert, Kreditraten-Se - `all_conflicts(session) -> list[Konflikt]` (sortiert nach Posten-, dann Kreditname) - Abweichung ggü. Spec: `exclude_id` entfällt — die Prüfung ist typübergreifend (Posten gegen Kredite), ein Datensatz kann nicht mit sich selbst kollidieren. -- [ ] **Step 1: Failing Tests** — `finance/tests/test_double_count.py`: +- [x] **Step 1: Failing Tests** — `finance/tests/test_double_count.py`: ```python from datetime import date @@ -294,12 +294,12 @@ def test_conflicts_for_recurring_und_loan(db): assert [k.posten_name for k in conflicts_for_loan(db, kredit)] == ["Miete"] ``` -- [ ] **Step 2: Tests laufen lassen — müssen fehlschlagen** +- [x] **Step 2: Tests laufen lassen — müssen fehlschlagen** Run: `cd finance && .venv/bin/python -m pytest -q tests/test_double_count.py` Expected: FAIL (`ModuleNotFoundError`). -- [ ] **Step 3: `finance/app/services/double_count.py`** +- [x] **Step 3: `finance/app/services/double_count.py`** ```python """Doppelzählungs-Erkennung Kredit ↔ wiederkehrender Posten (Ausbaustufe 12). @@ -431,14 +431,14 @@ def all_conflicts(session: Session) -> list[Konflikt]: return out ``` -- [ ] **Step 4: Tests grün + Gesamtsuite** +- [x] **Step 4: Tests grün + Gesamtsuite** Run: `cd finance && .venv/bin/python -m pytest -q` Expected: 277 + 20 = **297 passed, 3 skipped** (1 + 5 + 3 + 1 + 1 + 6 + 1 + 1 + 1). -- [ ] **Step 5: Fable-Gate** — unabhängige Handrechnung: je ein Annuitätenkredit mit Zins (eigene Werte), endfällig mit Zins, Grenzfälle der Toleranz auf den Cent, Zeitraum-Grenzen an erster/letzter Rate (inkl. Monatsende-Clamp, z.B. Auszahlung 31.01.) gegen `find_conflict`; Decimal-Durchgängigkeit (kein float). +- [x] **Step 5: Fable-Gate** — unabhängige Handrechnung: je ein Annuitätenkredit mit Zins (eigene Werte), endfällig mit Zins, Grenzfälle der Toleranz auf den Cent, Zeitraum-Grenzen an erster/letzter Rate (inkl. Monatsende-Clamp, z.B. Auszahlung 31.01.) gegen `find_conflict`; Decimal-Durchgängigkeit (kein float). -- [ ] **Step 6: Commit** +- [x] **Step 6: Commit** ```bash git add finance/app/services/double_count.py finance/tests/test_double_count.py @@ -457,7 +457,7 @@ git commit -m "feat: Erkennung möglicher Doppelzählung Kredit/Fixposten (A12-T - Consumes (Task 2): `CODE`, `PostenDaten`, `KreditDaten`, `Konflikt`, `posten_aus`, `kredit_aus`, `conflicts_for_recurring`, `conflicts_for_loan`. - Produces: 409-Body `{"detail": {"code": "doppelzaehlung_kredit", "meldung": str, "konflikte": [{"posten", "posten_betrag", "kredit", "kredit_rate"}]}}` (Beträge als Strings, Punkt-Dezimal, 2 Nachkommastellen). Request-Feld `doppelzaehlung_bestaetigt` (Task 4 sendet es). -- [ ] **Step 1: Failing Tests** — `finance/tests/test_double_count_api.py`: +- [x] **Step 1: Failing Tests** — `finance/tests/test_double_count_api.py`: ```python from decimal import Decimal @@ -553,12 +553,12 @@ def test_vorschlag_uebernehmen_nutzt_denselben_endpunkt(client): assert r.status_code == 409 ``` -- [ ] **Step 2: Tests laufen lassen — müssen fehlschlagen** +- [x] **Step 2: Tests laufen lassen — müssen fehlschlagen** Run: `cd finance && .venv/bin/python -m pytest -q tests/test_double_count_api.py` Expected: FAIL (201 statt 409). -- [ ] **Step 3: Implementierung in `routers/planning.py`** +- [x] **Step 3: Implementierung in `routers/planning.py`** Imports: ```python @@ -648,14 +648,14 @@ nach der bestehenden Ende-vor-Start-Prüfung, vor `session.commit()`: Hinweis: Bei neu angelegten Datensätzen ist `posten_id`/`kredit_id` in der Konflikt-Antwort `None` — die Antwort enthält bewusst nur Namen und Beträge. -- [ ] **Step 4: Tests grün + Gesamtsuite** +- [x] **Step 4: Tests grün + Gesamtsuite** Run: `cd finance && .venv/bin/python -m pytest -q` Expected: 297 + 8 = **305 passed, 3 skipped**; alle bestehenden Planning-/Szenario-Tests grün (z.B. `test_loan_in_scenario_keeps_balance_positive`: Miete −600 vs. Rate 117,43 → kein Konflikt). -- [ ] **Step 5: Fable-Gate** — Live-Approximation: lokaler uvicorn gegen Wegwerf-SQLite oder Wegwerf-Postgres (`FB_DATABASE_URL`, `alembic upgrade head`), curl-Roundtrips aller vier Schreibwege mit/ohne Flag, reine Umbenennung, Konflikt-Paar-Logik bei PATCH, Antwortschemata ohne Flag-Feld, 409-Body exakt; danach Aufräumen. +- [x] **Step 5: Fable-Gate** — Live-Approximation: lokaler uvicorn gegen Wegwerf-SQLite oder Wegwerf-Postgres (`FB_DATABASE_URL`, `alembic upgrade head`), curl-Roundtrips aller vier Schreibwege mit/ohne Flag, reine Umbenennung, Konflikt-Paar-Logik bei PATCH, Antwortschemata ohne Flag-Feld, 409-Body exakt; danach Aufräumen. -- [ ] **Step 6: Commit** +- [x] **Step 6: Commit** ```bash git add finance/app/routers/planning.py finance/tests/test_double_count_api.py @@ -679,7 +679,7 @@ git commit -m "feat: API meldet mögliche Doppelzählung mit 409, Speichern nur - Consumes: 409-Body aus Task 3; `all_conflicts` aus Task 2; `tests/test_sort_js.py` sammelt `tests/js/*.test.js` automatisch (keine Änderung nötig). - Produces (JS, `window.FBDoppel` bzw. `module.exports`): `parseDetail(xhr) -> detail|null`, `konfliktText(k) -> string`, `handle(evt) -> bool`, `bestaetigen(dlg)`, `abbrechen(dlg)`, `init(doc)`. -- [ ] **Step 1: Failing JS-Unit-Tests** — `finance/tests/js/doppelzaehlung.test.js`: +- [x] **Step 1: Failing JS-Unit-Tests** — `finance/tests/js/doppelzaehlung.test.js`: ```js 'use strict'; @@ -704,7 +704,7 @@ test('konfliktText deutsch formatiert', () => { }); ``` -- [ ] **Step 2: Failing DOM-Tests** — `finance/tests/js/doppelzaehlung.dom.test.js`: +- [x] **Step 2: Failing DOM-Tests** — `finance/tests/js/doppelzaehlung.dom.test.js`: ```js 'use strict'; @@ -784,7 +784,7 @@ test('Flag nach Request entfernt', { skip: !JSDOM }, () => { }); ``` -- [ ] **Step 3: Failing Python-Tests** — an `finance/tests/test_gui.py` anhängen: +- [x] **Step 3: Failing Python-Tests** — an `finance/tests/test_gui.py` anhängen: ```python def test_base_hat_doppelzaehlungs_dialog(client): @@ -832,12 +832,12 @@ def test_vorschlag_formular_nutzt_json_form_post(client): (`Loan`, `RecurringItem`, `Decimal`, `date` sind in `test_gui.py` bereits importiert — sonst ergänzen.) -- [ ] **Step 4: Tests laufen lassen — müssen fehlschlagen** +- [x] **Step 4: Tests laufen lassen — müssen fehlschlagen** Run: `cd finance && .venv/bin/python -m pytest -q tests/test_gui.py -k "doppelzaehlung or vorschlag_formular"` Expected: FAIL (Dialog/Hinweis fehlen; `test_vorschlag_formular_nutzt_json_form_post` darf bereits grün sein). -- [ ] **Step 5: `finance/app/static/doppelzaehlung.js`** +- [x] **Step 5: `finance/app/static/doppelzaehlung.js`** ```js /* Warnfenster bei möglicher Doppelzählung Kredit ↔ Fixposten (Ausbaustufe 12). @@ -931,7 +931,7 @@ Expected: FAIL (Dialog/Hinweis fehlen; `test_vorschlag_formular_nutzt_json_form_ })(); ``` -- [ ] **Step 6: `base.html`** +- [x] **Step 6: `base.html`** Nach ``: ```html @@ -968,7 +968,7 @@ Am Ende des Inline-Scripts (nach `toggleEdit`): if (window.FBDoppel) { window.FBDoppel.init(document); } ``` -- [ ] **Step 7: Dauerhafter Hinweis** +- [x] **Step 7: Dauerhafter Hinweis** `routers/gui.py`: Import `from app.services.double_count import all_conflicts`; in `planung_page` vor dem `TemplateResponse`: ```python @@ -1002,19 +1002,19 @@ Kredit-Namenszelle (`