fix: configure CI database connection properly

- Add dotenv loading to Django settings
- Update CI workflow to use correct environment variables
- Set POSTGRES_* variables instead of DATABASE_URL
- Add environment variables to all Django management commands
- Fixes CI test failures due to database connection issues
This commit is contained in:
Stiftung Development
2025-09-06 18:47:23 +02:00
parent dcc91b9f49
commit 35ba089a84
64 changed files with 7040 additions and 1419 deletions

View File

@@ -4,6 +4,8 @@ from io import StringIO
from django.db import models
from django.core.validators import MinValueValidator, MaxValueValidator
from django.utils import timezone
from dateutil.relativedelta import relativedelta
from stiftung.utils.date_utils import ensure_date, get_year_from_date
class CSVImport(models.Model):
"""Track CSV import operations for audit purposes"""
@@ -129,18 +131,18 @@ class Paechter(models.Model):
def get_aktive_verpachtungen(self):
"""Get all active leases for this tenant"""
return self.verpachtung_set.filter(status='aktiv')
return self.neue_verpachtungen.filter(status='aktiv')
def get_gesamt_pachtflaeche(self):
"""Calculate total leased area"""
return self.verpachtung_set.filter(status='aktiv').aggregate(
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.verpachtung_set.filter(status='aktiv').aggregate(
total=models.Sum('pachtzins_jaehrlich')
return self.neue_verpachtungen.filter(status='aktiv').aggregate(
total=models.Sum('pachtzins_pauschal')
)['total'] or 0
class Destinataer(models.Model):
@@ -505,12 +507,8 @@ class Land(models.Model):
if self.verp_flaeche_aktuell and self.verp_flaeche_aktuell > 0:
return self.verp_flaeche_aktuell
# Fallback: Legacy Verpachtungen (für Rückwärtskompatibilität)
legacy_total = self.verpachtung_set.filter(status='aktiv').aggregate(
total=Sum('verpachtete_flaeche')
)['total'] or 0
return legacy_total
# 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"""
@@ -690,17 +688,29 @@ class LandVerpachtung(models.Model):
def is_aktiv(self):
"""Prüft ob der Vertrag noch aktiv ist"""
from datetime import date
heute = date.today()
if self.pachtende:
return self.pachtbeginn <= heute <= self.pachtende
return self.pachtbeginn <= heute # Unbefristet
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()
if self.pachtende and self.pachtende > heute:
return (self.pachtende - heute).days
pachtende_date = ensure_date(self.pachtende)
if pachtende_date and pachtende_date > heute:
return (pachtende_date - heute).days
return None # Unbefristet
@property
@@ -708,10 +718,199 @@ class LandVerpachtung(models.Model):
"""Berechnet die USt auf Pacht (falls optiert)"""
from decimal import Decimal, ROUND_HALF_UP
if self.ust_option and self.pachtzins_pauschal:
ust = self.pachtzins_pauschal * (self.ust_satz / Decimal('100'))
return ust.quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)
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 decimal import Decimal
from datetime import date
# 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 decimal import Decimal
from datetime import date
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"""
@@ -877,99 +1076,6 @@ class LandAbrechnung(models.Model):
return ust.quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)
return Decimal('0.00')
class Verpachtung(models.Model):
"""Verpachtungsverträge für Ländereien"""
STATUS_CHOICES = [
('aktiv', 'Aktiv'),
('beendet', 'Beendet'),
('gekuendigt', 'Gekündigt'),
('verlängert', 'Verlängert'),
]
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
# Verpachtete Ländereien
land = models.ForeignKey(Land, on_delete=models.CASCADE, verbose_name="Land")
paechter = models.ForeignKey(Paechter, on_delete=models.CASCADE, 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(verbose_name="Pachtende")
verlaengerung = models.DateField(null=True, blank=True, verbose_name="Verlängerung bis")
# Finanzielle Bedingungen
pachtzins_pro_qm = models.DecimalField(
max_digits=8,
decimal_places=4,
verbose_name="Pachtzins pro qm (€)"
)
pachtzins_jaehrlich = models.DecimalField(
max_digits=12,
decimal_places=2,
verbose_name="Jährlicher Pachtzins (€)"
)
# Flächenangaben
verpachtete_flaeche = models.DecimalField(
max_digits=12,
decimal_places=2,
verbose_name="Verpachtete Fläche (qm)"
)
# Status
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='aktiv')
# Dokumentation
verwendungsnachweis = models.ForeignKey('DokumentLink', null=True, blank=True, on_delete=models.SET_NULL, verbose_name="Verwendungsnachweis")
bemerkungen = 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 = "Verpachtung"
verbose_name_plural = "Verpachtungen"
ordering = ['-pachtbeginn']
def __str__(self):
return f"{self.land} - {self.paechter} ({self.vertragsnummer})"
def get_vertragsdauer_tage(self):
"""Berechnet die Vertragsdauer in Tagen"""
from datetime import date
if self.pachtende:
return (self.pachtende - self.pachtbeginn).days
return 0
def get_restlaufzeit_tage(self):
"""Berechnet die Restlaufzeit in Tagen"""
from datetime import date
heute = date.today()
if self.pachtende and self.pachtende > heute:
return (self.pachtende - heute).days
return 0
def is_aktiv(self):
"""Prüft ob der Vertrag noch aktiv ist"""
from datetime import date
heute = date.today()
return self.pachtbeginn <= heute <= self.pachtende
@property
def verpachtete_flaeche_hektar(self):
"""Berechnet die verpachtete Fläche in Hektar"""
from decimal import Decimal, ROUND_HALF_UP
if self.verpachtete_flaeche and self.verpachtete_flaeche > 0:
# Umrechnung: 1 Hektar = 10.000 qm
hektar = Decimal(str(self.verpachtete_flaeche)) / Decimal('10000')
# Runden auf 2 Nachkommastellen
return hektar.quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)
return Decimal('0.00')
class DokumentLink(models.Model):
KONTEXT_CHOICES = [
('pachtvertrag', 'Pachtvertrag'),
@@ -997,6 +1103,7 @@ class DokumentLink(models.Model):
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"
@@ -1007,17 +1114,14 @@ class DokumentLink(models.Model):
return f"{self.titel} ({self.get_kontext_display()})"
def get_paperless_url(self):
"""Gibt die URL zum Dokument in Paperless zurück"""
from django.conf import settings
if settings.PAPERLESS_API_URL:
return f"{settings.PAPERLESS_API_URL}/documents/{self.paperless_document_id}/"
return None
"""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 f"{settings.PAPERLESS_API_URL}/api/paperless/documents/{self.paperless_document_id}/thumb/"
return None
def get_verpachtung(self):
@@ -1135,6 +1239,7 @@ class DestinataerUnterstuetzung(models.Model):
"""Geplante/ausgeführte Unterstützungszahlungen an Destinatäre"""
STATUS_CHOICES = [
('geplant', 'Geplant'),
('faellig', 'Fällig'),
('in_bearbeitung', 'In Bearbeitung'),
('ausgezahlt', 'Ausgezahlt'),
('storniert', 'Storniert'),
@@ -1147,6 +1252,17 @@ class DestinataerUnterstuetzung(models.Model):
faellig_am = models.DateField(verbose_name='Fällig am')
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='geplant', verbose_name='Status')
beschreibung = models.CharField(max_length=255, blank=True, verbose_name='Beschreibung')
# Enhanced fields for recurrent payments and IBAN tracking
empfaenger_iban = models.CharField(max_length=34, blank=True, verbose_name='Empfänger IBAN')
empfaenger_name = models.CharField(max_length=200, blank=True, verbose_name='Empfänger Name')
verwendungszweck = models.CharField(max_length=140, blank=True, verbose_name='Verwendungszweck')
ausgezahlt_am = models.DateField(null=True, blank=True, verbose_name='Ausgezahlt am')
ausgezahlt_von = models.ForeignKey('auth.User', on_delete=models.SET_NULL, null=True, blank=True, verbose_name='Ausgezahlt von')
# Link to recurrent payment template if this was auto-generated
wiederkehrend_von = models.ForeignKey('UnterstuetzungWiederkehrend', on_delete=models.SET_NULL, null=True, blank=True, verbose_name='Wiederkehrende Zahlung')
erstellt_am = models.DateTimeField(auto_now_add=True)
aktualisiert_am = models.DateTimeField(auto_now=True)
@@ -1157,10 +1273,106 @@ class DestinataerUnterstuetzung(models.Model):
indexes = [
models.Index(fields=['status', 'faellig_am']),
models.Index(fields=['destinataer', 'status']),
models.Index(fields=['wiederkehrend_von']),
]
def __str__(self):
return f"{self.destinataer.get_full_name()} {self.betrag} am {self.faellig_am} ({self.get_status_display()})"
def is_overdue(self):
"""Check if payment is overdue"""
from django.utils import timezone
return self.faellig_am < timezone.now().date() and self.status in ['geplant', 'faellig']
def can_be_marked_paid(self):
"""Check if payment can be marked as paid"""
return self.status in ['geplant', 'faellig', 'in_bearbeitung']
class UnterstuetzungWiederkehrend(models.Model):
"""Template for recurring support payments"""
INTERVALL_CHOICES = [
('monatlich', 'Monatlich'),
('quartalsweise', 'Vierteljährlich'),
('halbjaehrlich', 'Halbjährlich'),
('jaehrlich', 'Jährlich'),
]
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
destinataer = models.ForeignKey('Destinataer', on_delete=models.CASCADE, related_name='wiederkehrende_unterstuetzungen', verbose_name='Destinatär')
konto = models.ForeignKey('StiftungsKonto', on_delete=models.PROTECT, verbose_name='Zahlungskonto')
betrag = models.DecimalField(max_digits=12, decimal_places=2, verbose_name='Betrag (€)')
intervall = models.CharField(max_length=20, choices=INTERVALL_CHOICES, verbose_name='Intervall')
beschreibung = models.CharField(max_length=255, blank=True, verbose_name='Beschreibung')
# IBAN and payment details
empfaenger_iban = models.CharField(max_length=34, verbose_name='Empfänger IBAN')
empfaenger_name = models.CharField(max_length=200, verbose_name='Empfänger Name')
verwendungszweck = models.CharField(max_length=140, blank=True, verbose_name='Verwendungszweck')
# Schedule settings
erste_zahlung_am = models.DateField(verbose_name='Erste Zahlung am')
letzte_zahlung_am = models.DateField(null=True, blank=True, verbose_name='Letzte Zahlung am (optional)')
naechste_generierung = models.DateField(verbose_name='Nächste Generierung')
aktiv = models.BooleanField(default=True, verbose_name='Aktiv')
erstellt_am = models.DateTimeField(auto_now_add=True)
erstellt_von = models.ForeignKey('auth.User', on_delete=models.SET_NULL, null=True, blank=True, verbose_name='Erstellt von')
class Meta:
verbose_name = 'Wiederkehrende Unterstützung'
verbose_name_plural = 'Wiederkehrende Unterstützungen'
ordering = ['-erstellt_am']
indexes = [
models.Index(fields=['aktiv', 'naechste_generierung']),
models.Index(fields=['destinataer', 'aktiv']),
]
def __str__(self):
return f"{self.destinataer.get_full_name()} {self.get_intervall_display()}{self.betrag}"
def generiere_naechste_zahlung(self):
"""Generate the next scheduled payment"""
from datetime import timedelta
from dateutil.relativedelta import relativedelta
if not self.aktiv:
return None
heute = timezone.now().date()
if self.naechste_generierung > heute:
return None # Not yet time to generate
# Check if we've reached the end date
if self.letzte_zahlung_am and self.naechste_generierung > self.letzte_zahlung_am:
return None
# Create the next payment
neue_zahlung = DestinataerUnterstuetzung.objects.create(
destinataer=self.destinataer,
konto=self.konto,
betrag=self.betrag,
faellig_am=self.naechste_generierung,
beschreibung=self.beschreibung or f"{self.get_intervall_display()} Unterstützung",
empfaenger_iban=self.empfaenger_iban,
empfaenger_name=self.empfaenger_name,
verwendungszweck=self.verwendungszweck,
wiederkehrend_von=self,
status='geplant'
)
# Calculate next generation date
if self.intervall == 'monatlich':
self.naechste_generierung = self.naechste_generierung + relativedelta(months=1)
elif self.intervall == 'quartalsweise':
self.naechste_generierung = self.naechste_generierung + relativedelta(months=3)
elif self.intervall == 'halbjaehrlich':
self.naechste_generierung = self.naechste_generierung + relativedelta(months=6)
elif self.intervall == 'jaehrlich':
self.naechste_generierung = self.naechste_generierung + relativedelta(years=1)
self.save()
return neue_zahlung
class DestinataerNotiz(models.Model):
@@ -1665,3 +1877,153 @@ class BackupJob(models.Model):
return f"{size:.1f} {unit}"
size /= 1024
return f"{size:.1f} TB"
class AppConfiguration(models.Model):
"""Application configuration settings that can be managed through the admin interface"""
SETTING_TYPE_CHOICES = [
('text', 'Text'),
('number', 'Number'),
('boolean', 'Boolean'),
('url', 'URL'),
('tag', 'Tag Name'),
('tag_id', 'Tag ID'),
]
CATEGORY_CHOICES = [
('paperless', 'Paperless Integration'),
('general', 'General Settings'),
('corporate', 'Corporate Identity'),
('notifications', 'Notifications'),
('system', 'System Settings'),
]
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
key = models.CharField(max_length=100, unique=True, verbose_name="Setting Key")
display_name = models.CharField(max_length=200, verbose_name="Display Name")
description = models.TextField(blank=True, null=True, verbose_name="Description")
value = models.TextField(verbose_name="Value")
default_value = models.TextField(verbose_name="Default Value")
setting_type = models.CharField(max_length=20, choices=SETTING_TYPE_CHOICES, default='text', verbose_name="Type")
category = models.CharField(max_length=50, choices=CATEGORY_CHOICES, default='general', verbose_name="Category")
is_active = models.BooleanField(default=True, verbose_name="Active")
is_system = models.BooleanField(default=False, verbose_name="System Setting (read-only)")
order = models.IntegerField(default=0, verbose_name="Display Order")
# Metadata
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
verbose_name = "App Configuration"
verbose_name_plural = "App Configurations"
ordering = ['category', 'order', 'display_name']
def __str__(self):
return f"{self.display_name} ({self.key})"
def get_typed_value(self):
"""Return the value converted to the appropriate type"""
if self.setting_type == 'boolean':
return self.value.lower() in ('true', '1', 'yes', 'on')
elif self.setting_type == 'number':
try:
if '.' in self.value:
return float(self.value)
return int(self.value)
except (ValueError, TypeError):
return 0
return self.value
@classmethod
def get_setting(cls, key, default=None):
"""Get a setting value by key"""
try:
setting = cls.objects.get(key=key, is_active=True)
return setting.get_typed_value()
except cls.DoesNotExist:
return default
@classmethod
def set_setting(cls, key, value, display_name=None, description=None, setting_type='text', category='general'):
"""Set or update a setting value"""
setting, created = cls.objects.get_or_create(
key=key,
defaults={
'display_name': display_name or key,
'description': description,
'value': str(value),
'default_value': str(value),
'setting_type': setting_type,
'category': category,
}
)
if not created:
setting.value = str(value)
setting.save()
return setting
class HelpBox(models.Model):
"""Editierbare Hilfe-Infoboxen für Formulare"""
PAGE_CHOICES = [
('destinataer_new', 'Neuer Destinatär'),
('unterstuetzung_new', 'Neue Unterstützung'),
('foerderung_new', 'Neue Förderung'),
('paechter_new', 'Neuer Pächter'),
('laenderei_new', 'Neue Länderei'),
('verpachtung_new', 'Neue Verpachtung'),
('land_abrechnung_new', 'Neue Landabrechnung'),
('person_new', 'Neue Person'),
('konto_new', 'Neues Konto'),
('verwaltungskosten_new', 'Neue Verwaltungskosten'),
('rentmeister_new', 'Neuer Rentmeister'),
('dokument_new', 'Neues Dokument'),
('user_new', 'Neuer Benutzer'),
('csv_import_new', 'CSV Import'),
('destinataer_notiz_new', 'Destinatär Notiz'),
]
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
page_key = models.CharField(
max_length=50,
choices=PAGE_CHOICES,
unique=True,
verbose_name="Seite"
)
title = models.CharField(
max_length=200,
verbose_name="Titel der Hilfsbox"
)
content = models.TextField(
verbose_name="Inhalt (Markdown unterstützt)",
help_text="Sie können Markdown verwenden: **fett**, *kursiv*, `code`, [Link](url), etc."
)
is_active = models.BooleanField(
default=True,
verbose_name="Aktiv"
)
# Metadata
created_at = models.DateTimeField(auto_now_add=True, verbose_name="Erstellt am")
updated_at = models.DateTimeField(auto_now=True, verbose_name="Aktualisiert am")
created_by = models.CharField(max_length=100, null=True, blank=True, verbose_name="Erstellt von")
updated_by = models.CharField(max_length=100, null=True, blank=True, verbose_name="Aktualisiert von")
class Meta:
verbose_name = "Hilfs-Infobox"
verbose_name_plural = "Hilfs-Infoboxen"
ordering = ['page_key']
def __str__(self):
return f"{self.get_page_key_display()}: {self.title}"
@classmethod
def get_help_for_page(cls, page_key):
"""Hole die aktive Hilfs-Infobox für eine bestimmte Seite"""
try:
return cls.objects.get(page_key=page_key, is_active=True)
except cls.DoesNotExist:
return None