fix: Startdatum lag in der Zukunft – Saldi kamen, Umsaetze nicht

Beim ersten Abruf beginnt Certain am Tag NACH der neuesten vorhandenen Buchung
des Kontos, damit es nicht in schon eingelesene Monate zurueckgreift. Liegt
diese Buchung auf heute, ist der Tag danach morgen – und die Schnittstelle
lehnt das rundweg ab: "date_from can not be in the future".

Der Fehler faellt erst beim Umsatz-Abruf an. Die Saldi sind da schon gelesen
und geschrieben, der Abruf bricht danach ab. Von aussen sieht es genau so aus,
wie der Nutzer es beschrieben hat: Der Kontostand aktualisiert sich, die
Buchungen kommen nie – und die Abweichung bleibt bestehen, obwohl man den
Saldo gerade erst von der Bank uebernommen hat. Genau dieses Uebernehmen legt
naemlich keine Buchung an, aber es gibt fast immer eine von heute.

Das Startdatum wird jetzt auf heute begrenzt. Gegenprobe: Ohne die Begrenzung
verlangt der Abruf den 28.08. an einem 27.08.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Guido
2026-08-27 20:48:03 +02:00
co-authored by Claude Opus 5
parent b4c90f5221
commit 12ea391e9f
2 changed files with 42 additions and 4 deletions
+10 -4
View File
@@ -501,13 +501,14 @@ def _startdatum(db: Session, user: User, konto: Account,
ein Einzelabruf das Startdatum der uebrigen Konten mit vor, und deren ein Einzelabruf das Startdatum der uebrigen Konten mit vor, und deren
Umsaetze aus dem uebersprungenen Zeitraum kaemen nie an. Umsaetze aus dem uebersprungenen Zeitraum kaemen nie an.
""" """
heute = date.today()
if tage is not None: if tage is not None:
# Uebergeht den letzten Abruf. Noetig, wenn etwas nicht angekommen ist: # Uebergeht den letzten Abruf. Noetig, wenn etwas nicht angekommen ist:
# Sonst laege es fuer immer vor dem Fenster, obwohl die Bank es haette. # Sonst laege es fuer immer vor dem Fenster, obwohl die Bank es haette.
return date.today() - timedelta(days=tage) return heute - timedelta(days=tage)
if verknuepfung.last_sync_at: if verknuepfung.last_sync_at:
return verknuepfung.last_sync_at.date() - timedelta(days=3) return min(heute, verknuepfung.last_sync_at.date() - timedelta(days=3))
frueheste = date.today() - timedelta(days=ERSTER_ABRUF_TAGE) frueheste = heute - timedelta(days=ERSTER_ABRUF_TAGE)
letzte = ( letzte = (
db.query(func.max(Transaction.date)) db.query(func.max(Transaction.date))
.filter(Transaction.owner_id == user.id, Transaction.account_id == konto.id) .filter(Transaction.owner_id == user.id, Transaction.account_id == konto.id)
@@ -515,7 +516,12 @@ def _startdatum(db: Session, user: User, konto: Account,
) )
if letzte is None: if letzte is None:
return frueheste return frueheste
return max(frueheste, letzte + timedelta(days=1)) # NIE in die Zukunft: Die Schnittstelle lehnt das rundweg ab
# ("date_from can not be in the future") – und zwar erst beim Umsatz-Abruf,
# nachdem die Saldi schon gelesen sind. Dann sieht es aus, als kaemen nur
# Saldi und keine Umsaetze. Liegt die neueste Buchung auf heute, waere der
# Tag danach morgen.
return min(heute, max(frueheste, letzte + timedelta(days=1)))
def hole_umsaetze(db: Session, user: User, verbindung: BankConnection, def hole_umsaetze(db: Session, user: User, verbindung: BankConnection,
+32
View File
@@ -1053,3 +1053,35 @@ def test_verschluckter_umsatz_laesst_sich_nachholen(admin, fake_bank):
assert admin.post(f"/api/bank/links/{link['id']}/sync", assert admin.post(f"/api/bank/links/{link['id']}/sync",
params={"tage": 400}).status_code == 400 params={"tage": 400}).status_code == 400
def test_startdatum_liegt_nie_in_der_zukunft(admin, fake_bank):
"""Liegt die neueste Buchung auf heute, waere der Tag danach morgen – und
die Schnittstelle lehnt das ab ("date_from can not be in the future").
Der Fehler faellt erst beim Umsatz-Abruf an, nachdem die Saldi schon
gelesen sind: Es sieht dann aus, als kaemen nur Saldi und keine Umsaetze.
"""
_app(admin)
heute = date.today()
uid = f"acc-{secrets.token_hex(4)}"
gegenstelle = fake_bank(
konten=[{"uid": uid, "currency": "EUR"}],
umsaetze=[dict(UMSATZ, entry_reference=f"tx-{secrets.token_hex(4)}",
booking_date=heute.isoformat())])
state = admin.post("/api/bank/connect",
json={"aspsp_name": f"Zukunft-{secrets.token_hex(3)}",
"country": "DE"}).json()["state"]
admin.post(f"/api/bank/callback?state={state}&code=guter-code")
link = [v for v in admin.get("/api/bank/links").json() if v["remote_uid"] == uid][0]
konto = _konto(admin, name=f"Zukunft {secrets.token_hex(3)}", currency="EUR")
# Eine Buchung von HEUTE – der Tag danach ist morgen.
admin.post("/api/transactions", json={
"type": "expense", "date": heute.isoformat(), "account_id": konto["id"],
"amount_cents": 500, "payee": "von heute"})
admin.post(f"/api/bank/links/{link['id']}/account/{konto['id']}")
r = admin.post(f"/api/bank/links/{link['id']}/sync")
assert r.status_code == 200, r.text
assert gegenstelle.date_from <= heute.isoformat(), \
f"date_from liegt in der Zukunft: {gegenstelle.date_from}"
assert r.json()["ab"] == heute.isoformat()