feat: Salden-Seite mit Stichtag und Monatsuebersicht
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -7,7 +7,7 @@ H = {"Authorization": "Bearer test-key"}
|
||||
|
||||
|
||||
def test_pages_require_login(client):
|
||||
for path in ("/", "/buchungen", "/import", "/planung", "/hilfe"):
|
||||
for path in ("/", "/buchungen", "/import", "/salden", "/planung", "/hilfe"):
|
||||
r = client.get(path, follow_redirects=False)
|
||||
assert r.status_code in (302, 303), path
|
||||
assert r.headers["location"] == "/login"
|
||||
@@ -15,7 +15,7 @@ def test_pages_require_login(client):
|
||||
|
||||
def test_pages_render_after_login(client):
|
||||
client.post("/login", data={"username": "admin", "password": "geheim"})
|
||||
for path in ("/", "/buchungen", "/import", "/planung", "/hilfe"):
|
||||
for path in ("/", "/buchungen", "/import", "/salden", "/planung", "/hilfe"):
|
||||
r = client.get(path)
|
||||
assert r.status_code == 200
|
||||
assert "Finanzberatung" in r.text
|
||||
@@ -193,3 +193,78 @@ def test_planning_page_shows_empty_suggestions_hint(client):
|
||||
assert r.status_code == 200
|
||||
assert "Vorschläge aus Buchungen" in r.text
|
||||
assert "Keine Vorschläge" in r.text
|
||||
|
||||
|
||||
def test_salden_page_stichtag_and_month_overview(client, db):
|
||||
from app.engine.loans import add_months
|
||||
from app.models.tables import Transaction
|
||||
|
||||
# Drei Monate relativ zu "heute" verwenden statt fester Kalenderdaten,
|
||||
# damit der Test unabhaengig vom Ausfuehrungsdatum immer genau 3
|
||||
# Monatsanfangs-Zeilen (frühester Buchungsmonat bis heute) erzeugt.
|
||||
today = date.today()
|
||||
m0 = date(today.year, today.month, 1)
|
||||
m1 = add_months(m0, -1)
|
||||
m2 = add_months(m0, -2)
|
||||
|
||||
acc = Account(bank="dkb", iban="DE-SALDEN-1", name="Salden-Konto", type="giro")
|
||||
db.add(acc)
|
||||
db.flush()
|
||||
acc.anchor_date = m2
|
||||
acc.anchor_balance = Decimal("1000.00")
|
||||
db.flush()
|
||||
|
||||
db.add_all([
|
||||
Transaction(account_id=acc.id, booking_date=m2 + timedelta(days=5),
|
||||
amount=Decimal("111.11"), purpose="SALDEN-TEST-1",
|
||||
counterparty="X", status="confirmed", dedup_hash="salden-h1"),
|
||||
Transaction(account_id=acc.id, booking_date=m1 + timedelta(days=3),
|
||||
amount=Decimal("22.22"), purpose="SALDEN-TEST-2",
|
||||
counterparty="X", status="confirmed", dedup_hash="salden-h2"),
|
||||
Transaction(account_id=acc.id, booking_date=m0 + timedelta(days=2),
|
||||
amount=Decimal("-33.33"), purpose="SALDEN-TEST-3",
|
||||
counterparty="X", status="confirmed", dedup_hash="salden-h3"),
|
||||
])
|
||||
db.commit()
|
||||
|
||||
client.post("/login", data={"username": "admin", "password": "geheim"})
|
||||
|
||||
# Stichtag zwischen Buchung 2 und Buchung 3: Saldo = Anker + Buchung1 + Buchung2.
|
||||
stichtag = m1 + timedelta(days=10)
|
||||
expected_stichtag = Decimal("1000.00") + Decimal("111.11") + Decimal("22.22")
|
||||
r = client.get(f"/salden?stichtag={stichtag.isoformat()}")
|
||||
assert r.status_code == 200
|
||||
assert f"{expected_stichtag:.2f}" in r.text
|
||||
|
||||
# Monatsuebersicht: genau 3 Monatszeilen (m2, m1, m0).
|
||||
assert r.text.count('data-month="') == 3
|
||||
for m in (m0, m1, m2):
|
||||
assert f'data-month="{m.isoformat()}"' in r.text
|
||||
|
||||
# Monatsanfangs-Saldo fuer m1 = Stand Ende Vortag von m1 (letzter Tag von
|
||||
# m2), d.h. NACH Buchung 1 (in m2), aber VOR Buchung 2 (die faellt erst
|
||||
# in m1 selbst und darf den Monatsanfang nicht verfaelschen).
|
||||
expected_m1_start = Decimal("1000.00") + Decimal("111.11")
|
||||
assert f"{expected_m1_start:.2f}" in r.text
|
||||
|
||||
|
||||
def test_salden_stichtag_garbage_falls_back_to_today(client):
|
||||
# Lenient wie die Filter auf /buchungen: ein kaputter stichtag-Parameter
|
||||
# darf nie zu 422/500 fuehren, sondern degradiert auf "heute".
|
||||
client.post("/login", data={"username": "admin", "password": "geheim"})
|
||||
r = client.get("/salden?stichtag=garbage")
|
||||
assert r.status_code == 200
|
||||
assert date.today().strftime('%d.%m.%Y') in r.text
|
||||
|
||||
|
||||
def test_salden_shows_ohne_anker_footnote(client, db):
|
||||
# UX-Regel: Konten ohne Anker sind immer sichtbar als relative Werte
|
||||
# gekennzeichnet, nicht stillschweigend wie geankerte Konten dargestellt.
|
||||
acc = Account(bank="vr", iban="DE-SALDEN-2", name="Ohne-Anker-Konto", type="giro")
|
||||
db.add(acc)
|
||||
db.commit()
|
||||
|
||||
client.post("/login", data={"username": "admin", "password": "geheim"})
|
||||
r = client.get("/salden")
|
||||
assert r.status_code == 200
|
||||
assert "ohne Anker" in r.text and "relative Werte" in r.text
|
||||
|
||||
Reference in New Issue
Block a user