diff --git a/docs/ARCHITEKTUR.md b/docs/ARCHITEKTUR.md index e3b202b..4b94c4c 100644 --- a/docs/ARCHITEKTUR.md +++ b/docs/ARCHITEKTUR.md @@ -125,6 +125,7 @@ flowchart TD E_scenario --> E_recurrence E_breakdown --> E_scenario E_breakdown --> E_recurrence + E_breakdown --> E_loans P_registry --> P_vr P_registry --> P_hvb diff --git a/finance/app/routers/gui.py b/finance/app/routers/gui.py index 35be1e7..29aa0d3 100644 --- a/finance/app/routers/gui.py +++ b/finance/app/routers/gui.py @@ -426,8 +426,8 @@ def _aufteilung_context(session: Session, today: date, monat: str | None, if month is None: ctx["error"] = f"Ungültiger Monat „{monat}“ – bitte im Format JJJJ-MM wählen." return ctx - ctx["monat_wert"] = month.strftime("%Y-%m") - ctx["monat_anzeige"] = month.strftime("%m.%Y") + ctx["monat_wert"] = f"{month.year:04d}-{month.month:02d}" + ctx["monat_anzeige"] = f"{month.month:02d}.{month.year:04d}" scenario = None if szenario: valid_id = re.fullmatch(r"[0-9]{1,9}", szenario) # ASCII, kein Integer-Überlauf diff --git a/finance/app/templates/_torte_macros.html b/finance/app/templates/_torte_macros.html index 06ef6cb..1119315 100644 --- a/finance/app/templates/_torte_macros.html +++ b/finance/app/templates/_torte_macros.html @@ -14,8 +14,8 @@ class="seg {{ seg.s.color }}{% if seg.q %} klickbar{% endif %}{% if seg.gewaehlt
{{ titel }}
{% for seg in segmente %}{% set s = seg.s %} - {% if s.path %}{{ s.label }}: {{ s.value|eur }} € ({{ s.share|pct }}) - {% else %}{{ s.label }}: {{ s.value|eur }} € ({{ s.share|pct }}){% endif %} + {% if s.path %}{{ s.label }}{% if s.merged %} ({{ s.members|join(', ') }}){% endif %}: {{ s.value|eur }} € ({{ s.share|pct }}) + {% else %}{{ s.label }}{% if s.merged %} ({{ s.members|join(', ') }}){% endif %}: {{ s.value|eur }} € ({{ s.share|pct }}){% endif %} {% endfor %} diff --git a/finance/app/templates/hilfe.html b/finance/app/templates/hilfe.html index 1d9320f..c7ecac3 100644 --- a/finance/app/templates/hilfe.html +++ b/finance/app/templates/hilfe.html @@ -95,18 +95,18 @@ Aufteilung: zeigt für einen Monat, welche monatlichen Kosten je Kategorie anfallen und wie viel der Einkünfte frei verfügbar bleibt. Grundlage ist immer die Planung (Fixposten, Einmalposten, - Kredite), nie die tatsächlichen Buchungen: „System-Plan" rechnet mit + Kredite), nie die tatsächlichen Buchungen: „System-Plan“ rechnet mit allen Posten und Krediten ohne Szenario-Änderungen und ist für jeden Monat wählbar; ein Szenario (mit seinen Änderungen und Krediten) erst ab dem laufenden Monat. Fixposten werden auf den Monat umgerechnet (vierteljährlich ÷ 3, jährlich ÷ 12), Einmalposten und Kreditraten zählen voll im Monat ihrer Fälligkeit, eine Kredit-Auszahlung zählt nicht als - Einkunft. Die Häkchen „Fixposten", „Einmalposten", „Kredite" gelten für + Einkunft. Die Häkchen „Fixposten“, „Einmalposten“, „Kredite“ gelten für Einnahmen und Ausgaben. Das Tortendiagramm zeigt die Einkünfte (Kosten je - Kategorie plus „Frei verfügbar"); übersteigen die Kosten die Einkünfte, + Kategorie plus „Frei verfügbar“); übersteigen die Kosten die Einkünfte, zeigt es die Gesamtkosten. Kleine Kategorien (unter 3 %, bzw. ab der - neunten) sind zu „Übrige" zusammengefasst – höchstens 8 benannte - Kategorien plus „Übrige" und „Frei verfügbar". Ein Klick auf ein Segment oder + neunten) sind zu „Übrige“ zusammengefasst – höchstens 8 benannte + Kategorien plus „Übrige“ und „Frei verfügbar“. Ein Klick auf ein Segment oder einen Legendeneintrag zeigt darunter die Posten dieser Kategorie. diff --git a/finance/tests/test_aufteilung_gui.py b/finance/tests/test_aufteilung_gui.py index aeb4a83..38db7ea 100644 --- a/finance/tests/test_aufteilung_gui.py +++ b/finance/tests/test_aufteilung_gui.py @@ -276,3 +276,37 @@ def test_drilldown_sonderzeichen(client, db): assert "kategorie=Haus+%26+Garten" in t r = _drill(client, kategorie="Haus & Garten") assert "Haus & Garten: 300,00 € / Monat" in r.text and "Rasen" in r.text + + +def test_monat_vor_jahr_1000_vierstellig(client, db): + _login(client) + eink, wohnen = Category(name="Einkommen"), Category(name="Wohnen") + db.add_all([eink, wohnen]) + db.flush() + db.add_all([ + RecurringItem(name="Gehalt", amount=D("3000.00"), rhythm="monthly", due_day=1, category_id=eink.id), + RecurringItem(name="Miete", amount=D("-1000.00"), rhythm="monthly", due_day=1, category_id=wohnen.id), + ]) + db.commit() + r = _get(client, monat="0001-01") + assert r.status_code == 200 + assert 'value="0001-01"' in r.text + assert "01.0001" in r.text + assert "monat=0001-01" in r.text + + +def test_uebrige_mitglieder_in_legende_der_zweiten_torte(client, db): + _login(client) + eink, kat = Category(name="Einkommen"), Category(name="Vielfalt") + db.add_all([eink, kat]) + db.flush() + db.add(RecurringItem(name="Gehalt", amount=D("3000.00"), rhythm="monthly", due_day=1, category_id=eink.id)) + db.add(RecurringItem(name="Großposten", amount=D("-1000.00"), rhythm="monthly", due_day=1, category_id=kat.id)) + for i in range(10): + db.add(RecurringItem(name=f"Kleinposten {i}", amount=D("-5.00"), rhythm="monthly", + due_day=1, category_id=kat.id)) + db.commit() + t = _drill(client, kategorie="Vielfalt").text + legende = re.search(r'', t, re.S).group(1) + assert 'class="legende-zusatz"' in legende + assert "Kleinposten 3" in legende diff --git a/finance/tests/test_gui.py b/finance/tests/test_gui.py index 42104dc..d7fe5f8 100644 --- a/finance/tests/test_gui.py +++ b/finance/tests/test_gui.py @@ -17,6 +17,12 @@ def test_pages_require_login(client): assert r.headers["location"] == "/login" +def test_aufteilung_fragment_erfordert_login(client): + r = client.get("/aufteilung/kategorie", follow_redirects=False) + assert r.status_code in (302, 303) + assert r.headers["location"] == "/login" + + def test_pages_render_after_login(client): client.post("/login", data={"username": "admin", "password": "geheim"}) for path in ("/", "/buchungen", "/import", "/salden", "/planung", "/szenarien", "/aufteilung", "/hilfe"):