diff --git a/backend/scripts/pruefe_transfers.py b/backend/scripts/pruefe_transfers.py new file mode 100755 index 0000000..f5929fe --- /dev/null +++ b/backend/scripts/pruefe_transfers.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python3 +"""Zeigt, welches Konto an welches überträgt – und wo dabei etwas fehlt. + +Ein falscher Kontostand kommt selten von den Ausgaben, fast immer von den +Überträgen: falsche Richtung, fehlendes Zielkonto oder ein Währungswechsel +ohne Zielbetrag (dann wird der Fremdbetrag 1:1 gutgeschrieben). + +Aufruf: pruefe_transfers.py [pfad-zur-db] +""" +import sqlite3 +import sys + +DB = sys.argv[1] if len(sys.argv) > 1 else "/var/lib/certain-finance/app.db" +con = sqlite3.connect(f"file:{DB}?mode=ro", uri=True) +con.row_factory = sqlite3.Row + +konten = {r["id"]: r for r in con.execute("SELECT id, name, currency FROM accounts")} + + +def name(i): + return konten[i]["name"] if i in konten else f"?{i}" + + +def waehrung(i): + return konten[i]["currency"] if i in konten else "?" + + +def geld(c): + return f"{(c or 0) / 100:,.2f}".replace(",", "'") + + +print(f"Datenbank: {DB}\n") +print("=== Wer überträgt an wen ===") +print(f"{'von':<24}{'nach':<24}{'Anz':>5}{'Summe raus':>14}{'Summe rein':>14}") +print("-" * 81) +for r in con.execute( + "SELECT account_id a, to_account_id b, COUNT(*) n, SUM(amount_cents) raus, " + "SUM(COALESCE(to_amount_cents, amount_cents)) rein FROM transactions " + "WHERE type='transfer' AND to_account_id IS NOT NULL GROUP BY a, b ORDER BY raus DESC" +): + print(f"{name(r['a'])[:23]:<24}{name(r['b'])[:23]:<24}{r['n']:>5}" + f"{geld(r['raus']) + ' ' + waehrung(r['a']):>14}" + f"{geld(r['rein']) + ' ' + waehrung(r['b']):>14}") + +offen = con.execute( + "SELECT COUNT(*) n, SUM(amount_cents) s FROM transactions " + "WHERE type='transfer' AND to_account_id IS NULL").fetchone() +print(f"\nÜberträge OHNE Zielkonto: {offen['n']}, Summe {geld(offen['s'])}" + " <-- gehen vom Quellkonto ab und kommen nirgends an") + +print("\n=== Währungswechsel ohne Zielbetrag ===") +schief = con.execute( + "SELECT t.id, t.date, t.amount_cents, COALESCE(t.payee,'') p, a.name qn, a.currency qw, " + "z.name zn, z.currency zw FROM transactions t " + "JOIN accounts a ON a.id = t.account_id JOIN accounts z ON z.id = t.to_account_id " + "WHERE t.type='transfer' AND a.currency <> z.currency AND t.to_amount_cents IS NULL " + "ORDER BY t.amount_cents DESC").fetchall() +if not schief: + print(" keine – gut.") +else: + summe = sum(r["amount_cents"] for r in schief) + print(f" {len(schief)} Stück, Summe {geld(summe)}" + " <-- der Betrag wird ohne Umrechnung gutgeschrieben") + for r in schief[:12]: + print(f" {r['date']} {geld(r['amount_cents']):>10} {r['qw']} -> {r['zw']} " + f"{r['qn'][:16]:<16} -> {r['zn'][:16]:<16} {r['p'][:26]}") + +print("\n=== Die 12 grössten Überträge ===") +for r in con.execute( + "SELECT t.date, t.amount_cents, t.to_amount_cents, COALESCE(t.payee,'') p, " + "t.account_id a, t.to_account_id b FROM transactions t WHERE t.type='transfer' " + "ORDER BY t.amount_cents DESC LIMIT 12" +): + ziel = name(r["b"]) if r["b"] else "– kein Ziel –" + print(f" {r['date']} {geld(r['amount_cents']):>10} {waehrung(r['a'])} " + f"{name(r['a'])[:18]:<18} -> {ziel[:18]:<18} {r['p'][:30]}")