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>
386 lines
13 KiB
Python
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
|