feat(tags): Backend mit REST-API und Client-Anbindung fuer hierarchische Tags

- SQLAlchemy-Modell Tag mit Selbst-Referenz (parent_id), is_parent-Flag
- DB-Migration V01.10 (idempotent via create_all)
- Geschaeftslogik tag.py: CRUD, Hierarchie-Aufbau, kaskadierendes Loeschen,
  Zirkelbezug-Pruefung
- REST-Endpunkte /xhr/tag/*: Lesen fuer Mitarbeiter, Schreiben fuer AppAdmin
- Client-Modell tag.ts mit REST-Funktionen
- Frontend tags.ts auf REST-API umgestellt, hartcodierte Daten entfernt,
  Loesch-Dialog mit kaskadierender Bestaetigung

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
gg committed 2026-09-19 14:08:34 +02:00
1 parent 675d7775c1
commit 5abcb2d96c
6 files changed
+600 -648

No files matched your search

+61
View File
@@ -27,6 +27,7 @@ from crm.model import projekt as projekte
from crm.model import projekt_user as projekt_users
from crm.model import kontaktadresse as kontaktadressen
from crm.model import call_log as call_logs
from crm.model import tag as tags
print("crm-routes")
@@ -380,4 +381,64 @@ def calllog_delete():
return jsonify({}), 200
# --------------------------------------------------------------------------- #
# Tag (hierarchische Taxonomie) #
# --------------------------------------------------------------------------- #
# Tags sind nicht projektbezogen — die projektbezogene Sichtbarkeits-
# Filterung entfaellt. Lesen: alle Mitarbeiter. Schreiben: nur AppAdmin/SysAdmin.
# --------------------------------------------------------------------------- #
@app.get("/xhr/tag/list")
@authorisation_required([MITARBEITER, KUNDEN_MANAGER])
def tag_list():
"""Liefert alle Tags als flache Liste. Optional nach parent_id gefiltert."""
parent_id = request.args.get("parent_id", type=int)
result = [t.to_dict() for t in tags.get_tags(parent_id)]
return jsonify(result), 200
@app.get("/xhr/tag/hierarchy")
@authorisation_required([MITARBEITER, KUNDEN_MANAGER])
def tag_hierarchy():
"""Liefert die vollstaendige Tag-Hierarchie als verschachtelte Struktur."""
result = tags.get_tag_hierarchy()
return jsonify(result), 200
@app.get("/xhr/tag/get")
@authorisation_required([MITARBEITER, KUNDEN_MANAGER])
def tag_get():
"""Liefert einen einzelnen Tag anhand seiner ID (Query-Param id)."""
tag = tags.get_tag(int(request.args.get("id")))
return jsonify(tag.to_dict()), 200
@app.post("/xhr/tag/create")
# Leeres allowed_roles-Array: Nur AppAdmin/SysAdmin (vom Decorator
# implizit ergaenzt). Tags sind eine globale Taxonomie — die Anlage
# und Aenderung ist ausschliesslich den Administratoren vorbehalten.
@authorisation_required([])
def tag_create():
"""Legt einen neuen Tag an. ID wird serverseitig vergeben."""
tag = tags.create_tag(request.json)
return jsonify(tag.to_dict()), 200
@app.put("/xhr/tag/update")
@authorisation_required([])
def tag_update():
"""Aktualisiert einen bestehenden Tag."""
tag = tags.update_tag(request.json)
return jsonify(tag.to_dict()), 200
@app.delete("/xhr/tag/delete")
@authorisation_required([])
def tag_delete():
"""Loescht einen Tag inkl. aller Nachfahren (kaskadierend) anhand seiner ID."""
tags.delete_tag(int(request.args.get("id")))
return jsonify({}), 200
print("########## crm.routes importiert")