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

+34
View File
@@ -276,4 +276,38 @@ class CallLog(dbapp.Model):
return result
class Tag(dbapp.Model):
"""
Hierarchischer Tag (Taxonomie). Tags koennen beliebig verschachtelt
werden ueber die Selbst-Referenz parent_id -> tags.id. Ein Tag mit
is_parent=True ist ein reiner Gruppierungsknoten (nicht zuordenbar);
is_parent=False kennzeichnet einen echten, zuordenbaren Tag.
"""
__tablename__ = 'tags'
__bind_key__ = BINDKEY
__table_args__ = (
Index('ix_tags_parent_id', 'parent_id'),
)
id = Column(Integer, primary_key=True)
name = Column(String(200), nullable=False)
description = Column(String(2000), nullable=True)
color = Column(String(20), nullable=True)
parent_id = Column(Integer, ForeignKey('tags.id'), nullable=True)
is_parent = Column(Boolean, nullable=False, default=False)
# Selbst-Referenzierende Relationship: parent verweist auf den
# uebergeordneten Tag, children auf alle direkten Kinder. Die
# Sortierung der Kinder erfolgt alphabetisch nach name.
parent = relationship(
'Tag', remote_side='Tag.id', back_populates='children'
)
children = relationship(
'Tag', back_populates='parent', order_by='Tag.name'
)
def to_dict(self):
"""Liefert die Tag-Daten als Dictionary (fuer JSON-Serialisierung)."""
return columns2dict(self)
print("########## crm/model importiert")