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>
This commit is contained in:
co-authored by
Claude Opus 5
parent
175d9f50e0
commit
991a8e4fc2
@@ -23,6 +23,7 @@ from sqlalchemy import (
|
||||
DateTime,
|
||||
Enum as SAEnum,
|
||||
ForeignKey,
|
||||
Index,
|
||||
Integer,
|
||||
LargeBinary,
|
||||
Numeric,
|
||||
@@ -370,9 +371,44 @@ class Transaction(Base):
|
||||
)
|
||||
import_hash: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
|
||||
|
||||
# Die Kennung, unter der die Quelle diese Zahlung fuehrt – bei der Bank die
|
||||
# des Umsatzes, mit der Quelle davor ("bank:…", "mail:…", "shortcut:…").
|
||||
#
|
||||
# Sie steht bewusst HIER und nicht nur am Eingangs-Eintrag. Der Eintrag ist
|
||||
# eine Zwischenstation: Er kann verschwinden, weil ein Konto neu verbunden
|
||||
# wurde, weil ein aelteres Backup ihn nicht kannte, weil jemand aufgeraeumt
|
||||
# hat. Die Buchung bleibt. Solange die Kennung an der Buchung haengt, kann
|
||||
# dieselbe Zahlung kein zweites Mal hereinkommen – und zwar unabhaengig
|
||||
# davon, ob der pruefende Code davor daran gedacht hat: Die Eindeutigkeit
|
||||
# weiter unten laesst es gar nicht zu.
|
||||
#
|
||||
# Leer bleibt sie bei allem, was keine Kennung mitbringt: von Hand erfasst,
|
||||
# aus einer CSV-Datei. Dort schuetzt weiterhin nur ``import_hash``, und das
|
||||
# ist eine Aehnlichkeit, keine Kennung.
|
||||
#
|
||||
# 320 statt 255: Die Kennung der Quelle darf selbst schon 255 lang sein,
|
||||
# der Name der Quelle davor kostet ein paar Zeichen mehr. Abschneiden waere
|
||||
# hier die falsche Sparsamkeit – zwei abgeschnittene Kennungen saehen
|
||||
# gleich aus, und dann verschluckt eine Zahlung die andere.
|
||||
source_uid: Mapped[str | None] = mapped_column(String(320), nullable=True)
|
||||
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=_now)
|
||||
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=_now, onupdate=_now)
|
||||
|
||||
__table_args__ = (
|
||||
# Ein eindeutiger INDEX, keine UNIQUE-Spaltenregel: Letztere zwingt
|
||||
# SQLite dazu, die Tabelle neu zu bauen – und dabei reisst das
|
||||
# implizite DELETE die abhaengigen Zeilen ueber ON DELETE CASCADE mit
|
||||
# (Uebertrags-Verknuepfungen, abgelehnte Vorschlaege). Siehe
|
||||
# tests/test_migrations.py. Der Index haelt dasselbe durch, ohne die
|
||||
# Tabelle anzufassen.
|
||||
#
|
||||
# NULL zaehlt in SQL nicht als gleich – Buchungen ohne Kennung stehen
|
||||
# sich also nicht gegenseitig im Weg. Genau das ist gewollt: Die Regel
|
||||
# greift nur dort, wo es wirklich eine Kennung gibt.
|
||||
Index("uq_txn_owner_source_uid", "owner_id", "source_uid", unique=True),
|
||||
)
|
||||
|
||||
|
||||
class ExchangeRate(Base):
|
||||
"""EZB-Referenzkurs: wie viele Einheiten der Währung ein Euro wert war.
|
||||
|
||||
Reference in New Issue
Block a user