fix: Konto umbenennen scheitert nicht mehr an Identifikator-UNIQUE
_set_identifiers glich die Liste per clear()+neu-Anlegen ab; SQLAlchemy fügte dabei die (unveränderten) Identifikatoren vor dem Löschen ein und verletzte so UNIQUE(account_id, kind, value_norm) -> Internal Server Error beim Speichern. Jetzt als Diff: unveränderte behalten, nur fehlende hinzufügen / überzählige entfernen. Betrifft alle Identifikator-Arten (IBAN/Nummer/Text). Regressionstest: Umbenennen mit unverändertem Identifikator + Entfernen (76 grün). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -50,18 +50,31 @@ def to_out(db: Session, acc: Account, base: str, on: date) -> AccountOut:
|
|||||||
|
|
||||||
|
|
||||||
def _set_identifiers(acc: Account, items) -> None:
|
def _set_identifiers(acc: Account, items) -> None:
|
||||||
"""Ersetzt die Identifikator-Liste (dedupliziert nach Art + Normalform)."""
|
"""Gleicht die Identifikator-Liste auf den Ziel-Zustand ab (dedupliziert nach
|
||||||
acc.identifiers.clear()
|
Art + Normalform). Bewusst als Diff (unveränderte behalten, nur fehlende
|
||||||
seen: set[tuple[str, str]] = set()
|
hinzufügen / überzählige entfernen): ein einfaches clear()+neu-Anlegen würde
|
||||||
|
beim Speichern zuerst einfügen und dann löschen und so die UNIQUE-Bedingung
|
||||||
|
(account_id, kind, value_norm) verletzen (Fehler beim Umbenennen)."""
|
||||||
|
desired: dict[tuple[str, str], tuple] = {}
|
||||||
for it in items:
|
for it in items:
|
||||||
norm = normalize_identifier(it.value, it.kind)
|
norm = normalize_identifier(it.value, it.kind)
|
||||||
key = (it.kind.value, norm)
|
if not norm:
|
||||||
if not norm or key in seen:
|
|
||||||
continue
|
continue
|
||||||
seen.add(key)
|
key = (it.kind.value, norm)
|
||||||
acc.identifiers.append(
|
desired.setdefault(key, (it.kind, it.value.strip(), norm))
|
||||||
AccountIdentifier(kind=it.kind, value=it.value.strip(), value_norm=norm)
|
|
||||||
)
|
keep: set[tuple[str, str]] = set()
|
||||||
|
for existing in list(acc.identifiers):
|
||||||
|
key = (existing.kind.value, existing.value_norm)
|
||||||
|
if key in desired:
|
||||||
|
keep.add(key)
|
||||||
|
existing.value = desired[key][1] # Original-Schreibweise ggf. angleichen
|
||||||
|
else:
|
||||||
|
acc.identifiers.remove(existing)
|
||||||
|
|
||||||
|
for key, (kind, value, norm) in desired.items():
|
||||||
|
if key not in keep:
|
||||||
|
acc.identifiers.append(AccountIdentifier(kind=kind, value=value, value_norm=norm))
|
||||||
|
|
||||||
|
|
||||||
@router.get("", response_model=list[AccountOut])
|
@router.get("", response_model=list[AccountOut])
|
||||||
|
|||||||
@@ -53,6 +53,26 @@ def test_identifiers_stored_and_deduped(admin):
|
|||||||
assert ("card_no", "1234") in kinds
|
assert ("card_no", "1234") in kinds
|
||||||
|
|
||||||
|
|
||||||
|
def test_update_account_keeps_identifier(admin):
|
||||||
|
"""Umbenennen mit unverändertem Identifikator darf nicht an der UNIQUE-
|
||||||
|
Bedingung scheitern (Regression: früheres clear()+neu-Anlegen)."""
|
||||||
|
acc = _mk_account(admin, name="Revolut CHF",
|
||||||
|
identifiers=[{"kind": "text", "value": "Revolut**5064*"}])
|
||||||
|
r = admin.patch(f"/api/accounts/{acc['id']}", json={
|
||||||
|
"name": "Revolut CHF neu",
|
||||||
|
"identifiers": [{"kind": "text", "value": "Revolut**5064*"}],
|
||||||
|
})
|
||||||
|
assert r.status_code == 200, r.text
|
||||||
|
assert r.json()["name"] == "Revolut CHF neu"
|
||||||
|
idents = r.json()["identifiers"]
|
||||||
|
assert len(idents) == 1 and idents[0]["value"] == "Revolut**5064*"
|
||||||
|
|
||||||
|
# Identifikator entfernen klappt ebenfalls.
|
||||||
|
r2 = admin.patch(f"/api/accounts/{acc['id']}", json={"name": "Ohne", "identifiers": []})
|
||||||
|
assert r2.status_code == 200, r2.text
|
||||||
|
assert r2.json()["identifiers"] == []
|
||||||
|
|
||||||
|
|
||||||
def test_balance_and_cross_currency_transfer(admin):
|
def test_balance_and_cross_currency_transfer(admin):
|
||||||
_seed_recent_rates("0.95")
|
_seed_recent_rates("0.95")
|
||||||
d = date.today()
|
d = date.today()
|
||||||
|
|||||||
Reference in New Issue
Block a user