- 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>
733 lines
26 KiB
Python
733 lines
26 KiB
Python
"""
|
||
Schreib-Tools für den MCP Server der Stiftungsverwaltung.
|
||
|
||
Alle Tools:
|
||
- Prüfen die Rolle (editor oder admin erforderlich)
|
||
- Schreiben Audit-Log-Einträge
|
||
- Validieren Pflichtfelder vor dem Speichern
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from mcp_server.audit import log_mcp_create, log_mcp_update
|
||
from mcp_server.auth import can_write, require_role
|
||
from mcp_server.tools.helpers import format_result
|
||
|
||
|
||
def _require_write_role() -> str:
|
||
from mcp_server.auth import get_current_role
|
||
|
||
role = get_current_role()
|
||
require_role(role)
|
||
if not can_write(role):
|
||
raise PermissionError(
|
||
f"Rolle '{role}' hat keine Schreibrechte. "
|
||
"editor- oder admin-Rolle erforderlich."
|
||
)
|
||
return role
|
||
|
||
|
||
# ──────────────────────────────────────────────────────────────────────────────
|
||
# Destinatäre
|
||
# ──────────────────────────────────────────────────────────────────────────────
|
||
|
||
def destinataer_anlegen(
|
||
vorname: str,
|
||
nachname: str,
|
||
familienzweig: str = "",
|
||
email: str = "",
|
||
telefon: str = "",
|
||
geburtsdatum: str = "",
|
||
ort: str = "",
|
||
plz: str = "",
|
||
strasse: str = "",
|
||
berufsgruppe: str = "",
|
||
notizen: str = "",
|
||
) -> str:
|
||
"""
|
||
Legt einen neuen Destinatär an.
|
||
|
||
Args:
|
||
vorname: Vorname (Pflichtfeld)
|
||
nachname: Nachname (Pflichtfeld)
|
||
familienzweig: hauptzweig/nebenzweig/verwandt/anderer
|
||
email: E-Mail-Adresse
|
||
telefon: Telefonnummer
|
||
geburtsdatum: Geburtsdatum YYYY-MM-DD
|
||
ort: Wohnort
|
||
plz: Postleitzahl
|
||
strasse: Straße und Hausnummer
|
||
berufsgruppe: student/wissenschaftler/künstler/sozialarbeiter/umweltschützer/andere
|
||
notizen: Freitext-Notizen
|
||
"""
|
||
from stiftung.models import Destinataer
|
||
|
||
role = _require_write_role()
|
||
|
||
kwargs = {
|
||
"vorname": vorname.strip(),
|
||
"nachname": nachname.strip(),
|
||
"aktiv": True,
|
||
}
|
||
if familienzweig:
|
||
kwargs["familienzweig"] = familienzweig
|
||
if email:
|
||
kwargs["email"] = email
|
||
if telefon:
|
||
kwargs["telefon"] = telefon
|
||
if geburtsdatum:
|
||
kwargs["geburtsdatum"] = geburtsdatum
|
||
if ort:
|
||
kwargs["ort"] = ort
|
||
if plz:
|
||
kwargs["plz"] = plz
|
||
if strasse:
|
||
kwargs["strasse"] = strasse
|
||
if berufsgruppe:
|
||
kwargs["berufsgruppe"] = berufsgruppe
|
||
if notizen:
|
||
kwargs["notizen"] = notizen
|
||
|
||
obj = Destinataer.objects.create(**kwargs)
|
||
log_mcp_create(role, "destinataer", str(obj.id), f"{vorname} {nachname}")
|
||
return format_result({"erfolg": True, "id": str(obj.id), "name": f"{vorname} {nachname}"})
|
||
|
||
|
||
def destinataer_aktualisieren(
|
||
destinataer_id: str,
|
||
vorname: str = "",
|
||
nachname: str = "",
|
||
email: str = "",
|
||
telefon: str = "",
|
||
ort: str = "",
|
||
plz: str = "",
|
||
strasse: str = "",
|
||
aktiv: bool | None = None,
|
||
notizen: str = "",
|
||
familienzweig: str = "",
|
||
) -> str:
|
||
"""
|
||
Aktualisiert einen bestehenden Destinatär.
|
||
|
||
Args:
|
||
destinataer_id: UUID des Destinatärs (Pflichtfeld)
|
||
vorname: Neuer Vorname (optional)
|
||
nachname: Neuer Nachname (optional)
|
||
email: Neue E-Mail (optional)
|
||
telefon: Neue Telefonnummer (optional)
|
||
ort: Neuer Ort (optional)
|
||
plz: Neue PLZ (optional)
|
||
strasse: Neue Straße (optional)
|
||
aktiv: Aktivstatus (optional)
|
||
notizen: Neue Notizen (optional)
|
||
familienzweig: Neuer Familienzweig (optional)
|
||
"""
|
||
from stiftung.models import Destinataer
|
||
|
||
role = _require_write_role()
|
||
|
||
try:
|
||
obj = Destinataer.objects.get(id=destinataer_id)
|
||
except Destinataer.DoesNotExist:
|
||
return format_result({"fehler": f"Destinatär {destinataer_id} nicht gefunden"})
|
||
|
||
changes = {}
|
||
update_fields = []
|
||
|
||
def _set(field, value):
|
||
if value != "" and value is not None:
|
||
old = getattr(obj, field)
|
||
if str(old) != str(value):
|
||
changes[field] = {"alt": str(old), "neu": str(value)}
|
||
setattr(obj, field, value)
|
||
update_fields.append(field)
|
||
|
||
_set("vorname", vorname)
|
||
_set("nachname", nachname)
|
||
_set("email", email)
|
||
_set("telefon", telefon)
|
||
_set("ort", ort)
|
||
_set("plz", plz)
|
||
_set("strasse", strasse)
|
||
_set("notizen", notizen)
|
||
_set("familienzweig", familienzweig)
|
||
if aktiv is not None:
|
||
_set("aktiv", aktiv)
|
||
|
||
if not update_fields:
|
||
return format_result({"erfolg": True, "hinweis": "Keine Änderungen"})
|
||
|
||
obj.save(update_fields=update_fields)
|
||
name = f"{obj.vorname} {obj.nachname}"
|
||
log_mcp_update(role, "destinataer", str(obj.id), name, changes)
|
||
return format_result({"erfolg": True, "id": str(obj.id), "geaenderte_felder": list(changes.keys())})
|
||
|
||
|
||
# ──────────────────────────────────────────────────────────────────────────────
|
||
# Förderungen & Unterstützungen
|
||
# ──────────────────────────────────────────────────────────────────────────────
|
||
|
||
def foerderung_anlegen(
|
||
destinataer_id: str,
|
||
jahr: int,
|
||
betrag: float,
|
||
kategorie: str = "anderes",
|
||
bemerkungen: str = "",
|
||
) -> str:
|
||
"""
|
||
Legt eine neue Förderung für einen Destinatär an.
|
||
|
||
Args:
|
||
destinataer_id: UUID des Destinatärs (Pflichtfeld)
|
||
jahr: Förderjahr (Pflichtfeld)
|
||
betrag: Förderbetrag in EUR (Pflichtfeld)
|
||
kategorie: bildung/forschung/kultur/soziales/umwelt/anderes
|
||
bemerkungen: Freitext
|
||
"""
|
||
from datetime import date
|
||
|
||
from stiftung.models import Destinataer, Foerderung
|
||
|
||
role = _require_write_role()
|
||
|
||
try:
|
||
destinataer = Destinataer.objects.get(id=destinataer_id)
|
||
except Destinataer.DoesNotExist:
|
||
return format_result({"fehler": f"Destinatär {destinataer_id} nicht gefunden"})
|
||
|
||
foerderung = Foerderung.objects.create(
|
||
destinataer=destinataer,
|
||
jahr=jahr,
|
||
betrag=betrag,
|
||
kategorie=kategorie,
|
||
bemerkungen=bemerkungen,
|
||
status="beantragt",
|
||
antragsdatum=date.today(),
|
||
)
|
||
|
||
name = f"{destinataer.vorname} {destinataer.nachname} – {jahr}"
|
||
log_mcp_create(role, "foerderung", str(foerderung.id), name)
|
||
return format_result({"erfolg": True, "id": str(foerderung.id), "foerderung": name})
|
||
|
||
|
||
def unterstuetzung_anlegen(
|
||
destinataer_id: str,
|
||
konto_id: str,
|
||
betrag: float,
|
||
faellig_am: str,
|
||
beschreibung: str = "",
|
||
verwendungszweck: str = "",
|
||
) -> str:
|
||
"""
|
||
Legt eine neue Unterstützungszahlung für einen Destinatär an.
|
||
|
||
Args:
|
||
destinataer_id: UUID des Destinatärs (Pflichtfeld)
|
||
konto_id: UUID des Zahlungskontos (Pflichtfeld)
|
||
betrag: Betrag in EUR (Pflichtfeld)
|
||
faellig_am: Fälligkeitsdatum YYYY-MM-DD (Pflichtfeld)
|
||
beschreibung: Kurzbeschreibung (optional)
|
||
verwendungszweck: Verwendungszweck für Überweisung (optional)
|
||
"""
|
||
from stiftung.models import Destinataer, DestinataerUnterstuetzung, StiftungsKonto
|
||
|
||
role = _require_write_role()
|
||
|
||
try:
|
||
destinataer = Destinataer.objects.get(id=destinataer_id)
|
||
except Destinataer.DoesNotExist:
|
||
return format_result({"fehler": f"Destinatär {destinataer_id} nicht gefunden"})
|
||
|
||
try:
|
||
konto = StiftungsKonto.objects.get(id=konto_id)
|
||
except StiftungsKonto.DoesNotExist:
|
||
return format_result({"fehler": f"Konto {konto_id} nicht gefunden"})
|
||
|
||
unterstuetzung = DestinataerUnterstuetzung.objects.create(
|
||
destinataer=destinataer,
|
||
konto=konto,
|
||
betrag=betrag,
|
||
faellig_am=faellig_am,
|
||
beschreibung=beschreibung,
|
||
verwendungszweck=verwendungszweck,
|
||
status="geplant",
|
||
)
|
||
|
||
name = f"{destinataer.vorname} {destinataer.nachname} – {faellig_am}"
|
||
log_mcp_create(role, "destinataer", str(unterstuetzung.id), name)
|
||
return format_result({"erfolg": True, "id": str(unterstuetzung.id)})
|
||
|
||
|
||
# ──────────────────────────────────────────────────────────────────────────────
|
||
# Ländereien & Verpachtungen
|
||
# ──────────────────────────────────────────────────────────────────────────────
|
||
|
||
def land_anlegen(
|
||
lfd_nr: str,
|
||
amtsgericht: str,
|
||
gemeinde: str,
|
||
gemarkung: str,
|
||
flur: str,
|
||
flurstueck: str,
|
||
groesse_qm: float,
|
||
verpachtete_gesamtflaeche: float = 0.0,
|
||
adresse: str = "",
|
||
) -> str:
|
||
"""
|
||
Legt eine neue Länderei an.
|
||
|
||
Args:
|
||
lfd_nr: Laufende Nummer (Pflichtfeld, eindeutig)
|
||
amtsgericht: Zuständiges Amtsgericht (Pflichtfeld)
|
||
gemeinde: Gemeinde (Pflichtfeld)
|
||
gemarkung: Gemarkung (Pflichtfeld)
|
||
flur: Flur (Pflichtfeld)
|
||
flurstueck: Flurstück (Pflichtfeld)
|
||
groesse_qm: Gesamtgröße in Quadratmetern (Pflichtfeld)
|
||
verpachtete_gesamtflaeche: Verpachtete Fläche in qm (Standard: 0)
|
||
adresse: Adresse/Ortsangabe (optional)
|
||
"""
|
||
from stiftung.models import Land
|
||
|
||
role = _require_write_role()
|
||
|
||
if Land.objects.filter(lfd_nr=lfd_nr).exists():
|
||
return format_result({"fehler": f"Länderei mit lfd_nr '{lfd_nr}' existiert bereits"})
|
||
|
||
land = Land.objects.create(
|
||
lfd_nr=lfd_nr,
|
||
amtsgericht=amtsgericht,
|
||
gemeinde=gemeinde,
|
||
gemarkung=gemarkung,
|
||
flur=flur,
|
||
flurstueck=flurstueck,
|
||
groesse_qm=groesse_qm,
|
||
verpachtete_gesamtflaeche=verpachtete_gesamtflaeche,
|
||
adresse=adresse,
|
||
)
|
||
|
||
log_mcp_create(role, "land", str(land.id), str(land))
|
||
return format_result({"erfolg": True, "id": str(land.id), "bezeichnung": str(land)})
|
||
|
||
|
||
def verpachtung_anlegen(
|
||
land_id: str,
|
||
paechter_id: str,
|
||
vertragsnummer: str,
|
||
pachtbeginn: str,
|
||
verpachtete_flaeche: float,
|
||
pachtzins_pauschal: float,
|
||
zahlungsweise: str = "jaehrlich",
|
||
pachtende: str = "",
|
||
) -> str:
|
||
"""
|
||
Legt einen neuen Pachtvertrag für eine Länderei an.
|
||
|
||
Args:
|
||
land_id: UUID der Länderei (Pflichtfeld)
|
||
paechter_id: UUID des Pächters (Pflichtfeld)
|
||
vertragsnummer: Eindeutige Vertragsnummer (Pflichtfeld)
|
||
pachtbeginn: Datum YYYY-MM-DD (Pflichtfeld)
|
||
verpachtete_flaeche: Fläche in qm (Pflichtfeld)
|
||
pachtzins_pauschal: Jährlicher Pachtzins in EUR (Pflichtfeld)
|
||
zahlungsweise: jaehrlich/halbjaehrlich/vierteljaehrlich/monatlich
|
||
pachtende: Datum YYYY-MM-DD (optional)
|
||
"""
|
||
from stiftung.models import Land, LandVerpachtung, Paechter
|
||
|
||
role = _require_write_role()
|
||
|
||
try:
|
||
land = Land.objects.get(id=land_id)
|
||
except Land.DoesNotExist:
|
||
return format_result({"fehler": f"Länderei {land_id} nicht gefunden"})
|
||
|
||
try:
|
||
paechter = Paechter.objects.get(id=paechter_id)
|
||
except Paechter.DoesNotExist:
|
||
return format_result({"fehler": f"Pächter {paechter_id} nicht gefunden"})
|
||
|
||
if LandVerpachtung.objects.filter(vertragsnummer=vertragsnummer).exists():
|
||
return format_result({"fehler": f"Vertragsnummer '{vertragsnummer}' existiert bereits"})
|
||
|
||
kwargs = {
|
||
"land": land,
|
||
"paechter": paechter,
|
||
"vertragsnummer": vertragsnummer,
|
||
"pachtbeginn": pachtbeginn,
|
||
"verpachtete_flaeche": verpachtete_flaeche,
|
||
"pachtzins_pauschal": pachtzins_pauschal,
|
||
"zahlungsweise": zahlungsweise,
|
||
"status": "aktiv",
|
||
}
|
||
if pachtende:
|
||
kwargs["pachtende"] = pachtende
|
||
|
||
verpachtung = LandVerpachtung.objects.create(**kwargs)
|
||
name = f"{land} – {paechter}"
|
||
log_mcp_create(role, "verpachtung", str(verpachtung.id), name)
|
||
return format_result({"erfolg": True, "id": str(verpachtung.id)})
|
||
|
||
|
||
def paechter_anlegen(
|
||
vorname: str,
|
||
nachname: str,
|
||
email: str = "",
|
||
telefon: str = "",
|
||
ort: str = "",
|
||
plz: str = "",
|
||
strasse: str = "",
|
||
personentyp: str = "natuerlich",
|
||
) -> str:
|
||
"""
|
||
Legt einen neuen Pächter an.
|
||
|
||
Args:
|
||
vorname: Vorname (Pflichtfeld)
|
||
nachname: Nachname (Pflichtfeld)
|
||
email: E-Mail (optional)
|
||
telefon: Telefon (optional)
|
||
ort: Ort (optional)
|
||
plz: Postleitzahl (optional)
|
||
strasse: Straße (optional)
|
||
personentyp: natuerlich/gesellschaft
|
||
"""
|
||
from stiftung.models import Paechter
|
||
|
||
role = _require_write_role()
|
||
|
||
kwargs = {
|
||
"vorname": vorname.strip(),
|
||
"nachname": nachname.strip(),
|
||
"personentyp": personentyp,
|
||
}
|
||
for field, value in [("email", email), ("telefon", telefon), ("ort", ort), ("plz", plz), ("strasse", strasse)]:
|
||
if value:
|
||
kwargs[field] = value
|
||
|
||
paechter = Paechter.objects.create(**kwargs)
|
||
name = f"{vorname} {nachname}"
|
||
log_mcp_create(role, "paechter", str(paechter.id), name)
|
||
return format_result({"erfolg": True, "id": str(paechter.id), "name": name})
|
||
|
||
|
||
# ──────────────────────────────────────────────────────────────────────────────
|
||
# Verwaltungskosten
|
||
# ──────────────────────────────────────────────────────────────────────────────
|
||
|
||
def verwaltungskosten_erfassen(
|
||
bezeichnung: str,
|
||
kategorie: str,
|
||
betrag: float,
|
||
datum: str,
|
||
lieferant_firma: str = "",
|
||
rechnungsnummer: str = "",
|
||
status: str = "geplant",
|
||
) -> str:
|
||
"""
|
||
Erfasst eine neue Verwaltungskosten-Position.
|
||
|
||
Args:
|
||
bezeichnung: Bezeichnung (Pflichtfeld)
|
||
kategorie: rechnung_intern/bueroausstattung/fahrtkosten/porto/telefon_internet/
|
||
software/beratung/versicherung/steuerberatung/bankgebuehren/sonstiges
|
||
betrag: Betrag in EUR (Pflichtfeld)
|
||
datum: Datum YYYY-MM-DD (Pflichtfeld)
|
||
lieferant_firma: Lieferant oder Firma (optional)
|
||
rechnungsnummer: Rechnungsnummer (optional)
|
||
status: geplant/bestellt/erhalten/in_bearbeitung/bezahlt/storniert
|
||
"""
|
||
from stiftung.models import Verwaltungskosten
|
||
|
||
role = _require_write_role()
|
||
|
||
vk = Verwaltungskosten.objects.create(
|
||
bezeichnung=bezeichnung,
|
||
kategorie=kategorie,
|
||
betrag=betrag,
|
||
datum=datum,
|
||
lieferant_firma=lieferant_firma,
|
||
rechnungsnummer=rechnungsnummer,
|
||
status=status,
|
||
)
|
||
|
||
log_mcp_create(role, "verwaltungskosten", str(vk.id), bezeichnung)
|
||
return format_result({"erfolg": True, "id": str(vk.id), "bezeichnung": bezeichnung})
|
||
|
||
|
||
# ──────────────────────────────────────────────────────────────────────────────
|
||
# Termine
|
||
# ──────────────────────────────────────────────────────────────────────────────
|
||
|
||
def termin_anlegen(
|
||
titel: str,
|
||
datum: str,
|
||
kategorie: str = "termin",
|
||
prioritaet: str = "normal",
|
||
beschreibung: str = "",
|
||
uhrzeit: str = "",
|
||
ganztags: bool = True,
|
||
destinataer_id: str = "",
|
||
) -> str:
|
||
"""
|
||
Legt einen neuen Kalendertermin an.
|
||
|
||
Args:
|
||
titel: Titel des Termins (Pflichtfeld)
|
||
datum: Datum YYYY-MM-DD (Pflichtfeld)
|
||
kategorie: termin/zahlung/deadline/geburtstag/vertrag/pruefung/sonstiges
|
||
prioritaet: niedrig/normal/hoch/kritisch
|
||
beschreibung: Beschreibung (optional)
|
||
uhrzeit: Uhrzeit HH:MM (optional)
|
||
ganztags: Ganztägig (Standard: True)
|
||
destinataer_id: UUID eines zugehörigen Destinatärs (optional)
|
||
"""
|
||
from stiftung.models import Destinataer, StiftungsKalenderEintrag
|
||
|
||
role = _require_write_role()
|
||
|
||
kwargs = {
|
||
"titel": titel,
|
||
"datum": datum,
|
||
"kategorie": kategorie,
|
||
"prioritaet": prioritaet,
|
||
"beschreibung": beschreibung,
|
||
"ganztags": ganztags,
|
||
}
|
||
if uhrzeit:
|
||
kwargs["uhrzeit"] = uhrzeit
|
||
kwargs["ganztags"] = False
|
||
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"})
|
||
|
||
termin = StiftungsKalenderEintrag.objects.create(**kwargs)
|
||
log_mcp_create(role, "system", str(termin.id), titel)
|
||
return format_result({"erfolg": True, "id": str(termin.id), "titel": titel, "datum": datum})
|
||
|
||
|
||
# ──────────────────────────────────────────────────────────────────────────────
|
||
# Dokument verknüpfen
|
||
# ──────────────────────────────────────────────────────────────────────────────
|
||
|
||
def dokument_verknuepfen(
|
||
dokument_id: str,
|
||
land_id: str = "",
|
||
paechter_id: str = "",
|
||
destinataer_id: str = "",
|
||
) -> str:
|
||
"""
|
||
Verknüpft ein vorhandenes Dokument mit einer Länderei, einem Pächter oder Destinatär.
|
||
|
||
Args:
|
||
dokument_id: UUID des Dokuments (Pflichtfeld)
|
||
land_id: UUID der Länderei (optional)
|
||
paechter_id: UUID des Pächters (optional)
|
||
destinataer_id: UUID des Destinatärs (optional)
|
||
"""
|
||
from stiftung.models import DokumentDatei
|
||
|
||
role = _require_write_role()
|
||
|
||
try:
|
||
dokument = DokumentDatei.objects.get(id=dokument_id)
|
||
except DokumentDatei.DoesNotExist:
|
||
return format_result({"fehler": f"Dokument {dokument_id} nicht gefunden"})
|
||
|
||
changes = {}
|
||
update_fields = []
|
||
|
||
if land_id:
|
||
from stiftung.models import Land
|
||
try:
|
||
land = Land.objects.get(id=land_id)
|
||
dokument.land = land
|
||
update_fields.append("land")
|
||
changes["land"] = {"neu": str(land)}
|
||
except Land.DoesNotExist:
|
||
return format_result({"fehler": f"Länderei {land_id} nicht gefunden"})
|
||
|
||
if paechter_id:
|
||
from stiftung.models import Paechter
|
||
try:
|
||
paechter = Paechter.objects.get(id=paechter_id)
|
||
dokument.paechter = paechter
|
||
update_fields.append("paechter")
|
||
changes["paechter"] = {"neu": str(paechter)}
|
||
except Paechter.DoesNotExist:
|
||
return format_result({"fehler": f"Pächter {paechter_id} nicht gefunden"})
|
||
|
||
if destinataer_id:
|
||
from stiftung.models import Destinataer
|
||
try:
|
||
dest = Destinataer.objects.get(id=destinataer_id)
|
||
dokument.destinataer = dest
|
||
update_fields.append("destinataer")
|
||
changes["destinataer"] = {"neu": f"{dest.vorname} {dest.nachname}"}
|
||
except Destinataer.DoesNotExist:
|
||
return format_result({"fehler": f"Destinatär {destinataer_id} nicht gefunden"})
|
||
|
||
if not update_fields:
|
||
return format_result({"fehler": "Keine Verknüpfung angegeben (land_id, paechter_id oder destinataer_id)"})
|
||
|
||
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,
|
||
})
|