feat: Ueberträge als Verknuepfung erkennen, ohne Buchungen anzufassen
Ein Uebertrag steht in zwei Auszuegen und wird zweimal gebucht – richtig fuer die Salden, aber er zaehlt als Ausgabe UND als Einnahme in der Auswertung. Statt beide zu einer Uebertrags-Buchung zusammenzufuehren (der urspruengliche Plan) legt Certain nur eine Schicht darueber: transfer_links haelt fest, dass zwei Buchungen zusammengehoeren. An den Buchungen selbst aendert sich nichts – Betrag, Konto, Kategorie, Import-Schluessel und Stapel bleiben. Das haelt alles heraus, was am Zusammenfuehren haette brechen koennen: der Duplikatschutz verliert keinen Schluessel, undo_batch reisst keine Gegenseite mit, und eine falsche Paarung ist ein Etikett, das man abzieht. Die Auswertung ueberspringt verknuepfte Buchungen – nicht umkategorisiert: Loest man die Verknuepfung, zaehlt die Buchung mit ihrer alten Kategorie sofort wieder mit. Automatisch nur, wenn die Zuordnung zwingend ist UND keine Seite eine Kategorie traegt UND der Text auf einen Uebertrag hindeutet. Sonst Vorschlag. Waehrungswechsel werden ueber den EZB-Kurs des Tages geprueft (2,5 % Toleranz fuer den Bankaufschlag) – der Kurs dient nur als Pruefung, gespeichert wird nichts davon. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
"""Verknuepfung zweier Buchungen zu einem Uebertrag.
|
||||
|
||||
Eine Schicht obendrauf: An den Buchungen selbst wird nichts geaendert. Sie
|
||||
behalten Betrag, Konto, Kategorie, Import-Schluessel und Stapel-Zugehoerigkeit;
|
||||
hier steht nur, dass sie zusammengehoeren.
|
||||
|
||||
Der frueher geplante Weg – beide zu einer Uebertrags-Buchung zusammenfuehren und
|
||||
die Originale loeschen – haette den Duplikatschutz, die Import-Ruecknahme und die
|
||||
Umkehrbarkeit gleichzeitig beschaedigt. Nichts zu loeschen laesst das gar nicht
|
||||
erst entstehen.
|
||||
|
||||
Revision ID: 0020
|
||||
Revises: 0019
|
||||
"""
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
revision = "0020"
|
||||
down_revision = "0019"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.create_table(
|
||||
"transfer_links",
|
||||
sa.Column("id", sa.Integer(), primary_key=True),
|
||||
sa.Column("owner_id", sa.Integer(), nullable=False),
|
||||
sa.Column("from_txn_id", sa.Integer(), nullable=False),
|
||||
sa.Column("to_txn_id", sa.Integer(), nullable=False),
|
||||
sa.Column("automatisch", sa.Boolean(), nullable=False, server_default=sa.false()),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), nullable=True),
|
||||
sa.ForeignKeyConstraint(["owner_id"], ["users.id"], ondelete="CASCADE"),
|
||||
sa.ForeignKeyConstraint(["from_txn_id"], ["transactions.id"], ondelete="CASCADE"),
|
||||
sa.ForeignKeyConstraint(["to_txn_id"], ["transactions.id"], ondelete="CASCADE"),
|
||||
# Je Buchung hoechstens eine Verknuepfung.
|
||||
sa.UniqueConstraint("from_txn_id", name="uq_link_from"),
|
||||
sa.UniqueConstraint("to_txn_id", name="uq_link_to"),
|
||||
)
|
||||
op.create_index("ix_transfer_links_owner_id", "transfer_links", ["owner_id"])
|
||||
op.create_index("ix_transfer_links_from", "transfer_links", ["from_txn_id"])
|
||||
op.create_index("ix_transfer_links_to", "transfer_links", ["to_txn_id"])
|
||||
|
||||
op.create_table(
|
||||
"link_ignored",
|
||||
sa.Column("id", sa.Integer(), primary_key=True),
|
||||
sa.Column("owner_id", sa.Integer(), nullable=False),
|
||||
sa.Column("a_txn_id", sa.Integer(), nullable=False),
|
||||
sa.Column("b_txn_id", sa.Integer(), nullable=False),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), nullable=True),
|
||||
sa.ForeignKeyConstraint(["owner_id"], ["users.id"], ondelete="CASCADE"),
|
||||
sa.ForeignKeyConstraint(["a_txn_id"], ["transactions.id"], ondelete="CASCADE"),
|
||||
sa.ForeignKeyConstraint(["b_txn_id"], ["transactions.id"], ondelete="CASCADE"),
|
||||
sa.UniqueConstraint("a_txn_id", "b_txn_id", name="uq_ignored_pair"),
|
||||
)
|
||||
op.create_index("ix_link_ignored_owner_id", "link_ignored", ["owner_id"])
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_table("link_ignored")
|
||||
op.drop_table("transfer_links")
|
||||
Reference in New Issue
Block a user