Add MCP tools for Veranstaltung participant management
Some checks failed
CI/CD Pipeline / test (push) Has been cancelled
CI/CD Pipeline / deploy (push) Has been cancelled
Code Quality / quality (push) Has been cancelled

- veranstaltungen_anzeigen: list events with participant counts
- veranstaltung_teilnehmer_anzeigen: list participants by event
- veranstaltung_teilnehmer_anlegen: add single participant
- veranstaltung_teilnehmer_importieren: bulk import via JSON array

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
SysAdmin Agent
2026-03-21 09:15:35 +00:00
parent e0b377014c
commit fdf078fa10
4 changed files with 260 additions and 0 deletions

View File

@@ -569,6 +569,100 @@ def termine_anzeigen(
return format_result({"anzahl": len(results), "termine": results})
# ──────────────────────────────────────────────────────────────────────────────
# Veranstaltungen
# ──────────────────────────────────────────────────────────────────────────────
def veranstaltungen_anzeigen(
status: str = "",
limit: int = 20,
) -> str:
"""
Zeigt Veranstaltungen der Stiftung an.
Args:
status: geplant/einladungen_versendet/abgeschlossen/abgesagt (optional, leer = alle)
limit: Maximale Anzahl (max. 50)
"""
from stiftung.models.veranstaltungen import Veranstaltung
role = _get_role()
limit = min(limit, 50)
qs = Veranstaltung.objects.all()
if status:
qs = qs.filter(status=status)
qs = qs.order_by("-datum")[:limit]
results = []
for v in qs:
results.append({
"id": str(v.id),
"titel": v.titel,
"datum": v.datum.isoformat(),
"uhrzeit": v.uhrzeit.isoformat() if v.uhrzeit else None,
"ort": v.ort,
"status": v.status,
"teilnehmer_gesamt": v.get_teilnehmer_count(),
"zugesagt": v.get_zugesagte_count(),
"abgesagt": v.get_abgesagte_count(),
})
log_mcp_read(role, "veranstaltung", "Veranstaltungsübersicht", f"{len(results)} Veranstaltungen")
return format_result({"anzahl": len(results), "veranstaltungen": results})
def veranstaltung_teilnehmer_anzeigen(
veranstaltung_id: str,
rsvp_status: str = "",
) -> str:
"""
Zeigt die Teilnehmer einer Veranstaltung an.
Args:
veranstaltung_id: UUID der Veranstaltung (Pflichtfeld)
rsvp_status: eingeladen/zugesagt/abgesagt/keine_rueckmeldung (optional, leer = alle)
"""
from stiftung.models.veranstaltungen import Veranstaltung
role = _get_role()
try:
veranstaltung = Veranstaltung.objects.get(id=veranstaltung_id)
except Veranstaltung.DoesNotExist:
return format_result({"fehler": f"Veranstaltung {veranstaltung_id} nicht gefunden"})
qs = veranstaltung.teilnehmer.all()
if rsvp_status:
qs = qs.filter(rsvp_status=rsvp_status)
results = []
for t in qs:
results.append({
"id": str(t.id),
"anrede": t.anrede,
"vorname": t.vorname,
"nachname": t.nachname,
"strasse": t.strasse,
"plz": t.plz,
"ort": t.ort,
"email": t.email,
"rsvp_status": t.rsvp_status,
"destinataer_id": str(t.destinataer_id) if t.destinataer_id else None,
})
log_mcp_read(
role, "veranstaltung", str(veranstaltung.id),
f"{len(results)} Teilnehmer von '{veranstaltung.titel}'",
)
return format_result({
"veranstaltung": str(veranstaltung),
"anzahl": len(results),
"teilnehmer": results,
})
# ──────────────────────────────────────────────────────────────────────────────
# Globale Suche & Dashboard
# ──────────────────────────────────────────────────────────────────────────────

View File

@@ -575,3 +575,158 @@ def dokument_verknuepfen(
dokument.save(update_fields=update_fields)
log_mcp_update(role, "dokumentlink", str(dokument.id), dokument.titel, changes)
return format_result({"erfolg": True, "id": str(dokument.id), "verknuepft_mit": list(changes.keys())})
# ──────────────────────────────────────────────────────────────────────────────
# Veranstaltungen Teilnehmer
# ──────────────────────────────────────────────────────────────────────────────
def veranstaltung_teilnehmer_anlegen(
veranstaltung_id: str,
vorname: str,
nachname: str,
anrede: str = "",
strasse: str = "",
plz: str = "",
ort: str = "",
email: str = "",
rsvp_status: str = "eingeladen",
bemerkungen: str = "",
destinataer_id: str = "",
) -> str:
"""
Fügt einen Teilnehmer zu einer Veranstaltung hinzu.
Args:
veranstaltung_id: UUID der Veranstaltung (Pflichtfeld)
vorname: Vorname (Pflichtfeld)
nachname: Nachname (Pflichtfeld)
anrede: Herr/Frau (optional). Akzeptiert auch 'Herrn' → wird zu 'Herr' normalisiert.
strasse: Straße und Hausnummer (optional)
plz: Postleitzahl (optional)
ort: Ort (optional)
email: E-Mail-Adresse (optional)
rsvp_status: eingeladen/zugesagt/abgesagt/keine_rueckmeldung (Standard: eingeladen)
bemerkungen: Freitext-Bemerkungen (optional)
destinataer_id: UUID eines bestehenden Destinatärs zum Verknüpfen (optional)
"""
from stiftung.models import Destinataer
from stiftung.models.veranstaltungen import Veranstaltung, Veranstaltungsteilnehmer
role = _require_write_role()
try:
veranstaltung = Veranstaltung.objects.get(id=veranstaltung_id)
except Veranstaltung.DoesNotExist:
return format_result({"fehler": f"Veranstaltung {veranstaltung_id} nicht gefunden"})
# Normalize anrede: 'Herrn' → 'Herr'
anrede_norm = anrede.strip()
if anrede_norm.lower() == "herrn":
anrede_norm = "Herr"
kwargs = {
"veranstaltung": veranstaltung,
"vorname": vorname.strip(),
"nachname": nachname.strip(),
"anrede": anrede_norm,
"strasse": strasse.strip(),
"plz": plz.strip(),
"ort": ort.strip(),
"email": email.strip(),
"rsvp_status": rsvp_status,
"bemerkungen": bemerkungen,
}
if destinataer_id:
try:
kwargs["destinataer"] = Destinataer.objects.get(id=destinataer_id)
except Destinataer.DoesNotExist:
return format_result({"fehler": f"Destinatär {destinataer_id} nicht gefunden"})
teilnehmer = Veranstaltungsteilnehmer.objects.create(**kwargs)
name = f"{vorname} {nachname}"
log_mcp_create(role, "veranstaltung", str(teilnehmer.id), f"Teilnehmer: {name}")
return format_result({
"erfolg": True,
"id": str(teilnehmer.id),
"name": name,
"veranstaltung": str(veranstaltung),
})
def veranstaltung_teilnehmer_importieren(
veranstaltung_id: str,
teilnehmer_liste: str,
) -> str:
"""
Importiert mehrere Teilnehmer auf einmal in eine Veranstaltung.
Args:
veranstaltung_id: UUID der Veranstaltung (Pflichtfeld)
teilnehmer_liste: JSON-Array mit Teilnehmerdaten. Jedes Objekt kann enthalten:
vorname (Pflicht), nachname (Pflicht), anrede, strasse, plz, ort, email,
rsvp_status, bemerkungen.
Beispiel: [{"vorname": "Max", "nachname": "Muster", "anrede": "Herr",
"strasse": "Musterstr. 1", "plz": "12345", "ort": "Berlin"}]
"""
import json as _json
from stiftung.models.veranstaltungen import Veranstaltung, Veranstaltungsteilnehmer
role = _require_write_role()
try:
veranstaltung = Veranstaltung.objects.get(id=veranstaltung_id)
except Veranstaltung.DoesNotExist:
return format_result({"fehler": f"Veranstaltung {veranstaltung_id} nicht gefunden"})
try:
teilnehmer_data = _json.loads(teilnehmer_liste)
except _json.JSONDecodeError as e:
return format_result({"fehler": f"Ungültiges JSON: {e}"})
if not isinstance(teilnehmer_data, list):
return format_result({"fehler": "teilnehmer_liste muss ein JSON-Array sein"})
erstellt = []
fehler = []
for idx, entry in enumerate(teilnehmer_data):
vorname = (entry.get("vorname") or "").strip()
nachname = (entry.get("nachname") or "").strip()
if not vorname or not nachname:
fehler.append({"index": idx, "grund": "vorname und nachname sind Pflichtfelder"})
continue
anrede = (entry.get("anrede") or "").strip()
if anrede.lower() == "herrn":
anrede = "Herr"
teilnehmer = Veranstaltungsteilnehmer.objects.create(
veranstaltung=veranstaltung,
vorname=vorname,
nachname=nachname,
anrede=anrede,
strasse=(entry.get("strasse") or "").strip(),
plz=(entry.get("plz") or "").strip(),
ort=(entry.get("ort") or "").strip(),
email=(entry.get("email") or "").strip(),
rsvp_status=entry.get("rsvp_status", "eingeladen"),
bemerkungen=entry.get("bemerkungen", ""),
)
erstellt.append({"id": str(teilnehmer.id), "name": f"{vorname} {nachname}"})
log_mcp_create(
role, "veranstaltung", str(veranstaltung.id),
f"{len(erstellt)} Teilnehmer importiert",
)
return format_result({
"erfolg": True,
"veranstaltung": str(veranstaltung),
"erstellt": len(erstellt),
"fehler": len(fehler),
"teilnehmer": erstellt,
"fehler_details": fehler if fehler else None,
})