feat: Erneut verbinden statt loeschen und neu anlegen

Um eine Verbindung aufzufrischen – etwa nachdem man im Control Panel weitere
Konten verknuepft hat – haette man sie bisher erst loeschen muessen. Dabei
gehen die Kontozuordnungen mit verloren, also genau das, was man behalten
will; ein zweiter Verbindungsversuch haette sonst einen doppelten Eintrag
erzeugt.

Jetzt frischt das Verbinden derselben Bank den bestehenden Eintrag auf: neue
Sitzung, neue Konten, bestehende Zuordnungen bleiben. Der Warntext beim
Trennen sagt ausserdem, dass Trennen dafuer gar nicht noetig ist.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Guido
2026-08-26 22:35:52 +02:00
co-authored by Claude Opus 5
parent 277fe8b0e2
commit b4f76c03d2
4 changed files with 56 additions and 7 deletions
+17 -4
View File
@@ -146,11 +146,24 @@ def starte_verbindung(db: Session, user: User, aspsp_name: str, land: str,
raise BankFehler("Es ist noch keine Bank-Anwendung hinterlegt.")
state = secrets.token_urlsafe(24)
verbindung = BankConnection(
owner_id=user.id, aspsp_name=aspsp_name, aspsp_country=land.upper(),
pending_state=state,
# Dieselbe Bank erneut verbinden frischt den bestehenden Eintrag auf, statt
# einen zweiten anzulegen. Sonst müsste man vorher löschen – und dabei
# gingen die Kontozuordnungen mit verloren, obwohl man sie behalten will.
verbindung = (
db.query(BankConnection)
.filter(BankConnection.owner_id == user.id,
BankConnection.aspsp_name == aspsp_name,
BankConnection.aspsp_country == land.upper())
.order_by(BankConnection.id.desc())
.first()
)
db.add(verbindung)
if verbindung is None:
verbindung = BankConnection(
owner_id=user.id, aspsp_name=aspsp_name, aspsp_country=land.upper(),
)
db.add(verbindung)
verbindung.pending_state = state
verbindung.last_error = None
db.flush()
antwort = _client(db, user).starte_autorisierung(
+37 -1
View File
@@ -250,8 +250,12 @@ def _verbunden(admin, fake_bank_fn, **kw):
"currency": "EUR", "account_id": {"iban": "DE02120300000000202051"},
}])
gegenstelle = fake_bank_fn(**kw)
# Eigener Bankname je Aufruf: Erneutes Verbinden derselben Bank frischt
# bewusst den bestehenden Eintrag auf, sonst erbte jeder Test die
# Verbindung des vorigen.
bank = f"Testbank-{secrets.token_hex(3)}"
state = admin.post("/api/bank/connect",
json={"aspsp_name": "N26", "country": "DE"}).json()["state"]
json={"aspsp_name": bank, "country": "DE"}).json()["state"]
antwort = admin.post(f"/api/bank/callback?state={state}&code=guter-code").json()
link = [v for v in admin.get("/api/bank/links").json()
if v["connection_id"] == antwort["connection_id"]][0]
@@ -519,3 +523,35 @@ def test_konten_ohne_kennung_werden_gemeldet_statt_verworfen(admin, fake_bank):
assert antwort["uebermittelt"] == 1
assert antwort["ohne_kennung"] == 1
assert antwort["konten"] == 0
def test_erneutes_verbinden_behaelt_die_kontozuordnung(admin, fake_bank):
"""Ohne das müsste man vor dem Neuverbinden löschen – und verlöre dabei
die Zuordnungen, die man gerade behalten will."""
_app(admin)
uid = f"acc-{secrets.token_hex(4)}"
konten = [{"uid": uid, "name": "Girokonto", "currency": "EUR",
"account_id": {"iban": "DE02120300000000202051"}}]
fake_bank(konten=konten)
def verbinde():
state = admin.post("/api/bank/connect",
json={"aspsp_name": "Testbank", "country": "DE"}).json()["state"]
return admin.post(f"/api/bank/callback?state={state}&code=guter-code").json()
erste = verbinde()
link = [v for v in admin.get("/api/bank/links").json() if v["remote_uid"] == uid][0]
konto = _konto(admin, name="Zuordnung bleibt", currency="EUR")
admin.post(f"/api/bank/links/{link['id']}/account/{konto['id']}")
zweite = verbinde()
assert zweite["connection_id"] == erste["connection_id"], "kein zweiter Eintrag"
danach = [v for v in admin.get("/api/bank/links").json() if v["remote_uid"] == uid]
assert len(danach) == 1
assert danach[0]["account_id"] == konto["id"], "die Zuordnung muss bleiben"
# Und es steht genau eine Verbindung zu dieser Bank da.
passende = [c for c in admin.get("/api/bank/connections").json()
if c["aspsp_name"] == "Testbank"]
assert len(passende) == 1
+1 -1
View File
@@ -597,7 +597,7 @@
"validUntil": "Zustimmung gültig bis {{datum}}",
"expiresSoon": "Zustimmung läuft am {{datum}} ab – danach musst du neu autorisieren.",
"disconnect": "Verbindung trennen",
"disconnectConfirm": "Verbindung zu „{{name}}“ trennen? Bereits abgerufene Buchungen bleiben.",
"disconnectConfirm": "Verbindung zu „{{name}}“ trennen? Dabei gehen die Kontozuordnungen verloren. Zum Auffrischen musst du NICHT trennen – such die Bank einfach erneut und verbinde nochmal, die Zuordnungen bleiben dann.",
"search": "Bank suchen",
"searchPlaceholder": "z. B. revolut oder n26",
"searchHint": "Ohne Land wird überall gesucht. Das ist meist richtig: Das Sitzland einer Bank steht nicht in der IBAN – Revolut vergibt deutsche IBANs, sitzt aber in Litauen.",
+1 -1
View File
@@ -597,7 +597,7 @@
"validUntil": "Consent valid until {{datum}}",
"expiresSoon": "Consent expires on {{datum}} – you will need to authorise again.",
"disconnect": "Disconnect",
"disconnectConfirm": "Disconnect from “{{name}}”? Transactions already fetched stay.",
"disconnectConfirm": "Disconnect from “{{name}}”? The account mappings are lost. You do NOT need to disconnect to refresh – just search the bank and connect again, the mappings are kept.",
"search": "Search for a bank",
"searchPlaceholder": "e.g. revolut or n26",
"searchHint": "Without a country it searches everywhere, which is usually right: a bank's home country is not in the IBAN – Revolut issues German IBANs but is based in Lithuania.",