import uuid from django.core.validators import MinValueValidator from django.db import models from django.utils import timezone from stiftung.utils.date_utils import ensure_date, get_year_from_date class Paechter(models.Model): """Pächter (Tenants) für Ländereien und Verpachtungen""" id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) vorname = models.CharField(max_length=100, verbose_name="Vorname") nachname = models.CharField(max_length=100, verbose_name="Nachname") geburtsdatum = models.DateField(null=True, blank=True, verbose_name="Geburtsdatum") email = models.EmailField(null=True, blank=True, verbose_name="E-Mail") telefon = models.CharField( max_length=20, null=True, blank=True, verbose_name="Telefon" ) iban = models.CharField(max_length=34, null=True, blank=True, verbose_name="IBAN") # Adressfelder strasse = models.CharField( max_length=200, verbose_name="Straße", blank=True, null=True ) plz = models.CharField(max_length=10, verbose_name="PLZ", blank=True, null=True) ort = models.CharField(max_length=100, verbose_name="Ort", blank=True, null=True) # Typ des Pächters PERSONENTYP_CHOICES = [ ("natuerlich", "Natürliche Person"), ("gesellschaft", "Gesellschaft (GmbH, KG, etc.)"), ] personentyp = models.CharField( max_length=20, choices=PERSONENTYP_CHOICES, default="natuerlich", verbose_name="Typ des Pächters", ) # Pacht-spezifische Felder pachtnummer = models.CharField( max_length=50, null=True, blank=True, verbose_name="Pachtnummer" ) pachtbeginn_erste = models.DateField( null=True, blank=True, verbose_name="Erster Pachtbeginn" ) pachtende_letzte = models.DateField( null=True, blank=True, verbose_name="Letztes Pachtende" ) pachtzins_aktuell = models.DecimalField( max_digits=12, decimal_places=2, null=True, blank=True, verbose_name="Aktueller Pachtzins (€/Jahr)", ) # Landwirtschaftliche Informationen landwirtschaftliche_ausbildung = models.BooleanField( default=False, verbose_name="Landwirtschaftliche Ausbildung" ) berufserfahrung_jahre = models.IntegerField( null=True, blank=True, verbose_name="Berufserfahrung (Jahre)" ) spezialisierung = models.CharField( max_length=100, null=True, blank=True, verbose_name="Spezialisierung" ) # Kontakt und Notizen notizen = models.TextField(null=True, blank=True, verbose_name="Notizen") aktiv = models.BooleanField(default=True, verbose_name="Aktiv") class Meta: verbose_name = "Pächter" verbose_name_plural = "Pächter" ordering = ["nachname", "vorname"] def __str__(self): if self.vorname: return f"{self.nachname}, {self.vorname}" else: return self.nachname def get_full_name(self): if self.vorname: return f"{self.vorname} {self.nachname}" else: return self.nachname def get_aktive_verpachtungen(self): """Get all active leases for this tenant""" return self.neue_verpachtungen.filter(status="aktiv") def get_gesamt_pachtflaeche(self): """Calculate total leased area""" return ( self.neue_verpachtungen.filter(status="aktiv").aggregate( total=models.Sum("verpachtete_flaeche") )["total"] or 0 ) def get_gesamt_pachtzins(self): """Calculate total annual rent""" return ( self.neue_verpachtungen.filter(status="aktiv").aggregate( total=models.Sum("pachtzins_pauschal") )["total"] or 0 ) class Land(models.Model): """Landverwaltung für verpachtete Ländereien""" id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) # Grundlegende Identifikation lfd_nr = models.CharField(max_length=20, unique=True, verbose_name="Lfd. Nr.") ew_nummer = models.CharField( max_length=50, null=True, blank=True, verbose_name="EW-Nummer" ) grundbuchblatt = models.CharField( max_length=50, null=True, blank=True, verbose_name="Grundbuchblatt" ) # Gerichtliche Zuständigkeit amtsgericht = models.CharField(max_length=100, verbose_name="Amtsgericht") # Verwaltungsstruktur gemeinde = models.CharField(max_length=100, verbose_name="Gemeinde") gemarkung = models.CharField(max_length=100, verbose_name="Gemarkung") flur = models.CharField(max_length=50, verbose_name="Flur") flurstueck = models.CharField(max_length=50, verbose_name="Flurstück") adresse = models.CharField( max_length=200, null=True, blank=True, verbose_name="Adresse/Ortsangabe" ) # Flächenangaben groesse_qm = models.DecimalField( max_digits=12, decimal_places=2, verbose_name="Größe in qm", validators=[MinValueValidator(0.01)], ) # Landnutzung gruenland_qm = models.DecimalField( max_digits=12, decimal_places=2, default=0, verbose_name="Grünland (qm)", validators=[MinValueValidator(0)], ) acker_qm = models.DecimalField( max_digits=12, decimal_places=2, default=0, verbose_name="Acker (qm)", validators=[MinValueValidator(0)], ) wald_qm = models.DecimalField( max_digits=12, decimal_places=2, default=0, verbose_name="Wald (qm)", validators=[MinValueValidator(0)], ) sonstiges_qm = models.DecimalField( max_digits=12, decimal_places=2, default=0, verbose_name="Sonstiges (qm)", validators=[MinValueValidator(0)], ) # Verpachtung (Legacy-Felder für Kompatibilität) verpachtete_gesamtflaeche = models.DecimalField( max_digits=12, decimal_places=2, verbose_name="Verpachtete Gesamtfläche (qm)", validators=[MinValueValidator(0)], ) flaeche_alte_liste = models.DecimalField( max_digits=12, decimal_places=2, null=True, blank=True, verbose_name="Fläche alte Liste (qm)", ) verp_flaeche_aktuell = models.DecimalField( max_digits=12, decimal_places=2, verbose_name="Verp. Fläche aktuell (qm)", validators=[MinValueValidator(0)], ) # Aktuelle Verpachtung (Neue Struktur) aktueller_paechter = models.ForeignKey( "Paechter", on_delete=models.SET_NULL, null=True, blank=True, verbose_name="Aktueller Pächter", related_name="gepachtete_laendereien", ) paechter_name = models.CharField( max_length=150, null=True, blank=True, verbose_name="Pächter Name" ) paechter_anschrift = models.TextField( null=True, blank=True, verbose_name="Pächter Anschrift" ) pachtbeginn = models.DateField(null=True, blank=True, verbose_name="Pachtbeginn") pachtende = models.DateField(null=True, blank=True, verbose_name="Pachtende") verlaengerung_klausel = models.BooleanField( default=False, verbose_name="Automatische Verlängerung" ) # Pachtzins und Zahlungsweise ZAHLUNGSWEISE_CHOICES = [ ("jaehrlich", "Jährlich"), ("halbjaehrlich", "Halbjährlich"), ("vierteljaehrlich", "Vierteljährlich"), ("monatlich", "Monatlich"), ] zahlungsweise = models.CharField( max_length=20, choices=ZAHLUNGSWEISE_CHOICES, default="jaehrlich", null=True, blank=True, verbose_name="Zahlungsweise", ) pachtzins_pro_ha = models.DecimalField( max_digits=12, decimal_places=2, null=True, blank=True, verbose_name="Pachtzins pro ha (€)", validators=[MinValueValidator(0)], ) pachtzins_pauschal = models.DecimalField( max_digits=12, decimal_places=2, null=True, blank=True, verbose_name="Pachtzins pauschal/Jahr (€)", validators=[MinValueValidator(0)], ) # Umsatzsteuer ust_option = models.BooleanField(default=False, verbose_name="USt-Option") ust_satz = models.DecimalField( max_digits=4, decimal_places=2, default=19.00, null=True, blank=True, verbose_name="USt-Satz (%)" ) # Umlagen (Durchreichungen) grundsteuer_umlage = models.BooleanField( default=True, verbose_name="Grundsteuer umlagefähig" ) versicherungen_umlage = models.BooleanField( default=True, verbose_name="Versicherungen umlagefähig" ) verbandsbeitraege_umlage = models.BooleanField( default=True, verbose_name="Verbandsbeiträge umlagefähig" ) jagdpacht_anteil_umlage = models.BooleanField( default=False, verbose_name="Jagdpachtanteile umlagefähig" ) # Steuern und Abgaben anteil_grundsteuer = models.DecimalField( max_digits=8, decimal_places=2, null=True, blank=True, verbose_name="Anteil Grundsteuer (%)", ) anteil_lwk = models.DecimalField( max_digits=8, decimal_places=2, null=True, blank=True, verbose_name="Anteil LWK (%)", ) # Status aktiv = models.BooleanField(default=True, verbose_name="Aktiv") notizen = models.TextField( null=True, blank=True, verbose_name="Ergänzende Kommentare" ) # Zeitstempel erstellt_am = models.DateTimeField(auto_now_add=True) aktualisiert_am = models.DateTimeField(auto_now=True) class Meta: verbose_name = "Land" verbose_name_plural = "Ländereien" ordering = ["gemeinde", "gemarkung", "flur", "flurstueck"] def __str__(self): return f"{self.gemeinde} - {self.gemarkung} Flur {self.flur} Flurstück {self.flurstueck}" def get_gesamtflaeche(self): """Berechnet die Gesamtfläche aus allen Nutzungsarten""" return self.gruenland_qm + self.acker_qm + self.wald_qm + self.sonstiges_qm def get_verpachtungsgrad(self): """Berechnet den Verpachtungsgrad in Prozent""" if self.get_gesamtflaeche() > 0: return ( self.get_verpachtete_flaeche_aktuell() / self.get_gesamtflaeche() ) * 100 return 0 def get_verpachtete_flaeche_aktuell(self): """Gibt die aktuell verpachtete Fläche zurück (aus neuen Verpachtungen oder Legacy)""" from django.db.models import Sum # Priorität 1: Neue Verpachtungen (LandVerpachtung) neue_total = ( self.neue_verpachtungen.filter(status="aktiv").aggregate( total=Sum("verpachtete_flaeche") )["total"] or 0 ) if neue_total > 0: return neue_total # Priorität 2: Einzelverpachtung im Land-Model (verp_flaeche_aktuell) if self.verp_flaeche_aktuell and self.verp_flaeche_aktuell > 0: return self.verp_flaeche_aktuell # No legacy system - return neue_total (could be 0) return neue_total def get_verfuegbare_flaeche(self): """Berechnet die noch verfügbare Fläche für neue Verpachtungen""" return self.groesse_qm - self.get_verpachtete_flaeche_aktuell() def get_verpachtungsgrad_neu(self): """Berechnet den Verpachtungsgrad basierend auf neuen Verpachtungen""" if self.groesse_qm and self.groesse_qm > 0: return (self.get_verpachtete_flaeche_aktuell() / self.groesse_qm) * 100 return 0 def get_steuer_gesamt(self): """Berechnet den Gesamtsteueranteil""" grundsteuer = self.anteil_grundsteuer or 0 lwk = self.anteil_lwk or 0 return grundsteuer + lwk def _qm_to_hektar(self, qm_value): """Hilfsmethode zur Umrechnung von qm in Hektar""" from decimal import ROUND_HALF_UP, Decimal if qm_value and qm_value > 0: # Umrechnung: 1 Hektar = 10.000 qm hektar = Decimal(str(qm_value)) / Decimal("10000") # Runden auf 2 Nachkommastellen return hektar.quantize(Decimal("0.01"), rounding=ROUND_HALF_UP) return Decimal("0.00") @property def groesse_hektar(self): """Berechnet die Gesamtgröße in Hektar""" return self._qm_to_hektar(self.groesse_qm) @property def gruenland_hektar(self): """Berechnet die Grünlandfläche in Hektar""" return self._qm_to_hektar(self.gruenland_qm) @property def acker_hektar(self): """Berechnet die Ackerfläche in Hektar""" return self._qm_to_hektar(self.acker_qm) @property def wald_hektar(self): """Berechnet die Waldfläche in Hektar""" return self._qm_to_hektar(self.wald_qm) @property def sonstiges_hektar(self): """Berechnet die sonstige Fläche in Hektar""" return self._qm_to_hektar(self.sonstiges_qm) @property def verpachtete_gesamtflaeche_hektar(self): """Berechnet die verpachtete Gesamtfläche in Hektar""" return self._qm_to_hektar(self.verpachtete_gesamtflaeche) @property def flaeche_alte_liste_hektar(self): """Berechnet die Fläche aus alter Liste in Hektar""" return self._qm_to_hektar(self.flaeche_alte_liste) @property def verp_flaeche_aktuell_hektar(self): """Berechnet die aktuell verpachtete Fläche in Hektar""" return self._qm_to_hektar(self.verp_flaeche_aktuell) def get_gesamtflaeche_hektar(self): """Berechnet die Gesamtfläche aus allen Nutzungsarten in Hektar""" return self._qm_to_hektar(self.get_gesamtflaeche()) def get_verpachtete_flaeche_aktuell_hektar(self): """Berechnet die aktuell verpachtete Fläche basierend auf aktiven Verpachtungen in Hektar""" return self._qm_to_hektar(self.get_verpachtete_flaeche_aktuell()) class LandVerpachtung(models.Model): """Neue Verpachtungsverträge - mehrere pro Land möglich""" STATUS_CHOICES = [ ("aktiv", "Aktiv"), ("beendet", "Beendet"), ("gekuendigt", "Gekündigt"), ("verlängert", "Verlängert"), ] id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) # Grundlegende Verknüpfungen land = models.ForeignKey( Land, on_delete=models.CASCADE, related_name="neue_verpachtungen", verbose_name="Länderei", ) paechter = models.ForeignKey( Paechter, on_delete=models.CASCADE, related_name="neue_verpachtungen", verbose_name="Pächter", ) # Vertragsdaten vertragsnummer = models.CharField( max_length=50, unique=True, verbose_name="Vertragsnummer" ) pachtbeginn = models.DateField(verbose_name="Pachtbeginn") pachtende = models.DateField(null=True, blank=True, verbose_name="Pachtende") verlaengerung_klausel = models.BooleanField( default=False, verbose_name="Automatische Verlängerung" ) # Flächenangaben verpachtete_flaeche = models.DecimalField( max_digits=12, decimal_places=2, verbose_name="Verpachtete Fläche (qm)", validators=[MinValueValidator(0.01)], ) # Pachtzins pachtzins_pauschal = models.DecimalField( max_digits=12, decimal_places=2, verbose_name="Pachtzins pauschal/Jahr (€)", validators=[MinValueValidator(0)], ) pachtzins_pro_ha = models.DecimalField( max_digits=12, decimal_places=2, null=True, blank=True, verbose_name="Pachtzins pro ha (€)", validators=[MinValueValidator(0)], ) # Zahlungsweise ZAHLUNGSWEISE_CHOICES = [ ("jaehrlich", "Jährlich"), ("halbjaehrlich", "Halbjährlich"), ("vierteljaehrlich", "Vierteljährlich"), ("monatlich", "Monatlich"), ] zahlungsweise = models.CharField( max_length=20, choices=ZAHLUNGSWEISE_CHOICES, default="jaehrlich", verbose_name="Zahlungsweise", ) # Umsatzsteuer ust_option = models.BooleanField(default=False, verbose_name="USt-Option") ust_satz = models.DecimalField( max_digits=4, decimal_places=2, default=19.00, verbose_name="USt-Satz (%)" ) # Umlagen (Durchreichungen) grundsteuer_umlage = models.BooleanField( default=True, verbose_name="Grundsteuer umlagefähig" ) versicherungen_umlage = models.BooleanField( default=True, verbose_name="Versicherungen umlagefähig" ) verbandsbeitraege_umlage = models.BooleanField( default=True, verbose_name="Verbandsbeiträge umlagefähig" ) jagdpacht_anteil_umlage = models.BooleanField( default=False, verbose_name="Jagdpachtanteile umlagefähig" ) # Status und Notizen status = models.CharField( max_length=20, choices=STATUS_CHOICES, default="aktiv", verbose_name="Status" ) bemerkungen = models.TextField(null=True, blank=True, verbose_name="Bemerkungen") # Zeitstempel erstellt_am = models.DateTimeField(auto_now_add=True) aktualisiert_am = models.DateTimeField(auto_now=True) class Meta: verbose_name = "Landverpachtung" verbose_name_plural = "Landverpachtungen" ordering = ["-pachtbeginn", "land"] def __str__(self): return f"{self.land} - {self.paechter} ({self.vertragsnummer})" @property def verpachtete_flaeche_hektar(self): """Berechnet die verpachtete Fläche in Hektar""" from decimal import ROUND_HALF_UP, Decimal if self.verpachtete_flaeche and self.verpachtete_flaeche > 0: hektar = Decimal(str(self.verpachtete_flaeche)) / Decimal("10000") return hektar.quantize(Decimal("0.01"), rounding=ROUND_HALF_UP) return Decimal("0.00") def is_aktiv(self): """Prüft ob der Vertrag noch aktiv ist""" from datetime import date heute = date.today() pachtbeginn_date = ensure_date(self.pachtbeginn) pachtende_date = ensure_date(self.pachtende) if not pachtbeginn_date: return False if pachtende_date: return pachtbeginn_date <= heute <= pachtende_date return pachtbeginn_date <= heute # Unbefristet def get_restlaufzeit_tage(self): """Berechnet die Restlaufzeit in Tagen""" from datetime import date heute = date.today() pachtende_date = ensure_date(self.pachtende) if pachtende_date and pachtende_date > heute: return (pachtende_date - heute).days return None # Unbefristet @property def ust_pacht_betrag(self): """Berechnet die USt auf Pacht (falls optiert)""" from decimal import ROUND_HALF_UP, Decimal if self.ust_option and self.pachtzins_pauschal: ust_betrag = ( Decimal(str(self.pachtzins_pauschal)) * Decimal(str(self.ust_satz)) / Decimal("100") ) return ust_betrag.quantize(Decimal("0.01"), rounding=ROUND_HALF_UP) return Decimal("0.00") def save(self, *args, **kwargs): """Override save to trigger Abrechnung updates""" is_new = self.pk is None old_instance = None if not is_new: try: old_instance = LandVerpachtung.objects.get(pk=self.pk) except LandVerpachtung.DoesNotExist: old_instance = None super().save(*args, **kwargs) # Update Abrechnungen after save self._update_abrechnungen(old_instance, is_new) def _update_abrechnungen(self, old_instance, is_new): """Update LandAbrechnung records when Verpachtung changes""" from datetime import date # Determine affected years years_to_update = set() pachtbeginn_year = get_year_from_date(self.pachtbeginn) if pachtbeginn_year: years_to_update.add(pachtbeginn_year) pachtende_year = get_year_from_date(self.pachtende) if pachtende_year: years_to_update.add(pachtende_year) # If updated, check old dates too if old_instance: old_pachtbeginn_year = get_year_from_date(old_instance.pachtbeginn) if old_pachtbeginn_year: years_to_update.add(old_pachtbeginn_year) old_pachtende_year = get_year_from_date(old_instance.pachtende) if old_pachtende_year: years_to_update.add(old_pachtende_year) # Add current year if contract is active if self.is_aktiv(): years_to_update.add(date.today().year) # Update each affected year for year in years_to_update: self._update_abrechnung_for_year(year, old_instance, is_new) def _update_abrechnung_for_year(self, year, old_instance, is_new): """Update or create LandAbrechnung for specific year""" from datetime import date from decimal import Decimal # Get or create Abrechnung for this year abrechnung, created = LandAbrechnung.objects.get_or_create( land=self.land, abrechnungsjahr=year, defaults={ "pacht_vereinnahmt": Decimal("0.00"), "umlagen_vereinnahmt": Decimal("0.00"), "bemerkungen": f"Automatisch erstellt für {self.vertragsnummer}", }, ) # Calculate rent for this year rent_for_year = self._calculate_rent_for_year(year) umlage_for_year = self._calculate_umlage_for_year(year) # Update or add to existing amounts if created or is_new: # New Abrechnung or new Verpachtung abrechnung.pacht_vereinnahmt += rent_for_year abrechnung.umlagen_vereinnahmt += umlage_for_year change_note = f"Neue Verpachtung {self.vertragsnummer} hinzugefügt" else: # Update existing - calculate difference old_rent = ( old_instance._calculate_rent_for_year(year) if old_instance else Decimal("0.00") ) old_umlage = ( old_instance._calculate_umlage_for_year(year) if old_instance else Decimal("0.00") ) rent_diff = rent_for_year - old_rent umlage_diff = umlage_for_year - old_umlage abrechnung.pacht_vereinnahmt += rent_diff abrechnung.umlagen_vereinnahmt += umlage_diff if rent_diff != 0 or umlage_diff != 0: change_note = f"Verpachtung {self.vertragsnummer} geändert: Pacht {rent_diff:+.2f}€, Umlagen {umlage_diff:+.2f}€" else: change_note = f"Verpachtung {self.vertragsnummer} aktualisiert (keine Betragsänderung)" # Add change tracking to bemerkungen (if significant change) if change_note and ("hinzugefügt" in change_note or "geändert" in change_note): if abrechnung.bemerkungen: abrechnung.bemerkungen += ( f"\n[{date.today().strftime('%d.%m.%Y')}] {change_note}" ) else: abrechnung.bemerkungen = ( f"[{date.today().strftime('%d.%m.%Y')}] {change_note}" ) abrechnung.save() def _calculate_rent_for_year(self, year): """Calculate rent amount for specific year""" from datetime import date from decimal import Decimal from django.utils.dateparse import parse_date # Helper function to convert date strings to date objects def ensure_date(date_value): if not date_value: return None if isinstance(date_value, str): return parse_date(date_value) return date_value if not self.pachtzins_pauschal or not self.pachtbeginn: return Decimal("0.00") # Check if contract is active in this year year_start = date(year, 1, 1) year_end = date(year, 12, 31) # Convert dates to ensure they are date objects pachtbeginn_date = ensure_date(self.pachtbeginn) pachtende_date = ensure_date(self.pachtende) if not pachtbeginn_date: return Decimal("0.00") contract_start = max(pachtbeginn_date, year_start) contract_end = min(pachtende_date or year_end, year_end) if contract_start > contract_end: return Decimal("0.00") # No overlap # Calculate proportion of year days_in_year = (year_end - year_start).days + 1 days_active = (contract_end - contract_start).days + 1 proportion = Decimal(str(days_active)) / Decimal(str(days_in_year)) return Decimal(str(self.pachtzins_pauschal)) * proportion def _calculate_umlage_for_year(self, year): """Calculate Umlage amount for specific year based on what can be passed through""" from decimal import Decimal # This would need to be calculated based on actual costs and what's umlagefähig # For now, return 0 - this can be enhanced later with actual cost calculation return Decimal("0.00") def delete(self, *args, **kwargs): """Override delete to update Abrechnungen when Verpachtung is removed""" # Calculate what needs to be removed from Abrechnungen years_to_update = set() pachtbeginn_year = get_year_from_date(self.pachtbeginn) if pachtbeginn_year: years_to_update.add(pachtbeginn_year) pachtende_year = get_year_from_date(self.pachtende) if pachtende_year: years_to_update.add(pachtende_year) # Remove from Abrechnungen before deleting for year in years_to_update: try: abrechnung = LandAbrechnung.objects.get( land=self.land, abrechnungsjahr=year ) rent_to_remove = self._calculate_rent_for_year(year) umlage_to_remove = self._calculate_umlage_for_year(year) abrechnung.pacht_vereinnahmt -= rent_to_remove abrechnung.umlagen_vereinnahmt -= umlage_to_remove # Add deletion note from datetime import date change_note = f"Verpachtung {self.vertragsnummer} gelöscht: Pacht -{rent_to_remove:.2f}€, Umlagen -{umlage_to_remove:.2f}€" if abrechnung.bemerkungen: abrechnung.bemerkungen += ( f"\n[{date.today().strftime('%d.%m.%Y')}] {change_note}" ) else: abrechnung.bemerkungen = ( f"[{date.today().strftime('%d.%m.%Y')}] {change_note}" ) abrechnung.save() except LandAbrechnung.DoesNotExist: pass # No Abrechnung to update super().delete(*args, **kwargs) class LandAbrechnung(models.Model): """Jahresabrechnung für Ländereien""" id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) land = models.ForeignKey( Land, on_delete=models.CASCADE, related_name="abrechnungen", verbose_name="Länderei", ) abrechnungsjahr = models.IntegerField( verbose_name="Abrechnungsjahr", validators=[MinValueValidator(2000)] ) # Einnahmen pacht_vereinnahmt = models.DecimalField( max_digits=12, decimal_places=2, default=0, verbose_name="Pacht vereinnahmt (€)", validators=[MinValueValidator(0)], ) umlagen_vereinnahmt = models.DecimalField( max_digits=12, decimal_places=2, default=0, verbose_name="Umlagen vereinnahmt (€)", validators=[MinValueValidator(0)], ) sonstige_einnahmen = models.DecimalField( max_digits=12, decimal_places=2, default=0, verbose_name="Sonstige Einnahmen (€)", validators=[MinValueValidator(0)], ) # Zahlungstermine (optional) zahlungen = models.JSONField( null=True, blank=True, verbose_name="Zahlungstermine", help_text="Liste von Objekten {datum, betrag, art}", ) # Ausgaben grundsteuer_bescheid_nr = models.CharField( max_length=80, null=True, blank=True, verbose_name="Grundsteuer-Bescheid Nr." ) grundsteuer_betrag = models.DecimalField( max_digits=12, decimal_places=2, default=0, verbose_name="Grundsteuer Betrag (€)", validators=[MinValueValidator(0)], ) versicherungen_betrag = models.DecimalField( max_digits=12, decimal_places=2, default=0, verbose_name="Versicherungen Betrag (€)", validators=[MinValueValidator(0)], ) verbandsbeitraege_betrag = models.DecimalField( max_digits=12, decimal_places=2, default=0, verbose_name="Verbandsbeiträge Betrag (€)", validators=[MinValueValidator(0)], ) sonstige_abgaben_betrag = models.DecimalField( max_digits=12, decimal_places=2, default=0, verbose_name="Sonstige öffentliche Abgaben (€)", validators=[MinValueValidator(0)], ) instandhaltung_betrag = models.DecimalField( max_digits=12, decimal_places=2, default=0, verbose_name="Instandhaltung/Reparaturen (€)", validators=[MinValueValidator(0)], ) verwaltung_recht_betrag = models.DecimalField( max_digits=12, decimal_places=2, default=0, verbose_name="Verwaltung/Recht (€)", validators=[MinValueValidator(0)], ) # Umsatzsteuer/Vorsteuer vorsteuer_aus_umlagen = models.DecimalField( max_digits=12, decimal_places=2, default=0, verbose_name="Vorsteuer aus umgelegten Kosten (€)", validators=[MinValueValidator(0)], ) # Sonstiges offene_posten = models.DecimalField( max_digits=12, decimal_places=2, default=0, verbose_name="Offene Posten (€)" ) bemerkungen = models.TextField( null=True, blank=True, verbose_name="Bemerkungen Abrechnung" ) # Dokumente pachtvertrag_datei = models.FileField( upload_to="land_abrechnungen/vertraege/", null=True, blank=True, verbose_name="Pachtvertrag (Datei)", ) grundsteuer_bescheid_datei = models.FileField( upload_to="land_abrechnungen/bescheide/", null=True, blank=True, verbose_name="Grundsteuerbescheid (Datei)", ) versicherungsnachweis_datei = models.FileField( upload_to="land_abrechnungen/versicherungen/", null=True, blank=True, verbose_name="Versicherungsnachweis (Datei)", ) # Zeitstempel erstellt_am = models.DateTimeField(auto_now_add=True) aktualisiert_am = models.DateTimeField(auto_now=True) class Meta: verbose_name = "Landabrechnung" verbose_name_plural = "Landabrechnungen" ordering = ["-abrechnungsjahr", "land__gemeinde", "land__gemarkung"] unique_together = ["land", "abrechnungsjahr"] # Ein Jahr pro Land def __str__(self): return f"{self.land} - Abrechnung {self.abrechnungsjahr}" @property def einnahmen_gesamt(self): """Berechnet die Gesamteinnahmen""" from decimal import Decimal return ( self.pacht_vereinnahmt + self.umlagen_vereinnahmt + self.sonstige_einnahmen ) @property def ausgaben_gesamt(self): """Berechnet die Gesamtausgaben""" from decimal import Decimal return ( self.grundsteuer_betrag + self.versicherungen_betrag + self.verbandsbeitraege_betrag + self.sonstige_abgaben_betrag + self.instandhaltung_betrag + self.verwaltung_recht_betrag ) @property def nettoergebnis(self): """Berechnet das Nettoergebnis""" return self.einnahmen_gesamt - self.ausgaben_gesamt @property def ust_pacht_betrag(self): """Berechnet die USt auf Pacht (falls optiert)""" from decimal import ROUND_HALF_UP, Decimal if self.land.ust_option and self.pacht_vereinnahmt: ust = self.pacht_vereinnahmt * (self.land.ust_satz / Decimal("100")) return ust.quantize(Decimal("0.01"), rounding=ROUND_HALF_UP) return Decimal("0.00") class DokumentLink(models.Model): KONTEXT_CHOICES = [ ("pachtvertrag", "Pachtvertrag"), ("antrag", "Antrag"), ("verwendungsnachweis", "Verwendungsnachweis"), ("rechnung", "Rechnung"), ("vertrag", "Vertrag"), ("bericht", "Bericht"), ("landkarte", "Landkarte"), ("kataster", "Kataster"), ("anderes", "Anderes"), ] id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) paperless_document_id = models.IntegerField() kontext = models.CharField( max_length=30, choices=KONTEXT_CHOICES, default="anderes" ) titel = models.CharField(max_length=255) beschreibung = models.TextField(null=True, blank=True) # Verknüpfungen zu anderen Modellen (als Strings für Flexibilität) verpachtung_id = models.UUIDField( null=True, blank=True, verbose_name="Verpachtung ID (Legacy)" ) land_verpachtung_id = models.UUIDField( null=True, blank=True, verbose_name="Landverpachtung ID (Neu)" ) land_id = models.UUIDField(null=True, blank=True, verbose_name="Länderei ID") paechter_id = models.UUIDField(null=True, blank=True, verbose_name="Pächter ID") destinataer_id = models.UUIDField( null=True, blank=True, verbose_name="Destinatär ID" ) foerderung_id = models.UUIDField(null=True, blank=True, verbose_name="Förderung ID") rentmeister_id = models.UUIDField( null=True, blank=True, verbose_name="Rentmeister ID" ) abrechnung_id = models.UUIDField( null=True, blank=True, verbose_name="Abrechnung ID" ) class Meta: verbose_name = "Dokument" verbose_name_plural = "Dokumente" ordering = ["titel"] def __str__(self): return f"{self.titel} ({self.get_kontext_display()})" def get_paperless_url(self): """Gibt die URL zum Dokument in Paperless zurück (über Django Redirect)""" return f"/api/paperless/documents/{self.paperless_document_id}/" def get_paperless_thumbnail_url(self): """Gibt die URL zum Thumbnail in Paperless zurück""" from django.conf import settings if settings.PAPERLESS_API_URL: return f"{settings.PAPERLESS_API_URL}/api/documents/{self.paperless_document_id}/thumb/" return None def get_verpachtung(self): """Gibt die verknüpfte Verpachtung zurück""" if self.verpachtung_id: try: return LandVerpachtung.objects.get(pk=self.verpachtung_id) except LandVerpachtung.DoesNotExist: return None return None def get_land(self): """Gibt die verknüpfte Länderei zurück""" if self.land_id: try: return Land.objects.get(pk=self.land_id) except Land.DoesNotExist: return None return None def get_paechter(self): """Gibt den verknüpften Pächter zurück""" if self.paechter_id: try: return Paechter.objects.get(pk=self.paechter_id) except Paechter.DoesNotExist: return None return None def get_destinataer(self): """Gibt den verknüpften Destinatär zurück""" if self.destinataer_id: try: from stiftung.models import Destinataer return Destinataer.objects.get(pk=self.destinataer_id) except Destinataer.DoesNotExist: return None return None def get_foerderung(self): """Gibt die verknüpfte Förderung zurück""" if self.foerderung_id: try: from stiftung.models import Foerderung return Foerderung.objects.get(pk=self.foerderung_id) except Foerderung.DoesNotExist: return None return None def get_land_verpachtung(self): """Gibt die verknüpfte neue Landverpachtung zurück""" if self.land_verpachtung_id: try: return LandVerpachtung.objects.get(pk=self.land_verpachtung_id) except LandVerpachtung.DoesNotExist: return None return None