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>
This commit is contained in:
385
app/stiftung/models/finanzen.py
Normal file
385
app/stiftung/models/finanzen.py
Normal file
@@ -0,0 +1,385 @@
|
||||
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
|
||||
Reference in New Issue
Block a user