Eine Saeule 3a ist gesperrt – bis zur Rente, zur Selbstaendigkeit oder zur Auswanderung. Bisher zaehlte ihr Stand in dieselbe Summe wie das Girokonto, und die Antwort auf "wie viel habe ich" war um genau diesen Betrag zu hoch. Das Geld steht da und ist doch nicht da. Konten lassen sich jetzt als gebunden kennzeichnen. "Gesamt" zeigt dann nur noch, was wirklich ausgegeben werden kann; darunter steht "zusaetzlich CHF 2'004.61 gebunden" – aufklappbar, mit jedem Konto einzeln. Eine Summe, die man nicht nachrechnen kann, muss man glauben. Verschwiegen wird nichts: Der Saldo bleibt am Konto stehen, und Vermoegensverlauf wie Nettovermoegen rechnen weiter damit. Es nicht ausgeben zu koennen heisst nicht, es nicht zu besitzen – das ist der ganze Unterschied, und ein Test haelt ihn fest. Ein eigenes Kennzeichen, keine Ableitung aus dem Kontotyp: Ein frei verkaeufliches Depot und eine gesperrte Saeule 3a kommen beide als "depot" herein. Am Typ laesst sich das nicht ablesen – am ausdruecklichen Eintrag schon. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
114 lines
4.3 KiB
Python
114 lines
4.3 KiB
Python
"""Geld, an das man nicht herankommt.
|
||
|
||
Eine Saeule 3a ist gesperrt bis zur Rente, zur Selbstaendigkeit oder zur
|
||
Auswanderung. Sie in dieselbe Summe zu werfen wie das Girokonto beantwortet
|
||
eine andere Frage, als der Nutzer stellt.
|
||
|
||
Der Unterschied, um den es geht: VERMOEGEN bleibt es – VERFUEGBAR ist es nicht.
|
||
"""
|
||
|
||
import json
|
||
from datetime import date
|
||
|
||
|
||
def _konto(admin, name, **kw):
|
||
body = {"name": name, "type": "girokonto", "currency": "EUR",
|
||
"opening_balance_cents": 0}
|
||
body.update(kw)
|
||
r = admin.post("/api/accounts", json=body)
|
||
assert r.status_code == 201, r.text
|
||
return r.json()
|
||
|
||
|
||
def test_gebunden_wird_gespeichert_und_laesst_sich_loesen(admin):
|
||
konto = _konto(admin, "Saeule 3a", type="depot", is_locked=True,
|
||
opening_balance_cents=200461)
|
||
assert konto["is_locked"] is True
|
||
|
||
r = admin.patch(f"/api/accounts/{konto['id']}", json={"is_locked": False})
|
||
assert r.json()["is_locked"] is False, "ausgewandert – jetzt kommt man dran"
|
||
|
||
r = admin.patch(f"/api/accounts/{konto['id']}", json={"name": "Saeule 3a ZKB"})
|
||
assert r.json()["is_locked"] is False, "ein Namenswechsel aendert nichts daran"
|
||
|
||
|
||
def test_neue_konten_sind_nicht_gebunden(admin):
|
||
assert _konto(admin, "Ganz normal")["is_locked"] is False
|
||
|
||
|
||
def test_gebundenes_geld_faellt_aus_der_verfuegbaren_summe(admin):
|
||
"""Der Punkt der ganzen Sache."""
|
||
vorher = admin.get("/api/dashboard/balances").json()
|
||
|
||
_konto(admin, "Giro Verfuegbar", opening_balance_cents=300000)
|
||
_konto(admin, "Saeule 3a Gesperrt", type="depot", is_locked=True,
|
||
opening_balance_cents=200461)
|
||
|
||
jetzt = admin.get("/api/dashboard/balances").json()
|
||
assert jetzt["total"] == vorher["total"] + 300000, "nur das Freie zaehlt"
|
||
assert jetzt["gebunden"] == vorher.get("gebunden", 0) + 200461
|
||
|
||
gesperrt = [a for a in jetzt["accounts"] if a["name"] == "Saeule 3a Gesperrt"][0]
|
||
assert gesperrt["locked"] is True
|
||
assert gesperrt["balance_base_cents"] == 200461, "der Saldo bleibt, was er ist"
|
||
|
||
|
||
def test_gebundenes_geld_bleibt_vermoegen(admin):
|
||
"""Der Unterschied, auf den es ankommt.
|
||
|
||
Es nicht ausgeben zu koennen heisst nicht, es nicht zu besitzen. Der
|
||
Vermoegensverlauf und das Nettovermoegen rechnen es weiter mit – nur die
|
||
Summe, die „wie viel habe ich zur Verfuegung" beantwortet, laesst es weg.
|
||
"""
|
||
from app.database import SessionLocal
|
||
from app.models import Account, User
|
||
from app.services import balances
|
||
|
||
def vermoegen() -> int:
|
||
db = SessionLocal()
|
||
try:
|
||
user = db.query(User).filter(User.username == "admin").one()
|
||
konten = db.query(Account).filter(Account.owner_id == user.id).all()
|
||
return balances.net_worth_cents(db, konten, "EUR", date.today())
|
||
finally:
|
||
db.close()
|
||
|
||
vorher = vermoegen()
|
||
_konto(admin, "Saeule 3a Vermoegen", type="depot", is_locked=True,
|
||
opening_balance_cents=200461)
|
||
assert vermoegen() == vorher + 200461
|
||
|
||
|
||
def test_archiviertes_gebundenes_konto_zaehlt_nirgends(admin):
|
||
konto = _konto(admin, "Alte Saeule", type="depot", is_locked=True,
|
||
opening_balance_cents=50000)
|
||
vorher = admin.get("/api/dashboard/balances").json()
|
||
|
||
admin.patch(f"/api/accounts/{konto['id']}", json={"is_archived": True})
|
||
|
||
jetzt = admin.get("/api/dashboard/balances").json()
|
||
assert jetzt["total"] == vorher["total"]
|
||
assert jetzt["gebunden"] == vorher["gebunden"] - 50000
|
||
|
||
|
||
def test_gebunden_ueberlebt_das_backup(admin):
|
||
konto = _konto(admin, "Saeule 3a Backup", type="depot", is_locked=True,
|
||
opening_balance_cents=123456)
|
||
|
||
daten = json.loads(admin.get("/api/export/backup.json").content)
|
||
gesichert = [a for a in daten["data"]["accounts"] if a["name"] == "Saeule 3a Backup"]
|
||
assert gesichert and gesichert[0]["is_locked"] in (True, 1)
|
||
|
||
admin.delete(f"/api/accounts/{konto['id']}")
|
||
r = admin.post(
|
||
"/api/backup/restore",
|
||
files={"file": ("backup.json", json.dumps(daten).encode("utf-8"),
|
||
"application/json")},
|
||
data={"mode": "replace"},
|
||
)
|
||
assert r.status_code == 200, r.text
|
||
|
||
zurueck = [a for a in admin.get("/api/accounts").json()
|
||
if a["name"] == "Saeule 3a Backup"]
|
||
assert zurueck and zurueck[0]["is_locked"] is True
|