feat(tag): Workflow-Felder für Telefon-Akquise (is_erledigend, Wiedervorlage)

Tag-Modell um is_erledigend, loest_wiedervorlage_aus und
wiedervorlage_abstand_tage erweitert. Migration V01_12 idempotent
angelegt, Geschäftslogik mit Validierung, Client-Maske mit Controls
und Workflow-Badges. Farb-Default Grau bei fehlender Farbe, parent_id
im onchange direkt in currentTag übernehmen.

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
gg committed 2026-09-20 20:20:52 +02:00
1 parent 1d8201f46a
commit 6f86bc9808
5 files changed
+206 -4

No files matched your search

+9
View File
@@ -325,6 +325,15 @@ class Tag(dbapp.Model):
color = Column(String(20), nullable=True)
parent_id = Column(Integer, ForeignKey('tags.id'), nullable=True)
is_parent = Column(Boolean, nullable=False, default=False)
# Felder für die Telefon-Akquise-Workflow-Logik:
# is_erledigend: True = Kontakt gilt als erledigt, wenn dieser Tag
# zugewiesen wird.
# loest_wiedervorlage_aus: True = Tag löst eine Wiedervorlage aus.
# wiedervorlage_abstand_tage: Tage bis zur Wiedervorlage (nur relevant,
# wenn loest_wiedervorlage_aus True ist).
is_erledigend = Column(Boolean, nullable=False, default=False)
loest_wiedervorlage_aus = Column(Boolean, nullable=False, default=False)
wiedervorlage_abstand_tage = Column(Integer, nullable=True)
# Selbst-Referenzierende Relationship: parent verweist auf den
# uebergeordneten Tag, children auf alle direkten Kinder. Die
+32
View File
@@ -243,6 +243,38 @@ def _db_V01_11():
session.commit()
def _db_V01_12():
"""
Fügt der Tabelle 'tags' die drei neuen Spalten für die
Telefon-Akquise-Workflow-Logik hinzu:
- is_erledigend (BOOLEAN NOT NULL DEFAULT 0)
- loest_wiedervorlage_aus (BOOLEAN NOT NULL DEFAULT 0)
- wiedervorlage_abstand_tage (INTEGER)
Idempotent: Bei Neuinstallation hat create_all die Spalten bereits
angelegt — in diesem Fall wird das jeweilige ALTER TABLE übersprungen.
"""
from sqlalchemy import text
result = session.execute(text("PRAGMA table_info(tags)"))
spalten = {row[1] for row in result}
if 'is_erledigend' not in spalten:
session.execute(text(
"ALTER TABLE tags ADD COLUMN is_erledigend BOOLEAN NOT NULL DEFAULT 0"
))
if 'loest_wiedervorlage_aus' not in spalten:
session.execute(text(
"ALTER TABLE tags ADD COLUMN loest_wiedervorlage_aus BOOLEAN NOT NULL DEFAULT 0"
))
if 'wiedervorlage_abstand_tage' not in spalten:
session.execute(text(
"ALTER TABLE tags ADD COLUMN wiedervorlage_abstand_tage INTEGER"
))
_updatesysteminfo('01.12')
session.commit()
def checkupdates():
"""
Ermittelt die aktuelle DB-Version aus der Systeminfo-Tabelle und führt alle
+44
View File
@@ -20,6 +20,48 @@ from auth.model.errors import ApplicationError, HtmlStatusCode
session = dbapp.session
def _apply_workflow_felder(tag: Tag, data: dict) -> None:
"""
Übernimmt die Wiedervorlage- und Erledigt-Felder aus dem data-Dict in
den Tag. Für Parent-Tags (is_parent=True) werden die Felder auf False
bzw. None gesetzt, da sie dort keine Wirkung haben (Parent-Tags sind
nicht zuordenbar).
Validierung: Ist loest_wiedervorlage_aus True, muss wiedervorlage_abstand_tage
einen gültigen positiven Integer-Wert haben; sonst wird ein
ApplicationError (Bad_Request) geworfen.
"""
is_parent = bool(data.get("is_parent", tag.is_parent))
if is_parent:
# Parent-Tags: Workflow-Felder sind nicht relevant.
tag.is_erledigend = False
tag.loest_wiedervorlage_aus = False
tag.wiedervorlage_abstand_tage = None
return
tag.is_erledigend = bool(data.get("is_erledigend", False))
loest = bool(data.get("loest_wiedervorlage_aus", False))
tag.loest_wiedervorlage_aus = loest
abstand_raw = data.get("wiedervorlage_abstand_tage")
abstand = None
if abstand_raw is not None and str(abstand_raw).strip() != "":
try:
abstand = int(abstand_raw)
except (TypeError, ValueError):
abstand = None
if loest:
if abstand is None or abstand < 0:
raise ApplicationError(
"Wiedervorlage-Abstand in Tagen ist erforderlich, wenn "
"Wiedervorlage ausgelöst wird.",
htmlstatus=HtmlStatusCode.Bad_Request,
)
tag.wiedervorlage_abstand_tage = abstand
else:
tag.wiedervorlage_abstand_tage = None
def get_tags(parent_id: int | None = None) -> list[Tag]:
"""
Liefert Tags, sortiert nach name (alphabetisch).
@@ -76,6 +118,7 @@ def create_tag(data: dict) -> Tag:
tag.color = (data.get("color") or "").strip() or None
tag.parent_id = parent_id
tag.is_parent = bool(data.get("is_parent", False))
_apply_workflow_felder(tag, data)
session.add(tag)
session.commit()
return tag
@@ -124,6 +167,7 @@ def update_tag(data: dict) -> Tag:
tag.color = (data.get("color") or "").strip() or None
tag.parent_id = parent_id
tag.is_parent = bool(data.get("is_parent", False))
_apply_workflow_felder(tag, data)
session.commit()
return tag