Files
certain/backend/migrations/versions/0027_buchung_quellkennung.py
T
ScarriffleandClaude Opus 5 991a8e4fc2 fix: Die Kennung der Bank steht jetzt an der Buchung selbst
Bisher lag sie nur am Eingangs-Eintrag. Das reicht nicht: Der Eintrag ist eine
Zwischenstation und faellt weg, wenn ein Konto geloescht wird oder ein aelteres
Backup ihn nicht kannte. Dann war die Zahlung fuer den naechsten Abruf neu,
obwohl sie laengst gebucht war.

Am schlimmsten bei einem als Uebertrag gebuchten Umsatz: Der ist von der
Aehnlichkeitspruefung ausgenommen, dort griff also gar nichts mehr.

Die Kennung gehoert deshalb an die Buchung (transactions.source_uid, mit der
Quelle davor). Dort ueberlebt sie alles, was mit dem Eingang passiert, und die
Eindeutigkeit haelt die Datenbank selbst durch – nicht erst der pruefende Code
davor. Gestempelt wird an allen drei Buchungswegen: Eingang, Kurzbefehl und
Uebertrag.

Vorhandene Buchungen bekommen die Kennung nachgetragen, solange ihr
Eingangs-Eintrag noch da ist.

Ohne Kennung – von Hand erfasst, aus einer CSV-Datei – bleibt es beim
bisherigen Schutz ueber die Aehnlichkeit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-07 11:59:43 +02:00

71 lines
2.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Die Kennung der Quelle steht an der Buchung selbst.
Bisher lag sie nur am Eingangs-Eintrag (``inbox_items.external_id``). Das hat
gereicht, solange dieser Eintrag da war – und genau das ist er nicht immer: Er
faellt weg, wenn ein Konto geloescht wird, er fehlt in aelteren Backups, und
nach einem neu verbundenen Bankkonto passt sein Praefix nicht mehr. Dann war
die Zahlung fuer den naechsten Abruf neu, obwohl sie laengst gebucht war.
Die Kennung gehoert deshalb an die Buchung. Dort ueberlebt sie alles, was mit
dem Eingang passiert, und die Eindeutigkeit haelt die Datenbank selbst durch –
nicht erst der pruefende Code davor.
Vorhandene Buchungen bekommen die Kennung nachgetragen, solange ihr
Eingangs-Eintrag noch existiert. Was jetzt schon verloren ist, bleibt verloren;
ab hier faellt nichts Neues mehr aus.
Revision ID: 0027
Revises: 0026
"""
import sqlalchemy as sa
from alembic import op
revision = "0027"
down_revision = "0026"
branch_labels = None
depends_on = None
def upgrade() -> None:
with op.batch_alter_table("transactions") as batch:
batch.add_column(sa.Column("source_uid", sa.String(length=320), nullable=True))
# Nachtragen, was der Eingang noch weiss. Der Name der Quelle kommt davor:
# Eine Nachrichten-Kennung aus dem Postfach und eine Umsatzkennung der Bank
# duerfen nie zufaellig gleich lauten.
#
# Zwei Eingangs-Eintraege koennen auf dieselbe Buchung zeigen (die beiden
# Seiten eines Uebertrags). Dann gewinnt der aeltere – ``MIN(id)``. Zwei
# verschiedene Buchungen koennen dagegen nicht dieselbe Kennung bekommen:
# (owner_id, source, external_id) ist im Eingang bereits eindeutig.
op.execute(sa.text("""
UPDATE transactions SET source_uid = (
SELECT i.source || ':' || i.external_id
FROM inbox_items i
WHERE i.transaction_id = transactions.id
AND i.owner_id = transactions.owner_id
AND i.external_id IS NOT NULL
ORDER BY i.id
LIMIT 1
)
WHERE source_uid IS NULL
"""))
# Erst jetzt eindeutig stellen – vorher stuende die Regel ueber leeren
# Spalten und der Nachtrag koennte sie nicht mehr fuellen.
#
# Ein INDEX, keine UNIQUE-Spaltenregel: Letztere ginge nur ueber einen
# Tabellen-Umbau, und der loescht auf SQLite per ON DELETE CASCADE die
# Uebertrags-Verknuepfungen und abgelehnten Vorschlaege mit. Genau davor
# steht tests/test_migrations.py. Der Index haelt dasselbe durch, ohne die
# Tabelle anzufassen.
op.create_index("uq_txn_owner_source_uid", "transactions",
["owner_id", "source_uid"], unique=True)
def downgrade() -> None:
op.drop_index("uq_txn_owner_source_uid", table_name="transactions")
with op.batch_alter_table("transactions") as batch:
batch.drop_column("source_uid")