feat: Kontostand aufschluesseln – und der Angleich-Knopf bleibt
Zwei Sachen aus derselben Wurzel: Wenn ein Saldo nicht stimmt, sieht man nicht, warum, und kann auch nichts dagegen tun. Ein Klick auf den Kontostand zeigt jetzt die Rechnung: Anfangsbestand, Einnahmen, Ausgaben, Uebertraege raus und rein – jeweils mit der Anzahl der Buchungen – und was daraus wird. Darunter der Saldo der Bank und, einzeln aufgefuehrt, alle Uebertraege des Kontos. Genau die sind die haeufigste Ursache und in einer langen Buchungsliste am schwersten zu ueberblicken. Rechnung und Kontenliste kommen aus EINER Quelle: ``account_balance_cents`` liest jetzt aus der Aufschluesselung. Sonst koennte die Anzeige etwas anderes behaupten als der Saldo, und man suchte den Fehler im Falschen. Ausserdem war der Knopf "Saldo der Bank uebernehmen" verschwunden, sobald die Bank nur einen verfuegbaren Saldo meldet. Das war zu weit gedacht: Nicht vergleichbar heisst nicht, dass man nicht angleichen duerfte. Der Knopf ist zurueck, und die Nachfrage sagt in dem Fall, worauf man sich einlaesst – der verfuegbare Saldo enthaelt Vorgemerktes, das spaeter noch als Buchung kommt. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -221,3 +221,51 @@ def test_kontenreihenfolge_nimmt_keine_fremden_konten(admin, client):
|
||||
eigen = _mk_account(admin, name="Eigenes Konto")
|
||||
r = admin.put("/api/accounts/order", json={"ids": [eigen["id"], 999999]})
|
||||
assert r.status_code == 404
|
||||
|
||||
|
||||
def test_saldo_laesst_sich_aufschluesseln(admin):
|
||||
"""Stimmt ein Kontostand nicht, liegt es fast immer an einer dieser Zahlen
|
||||
oder an einem Uebertrag. Ohne diese Ansicht sucht man in einer langen
|
||||
Buchungsliste."""
|
||||
import secrets
|
||||
|
||||
a = admin.post("/api/accounts", json={
|
||||
"name": f"Aufschluss-A {secrets.token_hex(3)}", "type": "girokonto",
|
||||
"currency": "CHF", "opening_balance_cents": 100000}).json()
|
||||
b = admin.post("/api/accounts", json={
|
||||
"name": f"Aufschluss-B {secrets.token_hex(3)}", "type": "girokonto",
|
||||
"currency": "CHF", "opening_balance_cents": 0}).json()
|
||||
|
||||
admin.post("/api/transactions", json={
|
||||
"type": "income", "date": "2026-08-01", "account_id": a["id"],
|
||||
"amount_cents": 50000, "payee": "Lohn"})
|
||||
admin.post("/api/transactions", json={
|
||||
"type": "expense", "date": "2026-08-02", "account_id": a["id"],
|
||||
"amount_cents": 20000, "payee": "Miete"})
|
||||
admin.post("/api/transactions", json={
|
||||
"type": "transfer", "date": "2026-08-03", "account_id": a["id"],
|
||||
"to_account_id": b["id"], "amount_cents": 30000})
|
||||
|
||||
r = admin.get(f"/api/accounts/{a['id']}/aufschluesselung")
|
||||
assert r.status_code == 200, r.text
|
||||
teile = r.json()["teile"]
|
||||
assert teile["anfangsbestand"]["cents"] == 100000
|
||||
assert teile["einnahmen"] == {"cents": 50000, "anzahl": 1}
|
||||
assert teile["ausgaben"] == {"cents": 20000, "anzahl": 1}
|
||||
assert teile["uebertraege_raus"] == {"cents": 30000, "anzahl": 1}
|
||||
assert teile["uebertraege_rein"] == {"cents": 0, "anzahl": 0}
|
||||
assert teile["saldo"]["cents"] == 100000 + 50000 - 20000 - 30000
|
||||
|
||||
# Und die Rechnung muss dasselbe sagen wie die Kontenliste.
|
||||
assert admin.get(f"/api/accounts/{a['id']}").json()["balance_cents"] == \
|
||||
teile["saldo"]["cents"]
|
||||
|
||||
# Das Uebertrags-Bein steht einzeln da – dort sucht man zuerst.
|
||||
beine = r.json()["uebertraege"]
|
||||
assert len(beine) == 1 and beine[0]["raus"] is True
|
||||
assert beine[0]["cents"] == 30000 and beine[0]["nach"] == b["name"]
|
||||
|
||||
# Auf der Gegenseite dasselbe Bein, nur andersherum.
|
||||
gegen = admin.get(f"/api/accounts/{b['id']}/aufschluesselung").json()
|
||||
assert gegen["teile"]["uebertraege_rein"] == {"cents": 30000, "anzahl": 1}
|
||||
assert gegen["uebertraege"][0]["raus"] is False
|
||||
|
||||
Reference in New Issue
Block a user