models.py (3.496 Zeilen) in 6 Domain-Module aufgeteilt: - system.py: CSVImport, ApplicationPermission, AuditLog, BackupJob, AppConfiguration, HelpBox - land.py: Paechter, Land, LandVerpachtung, LandAbrechnung, DokumentLink - finanzen.py: Rentmeister, StiftungsKonto, BankTransaction, Verwaltungskosten - destinataere.py: Destinataer, Person, Foerderung, DestinataerUnterstuetzung, UnterstuetzungWiederkehrend, DestinataerNotiz, VierteljahresNachweis, DestinataerEmailEingang - veranstaltungen.py: BriefVorlage, Veranstaltung, Veranstaltungsteilnehmer - geschichte.py: GeschichteSeite, GeschichteBild, StiftungsKalenderEintrag __init__.py re-exportiert alle Models für volle Rückwärtskompatibilität. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
216 lines
7.0 KiB
Python
216 lines
7.0 KiB
Python
import uuid
|
||
|
||
from django.db import models
|
||
|
||
|
||
class BriefVorlage(models.Model):
|
||
"""Wiederverwendbare Briefvorlagen für Serienbriefe (Veranstaltungseinladungen u.ä.)"""
|
||
|
||
name = models.CharField(max_length=100, verbose_name="Vorlagenname")
|
||
beschreibung = models.TextField(
|
||
blank=True,
|
||
verbose_name="Beschreibung",
|
||
help_text="Kurze Beschreibung des Verwendungszwecks dieser Vorlage.",
|
||
)
|
||
briefvorlage = models.TextField(
|
||
verbose_name="Brieftext (HTML)",
|
||
help_text=(
|
||
"HTML-Text des Briefs. Verfügbare Platzhalter: "
|
||
"{{ anrede }}, {{ vorname }}, {{ nachname }}, {{ strasse }}, "
|
||
"{{ plz }}, {{ ort }}, {{ datum }}, {{ uhrzeit }}, "
|
||
"{{ veranstaltungsort }}, {{ gasthaus_adresse }}"
|
||
),
|
||
)
|
||
betreff = models.CharField(
|
||
max_length=300,
|
||
blank=True,
|
||
verbose_name="Standard-Betreff",
|
||
help_text="Wird beim Laden der Vorlage in die Veranstaltung übernommen. Leer = unveränderter Betreff.",
|
||
)
|
||
|
||
erstellt_am = models.DateTimeField(auto_now_add=True)
|
||
aktualisiert_am = models.DateTimeField(auto_now=True)
|
||
|
||
class Meta:
|
||
verbose_name = "Briefvorlage"
|
||
verbose_name_plural = "Briefvorlagen"
|
||
ordering = ["name"]
|
||
|
||
def __str__(self):
|
||
return self.name
|
||
|
||
|
||
class Veranstaltung(models.Model):
|
||
"""Veranstaltungen der Stiftung, z.B. Stiftungsessen mit Rechnungslegung"""
|
||
|
||
STATUS_CHOICES = [
|
||
("geplant", "Geplant"),
|
||
("einladungen_versendet", "Einladungen versendet"),
|
||
("abgeschlossen", "Abgeschlossen"),
|
||
("abgesagt", "Abgesagt"),
|
||
]
|
||
|
||
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
||
titel = models.CharField(max_length=200, verbose_name="Titel")
|
||
datum = models.DateField(verbose_name="Datum")
|
||
uhrzeit = models.TimeField(null=True, blank=True, verbose_name="Uhrzeit")
|
||
ort = models.CharField(max_length=200, verbose_name="Ort / Gasthaus")
|
||
adresse = models.TextField(blank=True, verbose_name="Adresse Gasthaus")
|
||
beschreibung = models.TextField(blank=True, verbose_name="Beschreibung / Zweck")
|
||
status = models.CharField(
|
||
max_length=30,
|
||
choices=STATUS_CHOICES,
|
||
default="geplant",
|
||
verbose_name="Status",
|
||
)
|
||
budget_pro_person = models.DecimalField(
|
||
max_digits=8,
|
||
decimal_places=2,
|
||
null=True,
|
||
blank=True,
|
||
verbose_name="Budget pro Person (€)",
|
||
help_text="Geschätztes Budget je Teilnehmer in €",
|
||
)
|
||
briefvorlage = models.TextField(
|
||
blank=True,
|
||
verbose_name="Briefvorlage",
|
||
help_text=(
|
||
"HTML/Text-Template für Serienbrief. Platzhalter: "
|
||
"{{ anrede }}, {{ vorname }}, {{ nachname }}, {{ strasse }}, "
|
||
"{{ plz }}, {{ ort }}, {{ datum }}, {{ uhrzeit }}, "
|
||
"{{ veranstaltungsort }}, {{ gasthaus_adresse }}"
|
||
),
|
||
)
|
||
betreff = models.CharField(
|
||
max_length=300,
|
||
blank=True,
|
||
verbose_name="Betreff",
|
||
help_text="Betreffzeile des Serienbriefs. Leer = Standardbetreff.",
|
||
)
|
||
unterschrift_1_name = models.CharField(
|
||
max_length=100,
|
||
blank=True,
|
||
default="Katrin Kleinpaß",
|
||
verbose_name="Unterschrift 1 – Name",
|
||
)
|
||
unterschrift_1_titel = models.CharField(
|
||
max_length=100,
|
||
blank=True,
|
||
default="Rentmeisterin",
|
||
verbose_name="Unterschrift 1 – Titel",
|
||
)
|
||
unterschrift_2_name = models.CharField(
|
||
max_length=100,
|
||
blank=True,
|
||
default="Jan Remmer Siebels",
|
||
verbose_name="Unterschrift 2 – Name",
|
||
)
|
||
unterschrift_2_titel = models.CharField(
|
||
max_length=100,
|
||
blank=True,
|
||
default="Rentmeister",
|
||
verbose_name="Unterschrift 2 – Titel",
|
||
)
|
||
|
||
erstellt_am = models.DateTimeField(auto_now_add=True)
|
||
aktualisiert_am = models.DateTimeField(auto_now=True)
|
||
|
||
class Meta:
|
||
verbose_name = "Veranstaltung"
|
||
verbose_name_plural = "Veranstaltungen"
|
||
ordering = ["-datum"]
|
||
|
||
def __str__(self):
|
||
return f"{self.titel} ({self.datum})"
|
||
|
||
def get_teilnehmer_count(self):
|
||
return self.teilnehmer.count()
|
||
|
||
def get_zugesagte_count(self):
|
||
return self.teilnehmer.filter(rsvp_status="zugesagt").count()
|
||
|
||
def get_abgesagte_count(self):
|
||
return self.teilnehmer.filter(rsvp_status="abgesagt").count()
|
||
|
||
def get_keine_rueckmeldung_count(self):
|
||
return self.teilnehmer.filter(rsvp_status="keine_rueckmeldung").count()
|
||
|
||
|
||
class Veranstaltungsteilnehmer(models.Model):
|
||
"""Teilnehmer einer Veranstaltung – primär freie Eingabe für Familienmitglieder"""
|
||
|
||
ANREDE_CHOICES = [
|
||
("Herr", "Herr"),
|
||
("Frau", "Frau"),
|
||
("", "Keine Anrede"),
|
||
]
|
||
|
||
RSVP_CHOICES = [
|
||
("eingeladen", "Eingeladen"),
|
||
("zugesagt", "Zugesagt"),
|
||
("abgesagt", "Abgesagt"),
|
||
("keine_rueckmeldung", "Keine Rückmeldung"),
|
||
]
|
||
|
||
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
||
veranstaltung = models.ForeignKey(
|
||
Veranstaltung,
|
||
on_delete=models.CASCADE,
|
||
related_name="teilnehmer",
|
||
verbose_name="Veranstaltung",
|
||
)
|
||
|
||
# Optionale Verknüpfung zu bestehenden Datensätzen
|
||
paechter = models.ForeignKey(
|
||
"stiftung.Paechter",
|
||
null=True,
|
||
blank=True,
|
||
on_delete=models.SET_NULL,
|
||
verbose_name="Pächter (optional)",
|
||
)
|
||
destinataer = models.ForeignKey(
|
||
"stiftung.Destinataer",
|
||
null=True,
|
||
blank=True,
|
||
on_delete=models.SET_NULL,
|
||
verbose_name="Destinatär (optional)",
|
||
)
|
||
|
||
# Freie Felder (Pflichtfelder für Serienbrief)
|
||
anrede = models.CharField(
|
||
max_length=10, choices=ANREDE_CHOICES, blank=True, verbose_name="Anrede"
|
||
)
|
||
vorname = models.CharField(max_length=100, verbose_name="Vorname")
|
||
nachname = models.CharField(max_length=100, verbose_name="Nachname")
|
||
strasse = models.CharField(max_length=200, blank=True, verbose_name="Straße")
|
||
plz = models.CharField(max_length=10, blank=True, verbose_name="PLZ")
|
||
ort = models.CharField(max_length=100, blank=True, verbose_name="Ort")
|
||
email = models.EmailField(
|
||
blank=True, verbose_name="E-Mail", help_text="Optional, für späteren E-Mail-Versand"
|
||
)
|
||
|
||
rsvp_status = models.CharField(
|
||
max_length=20,
|
||
choices=RSVP_CHOICES,
|
||
default="eingeladen",
|
||
verbose_name="RSVP-Status",
|
||
)
|
||
bemerkungen = models.TextField(blank=True, verbose_name="Bemerkungen")
|
||
|
||
erstellt_am = models.DateTimeField(auto_now_add=True)
|
||
|
||
class Meta:
|
||
verbose_name = "Veranstaltungsteilnehmer"
|
||
verbose_name_plural = "Veranstaltungsteilnehmer"
|
||
ordering = ["nachname", "vorname"]
|
||
|
||
def __str__(self):
|
||
return f"{self.anrede} {self.vorname} {self.nachname}".strip()
|
||
|
||
def get_full_name(self):
|
||
return f"{self.vorname} {self.nachname}".strip()
|
||
|
||
def get_full_address(self):
|
||
parts = [self.strasse, f"{self.plz} {self.ort}".strip()]
|
||
return ", ".join(p for p in parts if p)
|