fix: Aufteilung – Final-Review-Nachträge (Monat <1000, Übrige-Mitglieder, Auth-Test, Doku) (A13)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -14,8 +14,8 @@ class="seg {{ seg.s.color }}{% if seg.q %} klickbar{% endif %}{% if seg.gewaehlt
|
||||
<figcaption>{{ titel }}</figcaption>
|
||||
<svg class="torte-svg" viewBox="-1.05 -1.05 2.1 2.1" role="img" aria-label="{{ titel }}">
|
||||
{% for seg in segmente %}{% set s = seg.s %}
|
||||
{% if s.path %}<path d="{{ s.path }}" {{ seg_attrs(seg) }}><title>{{ s.label }}: {{ s.value|eur }} € ({{ s.share|pct }})</title></path>
|
||||
{% else %}<circle r="1" {{ seg_attrs(seg) }}><title>{{ s.label }}: {{ s.value|eur }} € ({{ s.share|pct }})</title></circle>{% endif %}
|
||||
{% if s.path %}<path d="{{ s.path }}" {{ seg_attrs(seg) }}><title>{{ s.label }}{% if s.merged %} ({{ s.members|join(', ') }}){% endif %}: {{ s.value|eur }} € ({{ s.share|pct }})</title></path>
|
||||
{% else %}<circle r="1" {{ seg_attrs(seg) }}><title>{{ s.label }}{% if s.merged %} ({{ s.members|join(', ') }}){% endif %}: {{ s.value|eur }} € ({{ s.share|pct }})</title></circle>{% endif %}
|
||||
{% endfor %}
|
||||
</svg>
|
||||
<ul class="legende">
|
||||
@@ -23,6 +23,7 @@ class="seg {{ seg.s.color }}{% if seg.q %} klickbar{% endif %}{% if seg.gewaehlt
|
||||
<li{% if seg.gewaehlt %} class="gewaehlt"{% endif %}><span class="swatch {{ s.color }}"></span>
|
||||
{%- if seg.q %}<a href="/aufteilung?{{ seg.q }}" hx-get="/aufteilung/kategorie?{{ seg.q }}" hx-target="#aufteilung-kategorie" hx-push-url="/aufteilung?{{ seg.q }}">{{ s.label }}</a>
|
||||
{%- else %}<span>{{ s.label }}</span>{% endif %}
|
||||
{%- if s.merged %} <span class="legende-zusatz">({{ s.members|join(', ') }})</span>{% endif %}
|
||||
<span class="legende-wert">{{ s.value|eur }} € · {{ s.share|pct }}{% if seg.zusatz %} · {{ seg.zusatz }}{% endif %}</span></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
@@ -95,18 +95,18 @@
|
||||
<strong>Aufteilung:</strong> 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.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -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'<ul class="legende">(.*?)</ul>', t, re.S).group(1)
|
||||
assert 'class="legende-zusatz"' in legende
|
||||
assert "Kleinposten 3" in legende
|
||||
|
||||
@@ -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"):
|
||||
|
||||
Reference in New Issue
Block a user