fix: Schon verbundene Buchungen stehen nicht mehr bei jedem Regel-Lauf unter "uebersprungen"
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
54ff6d6896
commit
f52113e05d
@@ -12,6 +12,7 @@ unangetastet und werden gemeldet. Geraten wird nichts.
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from sqlalchemy import select, union
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from ..models import (
|
from ..models import (
|
||||||
@@ -21,6 +22,7 @@ from ..models import (
|
|||||||
CategoryGroup,
|
CategoryGroup,
|
||||||
CategoryKind,
|
CategoryKind,
|
||||||
Transaction,
|
Transaction,
|
||||||
|
TransferLink,
|
||||||
TxnType,
|
TxnType,
|
||||||
User,
|
User,
|
||||||
)
|
)
|
||||||
@@ -81,11 +83,21 @@ def wende_an(db: Session, user: User, nur_vorschau: bool = True) -> dict:
|
|||||||
.join(CategoryGroup, CategoryGroup.id == Category.group_id)
|
.join(CategoryGroup, CategoryGroup.id == Category.group_id)
|
||||||
.filter(Category.owner_id == user.id)
|
.filter(Category.owner_id == user.id)
|
||||||
}
|
}
|
||||||
|
# Verknuepfte Buchungen sind erledigt – genau wie in der Arbeitsliste.
|
||||||
|
# Ohne diesen Ausschluss passen sie weiter auf die Uebertrags-Regel, werden
|
||||||
|
# abgelehnt („schon verbunden“) und stehen bei JEDEM Lauf erneut unter
|
||||||
|
# „uebersprungen“. Dort verdecken sie die wirklich offenen Buchungen und
|
||||||
|
# fuellen obendrein das Limit.
|
||||||
|
verknuepft = union(
|
||||||
|
select(TransferLink.from_txn_id).where(TransferLink.owner_id == user.id),
|
||||||
|
select(TransferLink.to_txn_id).where(TransferLink.owner_id == user.id),
|
||||||
|
)
|
||||||
offen = (
|
offen = (
|
||||||
db.query(Transaction)
|
db.query(Transaction)
|
||||||
.filter(Transaction.owner_id == user.id,
|
.filter(Transaction.owner_id == user.id,
|
||||||
Transaction.category_id.is_(None),
|
Transaction.category_id.is_(None),
|
||||||
Transaction.type != TxnType.transfer)
|
Transaction.type != TxnType.transfer,
|
||||||
|
Transaction.id.notin_(verknuepft))
|
||||||
.order_by(Transaction.date, Transaction.id)
|
.order_by(Transaction.date, Transaction.id)
|
||||||
.limit(MAX_BUCHUNGEN)
|
.limit(MAX_BUCHUNGEN)
|
||||||
.all()
|
.all()
|
||||||
|
|||||||
@@ -419,3 +419,39 @@ def test_nachtraegliches_anwenden_ruehrt_kategorisiertes_nicht_an(admin):
|
|||||||
|
|
||||||
admin.post("/api/categories/keywords/anwenden", params={"vorschau": False})
|
admin.post("/api/categories/keywords/anwenden", params={"vorschau": False})
|
||||||
assert admin.get(f"/api/transactions/{txn['id']}").json()["category_id"] == kat
|
assert admin.get(f"/api/transactions/{txn['id']}").json()["category_id"] == kat
|
||||||
|
|
||||||
|
|
||||||
|
def test_verknuepfte_buchungen_landen_nicht_in_uebersprungen(admin):
|
||||||
|
"""Eine verknuepfte Buchung ist erledigt – beide Seiten stehen, beide zaehlen
|
||||||
|
in keiner Auswertung. Sie passt aber weiter auf die Uebertrags-Regel und
|
||||||
|
wurde deshalb bei JEDEM Lauf neu abgelehnt: Die Uebersprungen-Liste fuellte
|
||||||
|
sich mit Hunderten Zeilen, die nie verschwinden, und verdeckte die
|
||||||
|
wirklich offenen Buchungen."""
|
||||||
|
quelle = _kto(admin, name=f"Revolut {secrets.token_hex(3)}")
|
||||||
|
pocket = _kto(admin, name=f"Sparen Pocket {secrets.token_hex(3)}")
|
||||||
|
kennung = f"um chf sparen {secrets.token_hex(6)}"
|
||||||
|
betrag = 60 + secrets.randbelow(900)
|
||||||
|
|
||||||
|
raus = admin.post("/api/transactions", json={
|
||||||
|
"type": "expense", "date": "2026-01-22", "account_id": quelle["id"],
|
||||||
|
"amount_cents": betrag, "payee": kennung.upper()}).json()
|
||||||
|
rein = admin.post("/api/transactions", json={
|
||||||
|
"type": "income", "date": "2026-01-22", "account_id": pocket["id"],
|
||||||
|
"amount_cents": betrag, "payee": kennung.upper()}).json()
|
||||||
|
|
||||||
|
r = admin.post("/api/categories/transfer-keywords", json={
|
||||||
|
"word": kennung, "target_account_id": pocket["id"]})
|
||||||
|
assert r.status_code == 201, r.text
|
||||||
|
|
||||||
|
# Beide Seiten sind dieselbe Bewegung – verbinden, nicht uebertragen.
|
||||||
|
assert admin.post("/api/uebertraege/verbinden",
|
||||||
|
json={"a_id": raus["id"], "b_id": rein["id"]}).status_code == 200
|
||||||
|
|
||||||
|
ergebnis = admin.post("/api/categories/keywords/anwenden",
|
||||||
|
params={"vorschau": False}).json()
|
||||||
|
texte = [z["text"] for z in ergebnis["uebersprungen"]]
|
||||||
|
assert kennung.upper() not in texte, "verknuepfte Buchung gehoert nicht in die Liste"
|
||||||
|
|
||||||
|
# Und sie bleibt unangetastet: kein Uebertrag, kein verschobenes Geld.
|
||||||
|
assert admin.get(f"/api/transactions/{raus['id']}").json()["type"] == "expense"
|
||||||
|
assert admin.get(f"/api/accounts/{pocket['id']}").json()["balance_cents"] == betrag
|
||||||
|
|||||||
Reference in New Issue
Block a user