feat: Zu welchem Budget-Monat eine Buchung gehoert
Bisher entschied allein das Datum. Bei einem Budget-Monat, der nicht am Ersten beginnt, faellt das auf: Die Zahlung geht am 24. ab und gehoert doch zum naechsten Monat, die Rechnung wird verspaetet beglichen und gehoert zum letzten. Neu traegt jede Buchung einen Budget-Monat. Leer heisst weiterhin "aus dem Datum" - der Normalfall, und er bleibt es. Wird einer von Hand gesetzt, zaehlt die Buchung nur noch dort und in ihrem eigenen Monat NICHT mehr, sonst stuende sie zweimal da. Das Aendern liegt bewusst hinter einem Klick und hinter Pfeilen statt hinter einer Auswahlliste: Es ist der Ausnahmefall, nicht der Normalfall, und soll sich auch so anfuehlen. Waehlt man genau den Monat, der ohnehin gilt, bleibt die Buchung automatisch - verschiebt sich das Datum spaeter, wandert sie mit. In der Liste zeigt eine Marke den Monat, aber nur, wenn jemand eingegriffen hat. Der Server nimmt drei Monate in jede Richtung an. Ein Vertipper wie 2019-03 wird abgelehnt: Er liesse die Buchung aus jeder Auswertung verschwinden, ohne dass irgendwo etwas fehlt. Geprueft im Browser gegen eine Wegwerf-Datenbank mit Stichtag 25: Die Karte summiert 25.08.-24.09., eine verschobene Buchung verlaesst diesen Zeitraum und kehrt beim Zuruecknehmen zurueck. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -215,3 +215,97 @@ def test_ausgabenanteile_nennen_das_geplante(admin):
|
||||
ganz = admin.get("/api/dashboard/expense-share").json()
|
||||
assert ganz["total_planned"] > 0
|
||||
assert ganz["total_over"] == (ganz["total"] > ganz["total_planned"])
|
||||
|
||||
|
||||
def test_budgetzeitraum_von_hand_verschiebt_die_buchung(admin):
|
||||
"""Eine Zahlung faellt sichtbar in den einen Monat und gehoert zum anderen.
|
||||
|
||||
Der Ausnahmefall, fuer den es das Feld gibt: Die Buchung wandert vollstaendig
|
||||
– sie zaehlt im gewaehlten Monat und in ihrem eigenen dann NICHT mehr, sonst
|
||||
stuende sie zweimal da.
|
||||
"""
|
||||
admin.put("/api/settings/budget_start_day", params={"value": "1"})
|
||||
today = date.today()
|
||||
hier = f"{today.year:04d}-{today.month:02d}"
|
||||
|
||||
from app.services import budget as b
|
||||
dort = b.shift_label(hier, 1)
|
||||
|
||||
acc = admin.post("/api/accounts", json={"name": "Zeitraumkonto", "currency": "EUR"}).json()
|
||||
leb = _lebensmittel(admin)
|
||||
vorher = _ist(admin, hier, leb)
|
||||
vorher_dort = _ist(admin, dort, leb)
|
||||
|
||||
tx = admin.post("/api/transactions", json={
|
||||
"type": "expense", "date": str(today), "account_id": acc["id"],
|
||||
"amount_cents": 12345, "category_id": leb}).json()
|
||||
assert tx["budget_period"] == hier
|
||||
assert tx["budget_period_manuell"] is False
|
||||
assert _ist(admin, hier, leb) == vorher + 12345
|
||||
|
||||
# In den naechsten Monat schieben.
|
||||
tx = admin.patch(f"/api/transactions/{tx['id']}", json={"budget_period": dort}).json()
|
||||
assert tx["budget_period"] == dort
|
||||
assert tx["budget_period_manuell"] is True
|
||||
assert _ist(admin, hier, leb) == vorher, "im eigenen Monat darf sie nicht mehr zaehlen"
|
||||
assert _ist(admin, dort, leb) == vorher_dort + 12345
|
||||
|
||||
# Zuruecknehmen -> wieder das Datum.
|
||||
tx = admin.patch(f"/api/transactions/{tx['id']}", json={"budget_period": None}).json()
|
||||
assert tx["budget_period"] == hier
|
||||
assert tx["budget_period_manuell"] is False
|
||||
assert _ist(admin, hier, leb) == vorher + 12345
|
||||
|
||||
|
||||
def _ist(admin, label, cat_id):
|
||||
v = admin.get(f"/api/budget/{label}").json()
|
||||
for g in v["groups"]:
|
||||
for c in g["categories"]:
|
||||
if c["category_id"] == cat_id:
|
||||
return c["actual_cents"]
|
||||
return 0
|
||||
|
||||
|
||||
def test_ein_abwegiger_budgetmonat_wird_abgelehnt(admin):
|
||||
"""Ein Vertipper wie 2019-03 liesse die Buchung aus jeder Auswertung
|
||||
verschwinden, ohne dass irgendwo etwas fehlt. Das muss auffallen."""
|
||||
admin.put("/api/settings/budget_start_day", params={"value": "1"})
|
||||
today = date.today()
|
||||
acc = admin.post("/api/accounts", json={"name": "Abwegkonto", "currency": "EUR"}).json()
|
||||
tx = admin.post("/api/transactions", json={
|
||||
"type": "expense", "date": str(today), "account_id": acc["id"],
|
||||
"amount_cents": 500, "category_id": _lebensmittel(admin)}).json()
|
||||
|
||||
r = admin.patch(f"/api/transactions/{tx['id']}", json={"budget_period": "2019-03"})
|
||||
assert r.status_code == 400
|
||||
assert "2019-03" in r.json()["detail"]
|
||||
|
||||
# Drei Monate in jede Richtung decken den echten Fall ab.
|
||||
from app.services import budget as b
|
||||
eigen = b.period_of(today, 1)
|
||||
assert admin.patch(f"/api/transactions/{tx['id']}",
|
||||
json={"budget_period": b.shift_label(eigen, 3)}).status_code == 200
|
||||
assert admin.patch(f"/api/transactions/{tx['id']}",
|
||||
json={"budget_period": b.shift_label(eigen, 4)}).status_code == 400
|
||||
|
||||
|
||||
def test_der_eigene_monat_wird_nicht_festgeschrieben(admin):
|
||||
"""Waehlt man genau den Monat, der ohnehin gilt, bleibt die Buchung
|
||||
automatisch – verschiebt sich das Datum spaeter, wandert sie mit."""
|
||||
admin.put("/api/settings/budget_start_day", params={"value": "25"})
|
||||
from app.services import budget as b
|
||||
|
||||
acc = admin.post("/api/accounts", json={"name": "Mitwanderkonto", "currency": "EUR"}).json()
|
||||
tx = admin.post("/api/transactions", json={
|
||||
"type": "expense", "date": "2026-09-10", "account_id": acc["id"],
|
||||
"amount_cents": 700, "category_id": _lebensmittel(admin)}).json()
|
||||
assert tx["budget_period"] == "2026-09"
|
||||
|
||||
tx = admin.patch(f"/api/transactions/{tx['id']}", json={"budget_period": "2026-09"}).json()
|
||||
assert tx["budget_period_manuell"] is False
|
||||
|
||||
# Datum ueber den Stichtag hinaus -> der Monat wandert von selbst mit.
|
||||
tx = admin.patch(f"/api/transactions/{tx['id']}", json={"date": "2026-09-26"}).json()
|
||||
assert tx["budget_period"] == "2026-10"
|
||||
assert b.period_of(date(2026, 9, 26), 25) == "2026-10"
|
||||
admin.put("/api/settings/budget_start_day", params={"value": "1"})
|
||||
|
||||
Reference in New Issue
Block a user