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