feat: Buchungen lassen sich mit eigenen, farbigen Tags versehen und danach filtern

Die Kategorie beantwortet "wofuer war das Geld" und gilt genau einmal. Alles,
was quer dazu liegt – "Urlaub Island", "absetzbar", "zurueckfordern" –, hatte
bisher keinen Platz, weil eine Buchung davon mehrere auf einmal sein kann.

Tags werden auf einer eigenen Seite angelegt, jeder mit seiner Farbe. Gesetzt
werden sie an der Buchung: direkt in der Liste oder beim Bearbeiten. Die
Spalte laesst sich filtern, auch nach mehreren Tags gleichzeitig.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-10-19 07:16:17 +02:00
co-authored by Claude Opus 5
parent a9ea0432d8
commit 50fc0e3353
16 changed files with 961 additions and 11 deletions
+58
View File
@@ -1281,3 +1281,61 @@ class BankAccountLink(Base):
except ValueError:
return {}
return daten if isinstance(daten, dict) else {}
class Tag(Base):
"""Ein frei vergebenes Etikett fuer Buchungen, mit eigener Farbe.
Neben der Kategorie, nicht statt ihr: Die Kategorie beantwortet „wofuer war
das Geld", ein Tag alles andere, was sich quer dazu legt – „Urlaub Island",
„steuerlich absetzbar", „zurueckfordern". Deshalb mehrere je Buchung und
keine Pflicht, genau eines zu waehlen.
Die Farbe traegt der Tag selbst, wie bei Kategorie und Gruppe: In einer
langen Liste erkennt man sie am Fleck, bevor man den Text liest.
"""
__tablename__ = "tags"
id: Mapped[int] = mapped_column(Integer, primary_key=True)
owner_id: Mapped[int] = mapped_column(
ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True
)
name: Mapped[str] = mapped_column(String(60), nullable=False)
color: Mapped[str] = mapped_column(String(9), nullable=False, default="#6b7480")
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=_now)
__table_args__ = (
UniqueConstraint("owner_id", "name", name="uq_tag_owner_name"),
)
class TransactionTag(Base):
"""Welcher Tag haengt an welcher Buchung.
Eine eigene Tabelle und kein ``secondary``-Verweis, wie ueberall hier
(``TransferLink``, ``LinkIgnored``): Sie traegt ihre eigene ``owner_id`` und
ist damit vom Backup und von der Mandanten-Pruefung erfasst wie jede andere.
CASCADE auf beiden Seiten. Verschwindet die Buchung oder der Tag, ist die
Zuordnung gegenstandslos – es verschwindet immer nur das Etikett, nie eine
Buchung.
"""
__tablename__ = "transaction_tags"
id: Mapped[int] = mapped_column(Integer, primary_key=True)
owner_id: Mapped[int] = mapped_column(
ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True
)
transaction_id: Mapped[int] = mapped_column(
ForeignKey("transactions.id", ondelete="CASCADE"), nullable=False, index=True
)
tag_id: Mapped[int] = mapped_column(
ForeignKey("tags.id", ondelete="CASCADE"), nullable=False, index=True
)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=_now)
__table_args__ = (
UniqueConstraint("transaction_id", "tag_id", name="uq_txn_tag"),
)