Files
SysAdmin Agent b4bad7bc83 Phase 0: models.py → models/ Package aufgeteilt
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>
2026-03-11 09:02:08 +00:00

386 lines
13 KiB
Python

import uuid
from django.db import models
class Rentmeister(models.Model):
"""Geschäftsführer der Stiftung (natürliche Personen)"""
ANREDE_CHOICES = [
("herr", "Herr"),
("frau", "Frau"),
("dr", "Dr."),
("prof", "Prof."),
("prof_dr", "Prof. Dr."),
]
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
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")
titel = models.CharField(max_length=50, blank=True, verbose_name="Titel")
# Kontaktdaten
email = models.EmailField(blank=True, verbose_name="E-Mail")
telefon = models.CharField(max_length=20, blank=True, verbose_name="Telefon")
mobil = models.CharField(max_length=20, blank=True, verbose_name="Mobil")
# Adresse
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")
# Bankdaten für Abrechnungen
iban = models.CharField(max_length=34, blank=True, verbose_name="IBAN")
bic = models.CharField(max_length=11, blank=True, verbose_name="BIC")
bank_name = models.CharField(max_length=100, blank=True, verbose_name="Bank")
# Stiftungs-spezifisch
seit_datum = models.DateField(verbose_name="Rentmeister seit")
bis_datum = models.DateField(null=True, blank=True, verbose_name="Rentmeister bis")
aktiv = models.BooleanField(default=True, verbose_name="Aktiv")
# Vergütung/Aufwandsentschädigung
monatliche_verguetung = models.DecimalField(
max_digits=8,
decimal_places=2,
null=True,
blank=True,
verbose_name="Monatliche Vergütung (€)",
)
km_pauschale = models.DecimalField(
max_digits=4,
decimal_places=2,
default=0.30,
verbose_name="Kilometerpauschale (€/km)",
)
notizen = models.TextField(blank=True, verbose_name="Notizen")
erstellt_am = models.DateTimeField(auto_now_add=True)
aktualisiert_am = models.DateTimeField(auto_now=True)
class Meta:
verbose_name = "Rentmeister"
verbose_name_plural = "Rentmeister"
ordering = ["nachname", "vorname"]
def __str__(self):
name_parts = []
if self.anrede:
name_parts.append(self.get_anrede_display())
if self.vorname:
name_parts.append(self.vorname)
name_parts.append(self.nachname)
if self.titel:
name_parts.append(f"({self.titel})")
return " ".join(name_parts)
def get_full_name(self):
"""Vollständiger Name ohne Anrede"""
if self.vorname:
return f"{self.vorname} {self.nachname}"
return self.nachname
def get_address(self):
"""Vollständige Adresse als String"""
parts = []
if self.strasse:
parts.append(self.strasse)
if self.plz and self.ort:
parts.append(f"{self.plz} {self.ort}")
elif self.ort:
parts.append(self.ort)
return ", ".join(parts)
class StiftungsKonto(models.Model):
"""Bankkonten der Stiftung"""
KONTO_TYP_CHOICES = [
("girokonto", "Girokonto"),
("sparkonto", "Sparkonto"),
("festgeld", "Festgeld"),
("tagesgeld", "Tagesgeld"),
("depot", "Depot"),
("sonstiges", "Sonstiges"),
]
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
kontoname = models.CharField(max_length=200, verbose_name="Kontoname")
bank_name = models.CharField(max_length=200, verbose_name="Bank")
iban = models.CharField(max_length=34, verbose_name="IBAN")
bic = models.CharField(max_length=11, blank=True, verbose_name="BIC")
konto_typ = models.CharField(
max_length=20,
choices=KONTO_TYP_CHOICES,
default="girokonto",
verbose_name="Kontotyp",
)
saldo = models.DecimalField(
max_digits=10, decimal_places=2, default=0.00, verbose_name="Aktueller Saldo"
)
saldo_datum = models.DateField(null=True, blank=True, verbose_name="Saldo-Datum")
zinssatz = models.DecimalField(
max_digits=5,
decimal_places=2,
null=True,
blank=True,
verbose_name="Zinssatz (%)",
)
laufzeit_bis = models.DateField(null=True, blank=True, verbose_name="Laufzeit bis")
aktiv = models.BooleanField(default=True, verbose_name="Aktiv")
notizen = models.TextField(blank=True, verbose_name="Notizen")
erstellt_am = models.DateTimeField(auto_now_add=True)
aktualisiert_am = models.DateTimeField(auto_now=True)
class Meta:
verbose_name = "Stiftungskonto"
verbose_name_plural = "Stiftungskonten"
ordering = ["bank_name", "kontoname"]
def __str__(self):
return f"{self.bank_name} - {self.kontoname}"
class BankTransaction(models.Model):
"""Banktransaktionen aus importierten Kontodaten"""
TRANSACTION_TYPE_CHOICES = [
("eingang", "Eingang"),
("ausgang", "Ausgang"),
("lastschrift", "Lastschrift"),
("ueberweisung", "Überweisung"),
("dauerauftrag", "Dauerauftrag"),
("kartenzahlung", "Kartenzahlung"),
("zinsen", "Zinsen"),
("gebuehren", "Gebühren"),
("sonstiges", "Sonstiges"),
]
STATUS_CHOICES = [
("imported", "Importiert"),
("verified", "Geprüft"),
("assigned", "Zugeordnet"),
("ignored", "Ignoriert"),
]
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
konto = models.ForeignKey(
StiftungsKonto, on_delete=models.CASCADE, verbose_name="Konto"
)
# Transaktionsdaten
datum = models.DateField(verbose_name="Buchungsdatum")
valuta = models.DateField(null=True, blank=True, verbose_name="Valutadatum")
betrag = models.DecimalField(
max_digits=12, decimal_places=2, verbose_name="Betrag (€)"
)
waehrung = models.CharField(max_length=3, default="EUR", verbose_name="Währung")
# Transaktionsdetails
verwendungszweck = models.TextField(verbose_name="Verwendungszweck")
empfaenger_zahlungspflichtiger = models.CharField(
max_length=200, blank=True, verbose_name="Empfänger/Zahlungspflichtiger"
)
iban_gegenpartei = models.CharField(
max_length=34, blank=True, verbose_name="IBAN Gegenpartei"
)
bic_gegenpartei = models.CharField(
max_length=11, blank=True, verbose_name="BIC Gegenpartei"
)
# Bankspezifische Daten
referenz = models.CharField(
max_length=100, blank=True, verbose_name="Referenz/Transaktions-ID"
)
transaction_type = models.CharField(
max_length=20,
choices=TRANSACTION_TYPE_CHOICES,
default="sonstiges",
verbose_name="Transaktionsart",
)
# Verwaltung
status = models.CharField(
max_length=20, choices=STATUS_CHOICES, default="imported", verbose_name="Status"
)
kommentare = models.TextField(blank=True, verbose_name="Kommentare")
verwaltungskosten = models.ForeignKey(
"Verwaltungskosten",
null=True,
blank=True,
on_delete=models.SET_NULL,
verbose_name="Zugeordnete Verwaltungskosten",
)
# Import-Metadaten
import_datei = models.CharField(
max_length=255, blank=True, verbose_name="Import-Datei"
)
importiert_am = models.DateTimeField(
auto_now_add=True, verbose_name="Importiert am"
)
saldo_nach_buchung = models.DecimalField(
max_digits=12,
decimal_places=2,
null=True,
blank=True,
verbose_name="Saldo nach Buchung",
)
class Meta:
verbose_name = "Banktransaktion"
verbose_name_plural = "Banktransaktionen"
ordering = ["-datum", "-importiert_am"]
unique_together = ["konto", "datum", "betrag", "referenz"] # Prevent duplicates
def __str__(self):
return f"{self.datum} - {self.betrag}€ - {self.verwendungszweck[:50]}"
def is_income(self):
"""Prüft ob es sich um einen Geldeingang handelt"""
return self.betrag > 0
def get_absolute_amount(self):
"""Gibt den absoluten Betrag zurück"""
return abs(self.betrag)
class Verwaltungskosten(models.Model):
"""Administrative Kosten und Ausgaben der Stiftung"""
KATEGORIE_CHOICES = [
("rechnung_intern", "Interne Rechnung"),
("bueroausstattung", "Büroausstattung"),
("fahrtkosten", "Fahrtkosten"),
("porto", "Porto & Versand"),
("telefon_internet", "Telefon & Internet"),
("software", "Software & Lizenzen"),
("beratung", "Beratung & Dienstleistungen"),
("versicherung", "Versicherungen"),
("steuerberatung", "Steuerberatung"),
("bankgebuehren", "Bankgebühren"),
("sonstiges", "Sonstiges"),
]
STATUS_CHOICES = [
("geplant", "Geplant"),
("bestellt", "Bestellt"),
("erhalten", "Erhalten"),
("in_bearbeitung", "In Bearbeitung"),
("bezahlt", "Bezahlt"),
("storniert", "Storniert"),
]
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
bezeichnung = models.CharField(max_length=200, verbose_name="Bezeichnung")
kategorie = models.CharField(
max_length=30, choices=KATEGORIE_CHOICES, verbose_name="Kategorie"
)
betrag = models.DecimalField(
max_digits=10, decimal_places=2, verbose_name="Betrag (€)"
)
datum = models.DateField(verbose_name="Datum")
lieferant_firma = models.CharField(
max_length=200, blank=True, verbose_name="Lieferant/Firma"
)
rechnungsnummer = models.CharField(
max_length=100, blank=True, verbose_name="Rechnungsnummer"
)
status = models.CharField(
max_length=20, choices=STATUS_CHOICES, default="geplant", verbose_name="Status"
)
# Zuständigkeit und Zahlung
rentmeister = models.ForeignKey(
Rentmeister,
on_delete=models.SET_NULL,
null=True,
blank=True,
verbose_name="Zuständiger Rentmeister",
)
zahlungskonto = models.ForeignKey(
StiftungsKonto,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name="zahlungen",
verbose_name="Zahlungskonto",
)
quellkonto = models.ForeignKey(
StiftungsKonto,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name="ausgaben",
verbose_name="Quellkonto",
)
# Legacy field für Rückwärtskompatibilität
konto = models.ForeignKey(
StiftungsKonto,
on_delete=models.SET_NULL,
null=True,
blank=True,
verbose_name="Konto (Legacy)",
help_text="Veraltet - verwende Zahlungskonto und Quellkonto",
)
# Fahrtkosten spezifisch
km_anzahl = models.DecimalField(
max_digits=8, decimal_places=1, null=True, blank=True, verbose_name="Kilometer"
)
km_satz = models.DecimalField(
max_digits=4, decimal_places=2, null=True, blank=True, verbose_name="€/km"
)
von_ort = models.CharField(max_length=100, blank=True, verbose_name="Von (Ort)")
nach_ort = models.CharField(max_length=100, blank=True, verbose_name="Nach (Ort)")
zweck = models.CharField(max_length=200, blank=True, verbose_name="Zweck der Fahrt")
beschreibung = models.TextField(blank=True, verbose_name="Beschreibung")
notizen = models.TextField(blank=True, verbose_name="Notizen")
erstellt_am = models.DateTimeField(auto_now_add=True)
aktualisiert_am = models.DateTimeField(auto_now=True)
class Meta:
verbose_name = "Verwaltungskosten"
verbose_name_plural = "Verwaltungskosten"
ordering = ["-datum", "-erstellt_am"]
def __str__(self):
return f"{self.bezeichnung} - €{self.betrag} ({self.datum})"
def get_status_color(self):
colors = {
"geplant": "secondary",
"bestellt": "warning",
"erhalten": "info",
"in_bearbeitung": "primary",
"bezahlt": "success",
"storniert": "danger",
}
return colors.get(self.status, "secondary")
def get_effective_zahlungskonto(self):
"""Gibt das Zahlungskonto zurück, fallback auf Legacy-Konto"""
return self.zahlungskonto or self.konto
def get_effective_quellkonto(self):
"""Gibt das Quellkonto zurück, fallback auf Zahlungskonto oder Legacy-Konto"""
return self.quellkonto or self.zahlungskonto or self.konto
def is_fahrtkosten(self):
"""Prüft ob es sich um Fahrtkosten handelt"""
return self.kategorie == "fahrtkosten"
def calculate_fahrtkosten(self):
"""Berechnet Fahrtkosten automatisch wenn km_anzahl und km_satz gesetzt sind"""
if self.km_anzahl and self.km_satz:
return self.km_anzahl * self.km_satz
return None