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
+34
View File
@@ -346,6 +346,28 @@ class SubscriptionListOut(BaseModel):
summary: SubscriptionSummary
# --- Tags ------------------------------------------------------------------
class TagOut(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: int
name: str
color: str
class TagCreate(BaseModel):
name: str = Field(min_length=1, max_length=60)
# Ohne Angabe dasselbe Grau wie bei einer neuen Kategorie.
color: str = "#6b7480"
class TagUpdate(BaseModel):
name: str | None = Field(default=None, min_length=1, max_length=60)
color: str | None = None
# --- Kategorien ------------------------------------------------------------
@@ -433,6 +455,9 @@ class TransactionCreate(BaseModel):
payee: str | None = Field(default=None, max_length=255)
description: str | None = Field(default=None, max_length=255)
notes: str | None = None
# Beim Anlegen gleich mitgeben, sonst muesste man die Buchung direkt
# danach noch einmal oeffnen, nur um sie zu etikettieren.
tag_ids: list[int] = []
class TransactionUpdate(BaseModel):
@@ -452,6 +477,10 @@ class TransactionUpdate(BaseModel):
payee: str | None = Field(default=None, max_length=255)
description: str | None = Field(default=None, max_length=255)
notes: str | None = None
# Die Tags dieser Buchung, vollstaendig. Mitgeschickt heisst ERSETZEN –
# eine leere Liste nimmt alle weg. Weggelassen laesst sie unberuehrt;
# darum haengt hier ``exclude_unset`` im Router dran.
tag_ids: list[int] | None = None
class TransactionOut(BaseModel):
@@ -486,6 +515,11 @@ class TransactionOut(BaseModel):
payee: str | None
description: str | None
notes: str | None
# Die Tags dieser Buchung. Anders als die Uebertrags-Verknuepfung stehen
# sie IM Schema und nicht in einem eigenen Endpunkt: Jede Zeile der
# Buchungsliste zeigt und filtert nach ihnen, ein zweiter Abruf wuerde im
# Browser doch nur wieder zusammengefuegt.
tag_ids: list[int] = []
class RateStatus(BaseModel):