Files
certain/backend/tests/test_transactions_offen.py
T
GuidoandClaude Opus 5 335a7d55c8 feat: Gleich aussehende Buchungen stehen in der Arbeitsliste
Bisher fand man Dubletten nur zufaellig beim Durchscrollen der Buchungsliste –
oder ich habe ein Skript dafuer geschrieben, und der Nutzer musste sich auf
einen Server einloggen, um seine eigenen Zahlen zu reparieren. Beides ist
falsch.

Die Arbeitsliste zeigt sie jetzt: gleiches Konto, gleicher Tag, gleicher
Betrag, gleicher Text. Je Zeile steht die Kennung der Bank dabei, und die
entscheidet die Frage – dieselbe Kennung heisst sicher dieselbe Zahlung,
verschiedene heissen, die Bank hat sie als verschieden gemeldet.

Entschieden wird bewusst NICHTS von selbst. Drei Aufrundungen ueber 1.00 am
selben Tag sind echt, drei gleiche Einkaeufe koennen es auch sein; das weiss
nur der Nutzer. Certain meldet und laesst loeschen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-30 14:32:40 +02:00

124 lines
5.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import io
import json
def _konto(admin, **kw):
body = {"name": "Giro", "type": "girokonto", "currency": "EUR", "opening_balance_cents": 0}
body.update(kw)
return admin.post("/api/accounts", json=body).json()
def _kategorie(admin):
for g in admin.get("/api/categories").json():
for c in g["categories"]:
if c["name"] == "Lebensmittel":
return c["id"]
raise AssertionError("Lebensmittel fehlt")
def test_offene_buchungen_listet_nur_unkategorisierte(admin):
"""Zum Abarbeiten braucht man die Buchungen ohne Kategorie – und zwar nur die."""
konto = _konto(admin, name="Abarbeiten")
kat = _kategorie(admin)
admin.post("/api/transactions", json={"type": "expense", "date": "2026-04-01",
"account_id": konto["id"], "amount_cents": 1000,
"payee": "Ohne Kategorie"})
admin.post("/api/transactions", json={"type": "expense", "date": "2026-04-02",
"account_id": konto["id"], "amount_cents": 2000,
"payee": "Mit Kategorie", "category_id": kat})
admin.post("/api/transactions", json={"type": "income", "date": "2026-04-03",
"account_id": konto["id"], "amount_cents": 3000,
"payee": "Einnahme ohne Kategorie"})
offen = admin.get("/api/transactions", params={"uncategorized": "true",
"account_id": konto["id"]}).json()
namen = sorted(t["payee"] for t in offen)
assert namen == ["Einnahme ohne Kategorie", "Ohne Kategorie"]
def test_offene_buchungen_lassen_uebertraege_aus(admin):
"""Ein Übertrag ordnet kein Geld ein, er verschiebt es – er gehört nicht auf die Liste."""
a = _konto(admin, name="Von")
b = _konto(admin, name="Nach")
admin.post("/api/transactions", json={"type": "transfer", "date": "2026-04-04",
"account_id": a["id"], "to_account_id": b["id"],
"amount_cents": 5000})
offen = admin.get("/api/transactions", params={"uncategorized": "true",
"account_id": a["id"]}).json()
assert offen == []
def test_verknuepfte_buchungen_sind_nicht_mehr_offen(admin):
"""Sie gehoeren zu einem Uebertrag und zaehlen in keiner Auswertung. Nach
einer Kategorie zu fragen, verlangt Arbeit, die nichts bewirkt."""
import secrets
a = admin.post("/api/accounts", json={
"name": f"Offen-A {secrets.token_hex(3)}", "type": "girokonto",
"currency": "CHF", "opening_balance_cents": 0}).json()
b = admin.post("/api/accounts", json={
"name": f"Offen-B {secrets.token_hex(3)}", "type": "girokonto",
"currency": "CHF", "opening_balance_cents": 0}).json()
betrag = 20000 + secrets.randbelow(900)
raus = admin.post("/api/transactions", json={
"type": "expense", "date": "2026-08-21", "account_id": a["id"],
"amount_cents": betrag, "payee": "Umbuchung"}).json()
rein = admin.post("/api/transactions", json={
"type": "income", "date": "2026-08-21", "account_id": b["id"],
"amount_cents": betrag, "payee": "Umbuchung"}).json()
offen = {t["id"] for t in admin.get("/api/transactions",
params={"uncategorized": True}).json()}
assert raus["id"] in offen and rein["id"] in offen, "vorher beide offen"
assert admin.post("/api/uebertraege/verbinden",
json={"a_id": raus["id"], "b_id": rein["id"]}).status_code == 200
danach = {t["id"] for t in admin.get("/api/transactions",
params={"uncategorized": True}).json()}
assert raus["id"] not in danach and rein["id"] not in danach, \
"verknuepfte Buchungen brauchen keine Kategorie mehr"
def test_gleich_aussehende_buchungen_werden_gemeldet_nicht_entschieden(admin):
"""Drei Aufrundungen ueber 1.00 am selben Tag sind echt, drei gleiche
Einkaeufe koennen es auch sein – das weiss nur der Nutzer. Deshalb wird
gemeldet, nicht entschieden, und die Kennung der Bank steht dabei."""
import secrets
from app.database import SessionLocal
from app.models import InboxItem, InboxSource, InboxStatus, User
konto = admin.post("/api/accounts", json={
"name": f"Dublette {secrets.token_hex(3)}", "type": "girokonto",
"currency": "CHF", "opening_balance_cents": 0}).json()
text = f"Migrolino {secrets.token_hex(3)}"
ids = [admin.post("/api/transactions", json={
"type": "expense", "date": "2026-08-25", "account_id": konto["id"],
"amount_cents": 390, "payee": text}).json()["id"] for _ in range(3)]
# Zwei davon kamen von der Bank, unter verschiedenen Kennungen.
db = SessionLocal()
try:
besitzer = db.query(User).order_by(User.id).first()
for i, txn_id in enumerate(ids[:2]):
db.add(InboxItem(owner_id=besitzer.id, source=InboxSource.bank,
status=InboxStatus.booked, account_id=konto["id"],
date=__import__("datetime").date(2026, 8, 25),
amount_cents=-390, currency="CHF", payee=text,
external_id=f"acc-x:ref-{i}", transaction_id=txn_id))
db.commit()
finally:
db.close()
gruppen = admin.get("/api/transactions/dubletten").json()["gruppen"]
meine = next(g for g in gruppen if g["account_id"] == konto["id"])
assert len(meine["zeilen"]) == 3
assert meine["amount_cents"] == 390
kennungen = {z["kennung"] for z in meine["zeilen"]}
assert kennungen == {"ref-0", "ref-1", None}, "die Kennung muss dabeistehen"
assert {z["herkunft"] for z in meine["zeilen"]} == {"bank", "manuell"}
# Und nichts wurde geloescht.
assert admin.get(f"/api/accounts/{konto['id']}").json()["balance_cents"] == -1170