- 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
943 lines
42 KiB
Python
943 lines
42 KiB
Python
from django import forms
|
|
from django.core.exceptions import ValidationError
|
|
from django.utils import timezone
|
|
from .models import (
|
|
Rentmeister, StiftungsKonto, Verwaltungskosten, Person,
|
|
Paechter, Destinataer, Land, DokumentLink, Foerderung, BankTransaction,
|
|
DestinataerUnterstuetzung, UnterstuetzungWiederkehrend, DestinataerNotiz, LandAbrechnung,
|
|
)
|
|
import re
|
|
|
|
|
|
class RentmeisterForm(forms.ModelForm):
|
|
"""Form für das Erstellen und Bearbeiten von Rentmeistern"""
|
|
|
|
class Meta:
|
|
model = Rentmeister
|
|
fields = [
|
|
'anrede', 'vorname', 'nachname', 'titel',
|
|
'email', 'telefon', 'mobil',
|
|
'strasse', 'plz', 'ort',
|
|
'iban', 'bic', 'bank_name',
|
|
'seit_datum', 'bis_datum', 'aktiv',
|
|
'monatliche_verguetung', 'km_pauschale',
|
|
'notizen'
|
|
]
|
|
|
|
widgets = {
|
|
'anrede': forms.Select(attrs={'class': 'form-select'}),
|
|
'vorname': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'nachname': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'titel': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'email': forms.EmailInput(attrs={'class': 'form-control'}),
|
|
'telefon': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'mobil': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'strasse': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'plz': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'ort': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'iban': forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'DE89370400440532013000'}),
|
|
'bic': forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'COBADEFFXXX'}),
|
|
'bank_name': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'seit_datum': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
|
|
'bis_datum': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
|
|
'aktiv': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
|
|
'monatliche_verguetung': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'}),
|
|
'km_pauschale': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01', 'value': '0.30'}),
|
|
'notizen': forms.Textarea(attrs={'class': 'form-control', 'rows': 4}),
|
|
}
|
|
|
|
labels = {
|
|
'anrede': 'Anrede',
|
|
'vorname': 'Vorname *',
|
|
'nachname': 'Nachname *',
|
|
'titel': 'Titel',
|
|
'email': 'E-Mail',
|
|
'telefon': 'Telefon',
|
|
'mobil': 'Mobil',
|
|
'strasse': 'Straße',
|
|
'plz': 'PLZ',
|
|
'ort': 'Ort',
|
|
'iban': 'IBAN',
|
|
'bic': 'BIC',
|
|
'bank_name': 'Bank',
|
|
'seit_datum': 'Rentmeister seit *',
|
|
'bis_datum': 'Rentmeister bis',
|
|
'aktiv': 'Aktiv',
|
|
'monatliche_verguetung': 'Monatliche Vergütung (€)',
|
|
'km_pauschale': 'Kilometerpauschale (€/km)',
|
|
'notizen': 'Notizen',
|
|
}
|
|
|
|
help_texts = {
|
|
'iban': 'Internationale Bankkontonummer für Abrechnungen',
|
|
'km_pauschale': 'Standard: 0,30 € pro Kilometer',
|
|
'seit_datum': 'Datum des Amtsantritts als Rentmeister',
|
|
'bis_datum': 'Leer lassen für aktive Rentmeister',
|
|
}
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
# Markiere Pflichtfelder
|
|
self.fields['vorname'].required = True
|
|
self.fields['nachname'].required = True
|
|
self.fields['seit_datum'].required = True
|
|
|
|
def clean_iban(self):
|
|
"""Validierung der IBAN"""
|
|
iban = self.cleaned_data.get('iban')
|
|
if iban:
|
|
# Entferne Leerzeichen und konvertiere zu Großbuchstaben
|
|
iban = re.sub(r'\s+', '', iban.upper())
|
|
|
|
# Einfache IBAN-Längenvalidierung für deutsche IBANs
|
|
if iban.startswith('DE') and len(iban) != 22:
|
|
raise ValidationError('Deutsche IBANs müssen 22 Zeichen lang sein.')
|
|
|
|
# Speichere die bereinigte IBAN
|
|
return iban
|
|
return iban
|
|
|
|
def clean_plz(self):
|
|
"""Validierung der PLZ"""
|
|
plz = self.cleaned_data.get('plz')
|
|
if plz and not re.match(r'^\d{5}$', plz):
|
|
raise ValidationError('PLZ muss aus 5 Ziffern bestehen.')
|
|
return plz
|
|
|
|
def clean(self):
|
|
"""Übergreifende Validierung"""
|
|
from django.utils.dateparse import parse_date
|
|
|
|
cleaned_data = super().clean()
|
|
seit_datum = cleaned_data.get('seit_datum')
|
|
bis_datum = cleaned_data.get('bis_datum')
|
|
|
|
# Helper function to ensure we have 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
|
|
|
|
# Convert to date objects if they're strings
|
|
seit_datum = ensure_date(seit_datum)
|
|
bis_datum = ensure_date(bis_datum)
|
|
|
|
# Prüfe Datum-Logik
|
|
if seit_datum and bis_datum and bis_datum <= seit_datum:
|
|
raise ValidationError('Das End-Datum muss nach dem Start-Datum liegen.')
|
|
|
|
return cleaned_data
|
|
|
|
|
|
class StiftungsKontoForm(forms.ModelForm):
|
|
"""Form für das Erstellen und Bearbeiten von Stiftungskonten"""
|
|
|
|
class Meta:
|
|
model = StiftungsKonto
|
|
fields = [
|
|
'kontoname', 'bank_name', 'iban', 'bic', 'konto_typ',
|
|
'saldo', 'saldo_datum', 'zinssatz', 'laufzeit_bis',
|
|
'aktiv', 'notizen'
|
|
]
|
|
|
|
widgets = {
|
|
'kontoname': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'bank_name': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'iban': forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'DE89370400440532013000'}),
|
|
'bic': forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'COBADEFFXXX'}),
|
|
'konto_typ': forms.Select(attrs={'class': 'form-select'}),
|
|
'saldo': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'}),
|
|
'saldo_datum': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
|
|
'zinssatz': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'}),
|
|
'laufzeit_bis': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
|
|
'aktiv': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
|
|
'notizen': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
|
|
}
|
|
|
|
|
|
class VerwaltungskostenForm(forms.ModelForm):
|
|
"""Form für das Erstellen und Bearbeiten von Verwaltungskosten"""
|
|
|
|
class Meta:
|
|
model = Verwaltungskosten
|
|
fields = [
|
|
'bezeichnung', 'kategorie', 'betrag', 'datum', 'status',
|
|
'rentmeister', 'zahlungskonto', 'quellkonto', 'lieferant_firma', 'rechnungsnummer',
|
|
'km_anzahl', 'km_satz', 'von_ort', 'nach_ort', 'zweck',
|
|
'beschreibung', 'notizen'
|
|
]
|
|
|
|
widgets = {
|
|
'bezeichnung': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'kategorie': forms.Select(attrs={'class': 'form-select'}),
|
|
'betrag': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'}),
|
|
'datum': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
|
|
'status': forms.Select(attrs={'class': 'form-select'}),
|
|
'rentmeister': forms.Select(attrs={'class': 'form-select'}),
|
|
'zahlungskonto': forms.Select(attrs={'class': 'form-select'}),
|
|
'quellkonto': forms.Select(attrs={'class': 'form-select'}),
|
|
'lieferant_firma': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'rechnungsnummer': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'km_anzahl': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.1'}),
|
|
'km_satz': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'}),
|
|
'von_ort': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'nach_ort': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'zweck': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'beschreibung': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
|
|
'notizen': forms.Textarea(attrs={'class': 'form-control', 'rows': 2}),
|
|
}
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
# Filtere nur aktive Rentmeister und Konten
|
|
self.fields['rentmeister'].queryset = Rentmeister.objects.filter(aktiv=True)
|
|
self.fields['zahlungskonto'].queryset = StiftungsKonto.objects.filter(aktiv=True)
|
|
self.fields['quellkonto'].queryset = StiftungsKonto.objects.filter(aktiv=True)
|
|
|
|
# Standardwerte setzen
|
|
if not self.instance.pk: # Nur bei neuen Objekten
|
|
# Standard km_satz auf 0.30 Euro setzen
|
|
self.fields['km_satz'].initial = 0.30
|
|
|
|
|
|
class PersonForm(forms.ModelForm):
|
|
"""Form für das Erstellen und Bearbeiten von Personen (Legacy)"""
|
|
|
|
class Meta:
|
|
model = Person
|
|
fields = [
|
|
'familienzweig', 'vorname', 'nachname', 'geburtsdatum',
|
|
'email', 'telefon', 'iban', 'adresse', 'notizen', 'aktiv'
|
|
]
|
|
|
|
widgets = {
|
|
'familienzweig': forms.Select(attrs={'class': 'form-select'}),
|
|
'vorname': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'nachname': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'geburtsdatum': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
|
|
'email': forms.EmailInput(attrs={'class': 'form-control'}),
|
|
'telefon': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'iban': forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'DE89370400440532013000'}),
|
|
'adresse': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
|
|
'notizen': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
|
|
'aktiv': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
|
|
}
|
|
|
|
labels = {
|
|
'familienzweig': 'Familienzweig',
|
|
'vorname': 'Vorname *',
|
|
'nachname': 'Nachname *',
|
|
'geburtsdatum': 'Geburtsdatum',
|
|
'email': 'E-Mail',
|
|
'telefon': 'Telefon',
|
|
'iban': 'IBAN',
|
|
'adresse': 'Adresse',
|
|
'notizen': 'Notizen',
|
|
'aktiv': 'Aktiv',
|
|
}
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
# Markiere Pflichtfelder
|
|
self.fields['vorname'].required = True
|
|
self.fields['nachname'].required = True
|
|
|
|
|
|
class PaechterForm(forms.ModelForm):
|
|
"""Form für das Erstellen und Bearbeiten von Pächtern"""
|
|
|
|
class Meta:
|
|
model = Paechter
|
|
fields = '__all__'
|
|
widgets = {
|
|
'anrede': forms.Select(attrs={'class': 'form-select'}),
|
|
'vorname': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'nachname': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'email': forms.EmailInput(attrs={'class': 'form-control'}),
|
|
'telefon': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'mobil': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'geburtsdatum': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
|
|
'strasse': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'plz': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'ort': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'aktiv': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
|
|
'notizen': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
|
|
}
|
|
|
|
|
|
class DestinataerForm(forms.ModelForm):
|
|
"""Form für das Erstellen und Bearbeiten von Destinatären"""
|
|
|
|
class Meta:
|
|
model = Destinataer
|
|
fields = '__all__'
|
|
widgets = {
|
|
'anrede': forms.Select(attrs={'class': 'form-select'}),
|
|
'vorname': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'nachname': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'titel': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'strasse': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'plz': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'ort': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'telefon': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'mobil': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'email': forms.EmailInput(attrs={'class': 'form-control'}),
|
|
'aktiv': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
|
|
'notizen': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
|
|
'ist_abkoemmling': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
|
|
'haushaltsgroesse': forms.NumberInput(attrs={'class': 'form-control', 'min': 1}),
|
|
# renamed in UI: use vierteljaehrlicher_betrag field
|
|
'vermoegen': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'}),
|
|
'unterstuetzung_bestaetigt': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
|
|
'standard_konto': forms.Select(attrs={'class': 'form-select'}),
|
|
'vierteljaehrlicher_betrag': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'}),
|
|
'studiennachweis_erforderlich': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
|
|
'letzter_studiennachweis': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
|
|
}
|
|
|
|
|
|
class LandForm(forms.ModelForm):
|
|
"""Form für das Erstellen und Bearbeiten von Ländern"""
|
|
|
|
class Meta:
|
|
model = Land
|
|
fields = [
|
|
# Grundlegende Identifikation
|
|
'lfd_nr', 'ew_nummer', 'grundbuchblatt',
|
|
# Gerichtliche Zuständigkeit
|
|
'amtsgericht',
|
|
# Verwaltungsstruktur
|
|
'gemeinde', 'gemarkung', 'flur', 'flurstueck', 'adresse',
|
|
# Flächenangaben
|
|
'groesse_qm', 'gruenland_qm', 'acker_qm', 'wald_qm', 'sonstiges_qm',
|
|
# Legacy Verpachtung (für Kompatibilität)
|
|
'verpachtete_gesamtflaeche', 'flaeche_alte_liste', 'verp_flaeche_aktuell',
|
|
# Aktuelle Verpachtung
|
|
'aktueller_paechter', 'paechter_name', 'paechter_anschrift',
|
|
'pachtbeginn', 'pachtende', 'verlaengerung_klausel',
|
|
'zahlungsweise', 'pachtzins_pro_ha', 'pachtzins_pauschal',
|
|
# Umsatzsteuer
|
|
'ust_option', 'ust_satz',
|
|
# Umlagen
|
|
'grundsteuer_umlage', 'versicherungen_umlage', 'verbandsbeitraege_umlage', 'jagdpacht_anteil_umlage',
|
|
# Legacy Steuern
|
|
'anteil_grundsteuer', 'anteil_lwk',
|
|
# Status
|
|
'aktiv', 'notizen',
|
|
]
|
|
widgets = {
|
|
# Grundlegende Identifikation
|
|
'lfd_nr': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'ew_nummer': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'grundbuchblatt': forms.TextInput(attrs={'class': 'form-control'}),
|
|
# Gerichtliche Zuständigkeit
|
|
'amtsgericht': forms.TextInput(attrs={'class': 'form-control'}),
|
|
# Verwaltungsstruktur
|
|
'gemeinde': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'gemarkung': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'flur': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'flurstueck': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'adresse': forms.TextInput(attrs={'class': 'form-control'}),
|
|
# Flächenangaben
|
|
'groesse_qm': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'}),
|
|
'gruenland_qm': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'}),
|
|
'acker_qm': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'}),
|
|
'wald_qm': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'}),
|
|
'sonstiges_qm': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'}),
|
|
# Legacy Verpachtung
|
|
'verpachtete_gesamtflaeche': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'}),
|
|
'flaeche_alte_liste': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'}),
|
|
'verp_flaeche_aktuell': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'}),
|
|
# Aktuelle Verpachtung
|
|
'aktueller_paechter': forms.Select(attrs={'class': 'form-select'}),
|
|
'paechter_name': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'paechter_anschrift': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
|
|
'pachtbeginn': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
|
|
'pachtende': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
|
|
'verlaengerung_klausel': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
|
|
'zahlungsweise': forms.Select(attrs={'class': 'form-select'}),
|
|
'pachtzins_pro_ha': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'}),
|
|
'pachtzins_pauschal': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'}),
|
|
# Umsatzsteuer
|
|
'ust_option': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
|
|
'ust_satz': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'}),
|
|
# Umlagen
|
|
'grundsteuer_umlage': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
|
|
'versicherungen_umlage': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
|
|
'verbandsbeitraege_umlage': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
|
|
'jagdpacht_anteil_umlage': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
|
|
# Legacy
|
|
'anteil_grundsteuer': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'}),
|
|
'anteil_lwk': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'}),
|
|
# Status
|
|
'aktiv': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
|
|
'notizen': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
|
|
}
|
|
|
|
|
|
class LandAbrechnungForm(forms.ModelForm):
|
|
"""Form für das Erstellen und Bearbeiten von Landabrechnungen"""
|
|
|
|
class Meta:
|
|
model = LandAbrechnung
|
|
fields = [
|
|
'land', 'abrechnungsjahr',
|
|
# Einnahmen
|
|
'pacht_vereinnahmt', 'umlagen_vereinnahmt', 'sonstige_einnahmen',
|
|
# Ausgaben
|
|
'grundsteuer_bescheid_nr', 'grundsteuer_betrag',
|
|
'versicherungen_betrag', 'verbandsbeitraege_betrag',
|
|
'sonstige_abgaben_betrag', 'instandhaltung_betrag', 'verwaltung_recht_betrag',
|
|
# Umsatzsteuer
|
|
'vorsteuer_aus_umlagen',
|
|
# Sonstiges
|
|
'offene_posten', 'bemerkungen',
|
|
# Dokumente werden über Paperless verknüpft, nicht hochgeladen
|
|
]
|
|
widgets = {
|
|
'land': forms.Select(attrs={'class': 'form-select'}),
|
|
'abrechnungsjahr': forms.NumberInput(attrs={'class': 'form-control', 'min': '2000', 'max': '2050'}),
|
|
# Einnahmen
|
|
'pacht_vereinnahmt': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'}),
|
|
'umlagen_vereinnahmt': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'}),
|
|
'sonstige_einnahmen': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'}),
|
|
# Ausgaben
|
|
'grundsteuer_bescheid_nr': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'grundsteuer_betrag': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'}),
|
|
'versicherungen_betrag': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'}),
|
|
'verbandsbeitraege_betrag': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'}),
|
|
'sonstige_abgaben_betrag': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'}),
|
|
'instandhaltung_betrag': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'}),
|
|
'verwaltung_recht_betrag': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'}),
|
|
# Umsatzsteuer
|
|
'vorsteuer_aus_umlagen': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'}),
|
|
# Sonstiges
|
|
'offene_posten': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'}),
|
|
'bemerkungen': forms.Textarea(attrs={'class': 'form-control', 'rows': 4}),
|
|
}
|
|
|
|
|
|
class DokumentLinkForm(forms.ModelForm):
|
|
"""Form für das Erstellen und Bearbeiten von Dokumentverknüpfungen"""
|
|
|
|
class Meta:
|
|
model = DokumentLink
|
|
fields = '__all__'
|
|
widgets = {
|
|
'paperless_id': forms.NumberInput(attrs={'class': 'form-control'}),
|
|
'content_type': forms.Select(attrs={'class': 'form-select'}),
|
|
'object_id': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'verknuepft_am': forms.DateTimeInput(attrs={'class': 'form-control', 'type': 'datetime-local'}),
|
|
}
|
|
|
|
|
|
class FoerderungForm(forms.ModelForm):
|
|
"""Form für das Erstellen und Bearbeiten von Förderungen"""
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
# Add empty option for optional fields
|
|
self.fields['verwendungsnachweis'].empty_label = "--- Kein Dokument verknüpfen ---"
|
|
# Ensure destinataer has proper choices
|
|
from .models import Destinataer, DokumentLink
|
|
from django.utils import timezone
|
|
self.fields['destinataer'].queryset = Destinataer.objects.all().order_by('nachname', 'vorname')
|
|
self.fields['verwendungsnachweis'].queryset = DokumentLink.objects.all().order_by('titel')
|
|
# Set current year as default for new forms
|
|
if not self.instance.pk:
|
|
self.fields['jahr'].initial = timezone.now().year
|
|
|
|
class Meta:
|
|
model = Foerderung
|
|
fields = [
|
|
'destinataer', 'jahr', 'betrag', 'kategorie', 'status',
|
|
'antragsdatum', 'entscheidungsdatum', 'verwendungsnachweis', 'bemerkungen'
|
|
]
|
|
widgets = {
|
|
'destinataer': forms.Select(attrs={'class': 'form-select'}),
|
|
'jahr': forms.NumberInput(attrs={'class': 'form-control'}),
|
|
'betrag': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'}),
|
|
'kategorie': forms.Select(attrs={'class': 'form-select'}),
|
|
'status': forms.Select(attrs={'class': 'form-select'}),
|
|
'antragsdatum': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
|
|
'entscheidungsdatum': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
|
|
'verwendungsnachweis': forms.Select(attrs={'class': 'form-select'}),
|
|
'bemerkungen': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
|
|
}
|
|
|
|
labels = {
|
|
'destinataer': 'Destinatär',
|
|
'verwendungsnachweis': 'Verknüpftes Dokument',
|
|
'bemerkungen': 'Bemerkungen/Beschreibung',
|
|
'antragsdatum': 'Antragsdatum',
|
|
'entscheidungsdatum': 'Entscheidungsdatum',
|
|
}
|
|
|
|
help_texts = {
|
|
'verwendungsnachweis': 'Optionale Verknüpfung zu einem Dokument aus dem Paperless-System',
|
|
'entscheidungsdatum': 'Datum der Bewilligung/Ablehnung (optional)',
|
|
'bemerkungen': 'Zusätzliche Informationen zur Förderung',
|
|
}
|
|
|
|
|
|
class UnterstuetzungForm(forms.ModelForm):
|
|
"""Form für das Erstellen und Bearbeiten von Unterstützungen"""
|
|
|
|
# Special field for creating recurring payments
|
|
ist_wiederkehrend = forms.BooleanField(
|
|
required=False,
|
|
label='Wiederkehrende Zahlung',
|
|
help_text='Aktivieren Sie diese Option um automatisch wiederkehrende Zahlungen zu erstellen'
|
|
)
|
|
intervall = forms.ChoiceField(
|
|
choices=[('', '--- Wählen Sie ein Intervall ---')] + UnterstuetzungWiederkehrend.INTERVALL_CHOICES,
|
|
required=False,
|
|
widget=forms.Select(attrs={'class': 'form-select'}),
|
|
label='Zahlungsintervall'
|
|
)
|
|
letzte_zahlung_am = forms.DateField(
|
|
required=False,
|
|
widget=forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
|
|
label='Letzte Zahlung am (optional)',
|
|
help_text='Leer lassen für unbegrenzte Wiederholung'
|
|
)
|
|
|
|
class Meta:
|
|
model = DestinataerUnterstuetzung
|
|
fields = [
|
|
'destinataer', 'konto', 'faellig_am', 'betrag', 'status',
|
|
'beschreibung', 'empfaenger_iban', 'empfaenger_name', 'verwendungszweck'
|
|
]
|
|
|
|
widgets = {
|
|
'destinataer': forms.Select(attrs={'class': 'form-select'}),
|
|
'konto': forms.Select(attrs={'class': 'form-select'}),
|
|
'faellig_am': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
|
|
'betrag': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'}),
|
|
'status': forms.Select(attrs={'class': 'form-select'}),
|
|
'beschreibung': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'empfaenger_iban': forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'DE89 3704 0044 0532 0130 00'}),
|
|
'empfaenger_name': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'verwendungszweck': forms.TextInput(attrs={'class': 'form-control', 'maxlength': '140'}),
|
|
}
|
|
|
|
labels = {
|
|
'destinataer': 'Destinatär',
|
|
'konto': 'Zahlungskonto',
|
|
'faellig_am': 'Fällig am',
|
|
'betrag': 'Betrag (€)',
|
|
'status': 'Status',
|
|
'beschreibung': 'Beschreibung',
|
|
'empfaenger_iban': 'Empfänger IBAN',
|
|
'empfaenger_name': 'Empfänger Name',
|
|
'verwendungszweck': 'Verwendungszweck',
|
|
}
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
# Add onchange event to destinataer field for AJAX IBAN fetching
|
|
self.fields['destinataer'].widget.attrs['onchange'] = 'updateDestinataerInfo()'
|
|
|
|
def clean(self):
|
|
cleaned_data = super().clean()
|
|
ist_wiederkehrend = cleaned_data.get('ist_wiederkehrend')
|
|
intervall = cleaned_data.get('intervall')
|
|
|
|
if ist_wiederkehrend and not intervall:
|
|
raise forms.ValidationError('Bitte wählen Sie ein Zahlungsintervall für wiederkehrende Zahlungen.')
|
|
|
|
return cleaned_data
|
|
|
|
|
|
class UnterstuetzungWiederkehrendForm(forms.ModelForm):
|
|
"""Form für das Bearbeiten von wiederkehrenden Unterstützungsvorlagen"""
|
|
|
|
class Meta:
|
|
model = UnterstuetzungWiederkehrend
|
|
fields = [
|
|
'destinataer', 'konto', 'betrag', 'intervall', 'beschreibung',
|
|
'empfaenger_iban', 'empfaenger_name', 'verwendungszweck',
|
|
'erste_zahlung_am', 'letzte_zahlung_am', 'aktiv'
|
|
]
|
|
|
|
widgets = {
|
|
'destinataer': forms.Select(attrs={'class': 'form-select'}),
|
|
'konto': forms.Select(attrs={'class': 'form-select'}),
|
|
'betrag': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'}),
|
|
'intervall': forms.Select(attrs={'class': 'form-select'}),
|
|
'beschreibung': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'empfaenger_iban': forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'DE89 3704 0044 0532 0130 00'}),
|
|
'empfaenger_name': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'verwendungszweck': forms.TextInput(attrs={'class': 'form-control', 'maxlength': '140'}),
|
|
'erste_zahlung_am': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
|
|
'letzte_zahlung_am': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
|
|
'aktiv': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
|
|
}
|
|
|
|
|
|
class UnterstuetzungMarkAsPaidForm(forms.Form):
|
|
"""Simple form to mark an Unterstützung as paid"""
|
|
|
|
ausgezahlt_am = forms.DateField(
|
|
widget=forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
|
|
label='Ausgezahlt am',
|
|
initial=timezone.now().date()
|
|
)
|
|
|
|
bemerkung = forms.CharField(
|
|
widget=forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
|
|
label='Bemerkung (optional)',
|
|
required=False,
|
|
help_text='Optionale Notiz zur Zahlung'
|
|
)
|
|
|
|
|
|
|
|
|
|
class BankTransactionForm(forms.ModelForm):
|
|
"""Form für das Bearbeiten von Banktransaktionen"""
|
|
|
|
class Meta:
|
|
model = BankTransaction
|
|
fields = [
|
|
'konto', 'datum', 'valuta', 'betrag', 'waehrung',
|
|
'verwendungszweck', 'empfaenger_zahlungspflichtiger',
|
|
'iban_gegenpartei', 'bic_gegenpartei', 'transaction_type',
|
|
'status', 'kommentare', 'verwaltungskosten'
|
|
]
|
|
|
|
widgets = {
|
|
'konto': forms.Select(attrs={'class': 'form-select'}),
|
|
'datum': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
|
|
'valuta': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
|
|
'betrag': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'}),
|
|
'waehrung': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'verwendungszweck': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
|
|
'empfaenger_zahlungspflichtiger': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'iban_gegenpartei': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'bic_gegenpartei': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'transaction_type': forms.Select(attrs={'class': 'form-select'}),
|
|
'status': forms.Select(attrs={'class': 'form-select'}),
|
|
'kommentare': forms.Textarea(attrs={'class': 'form-control', 'rows': 4}),
|
|
'verwaltungskosten': forms.Select(attrs={'class': 'form-select'}),
|
|
}
|
|
|
|
|
|
class DestinataerUnterstuetzungForm(forms.ModelForm):
|
|
"""Form für geplante/ausgeführte Destinatärunterstützungen"""
|
|
class Meta:
|
|
model = DestinataerUnterstuetzung
|
|
fields = ['destinataer', 'konto', 'betrag', 'faellig_am', 'status', 'beschreibung']
|
|
widgets = {
|
|
'destinataer': forms.Select(attrs={'class': 'form-select'}),
|
|
'konto': forms.Select(attrs={'class': 'form-select'}),
|
|
'betrag': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'}),
|
|
'faellig_am': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
|
|
'status': forms.Select(attrs={'class': 'form-select'}),
|
|
'beschreibung': forms.TextInput(attrs={'class': 'form-control'}),
|
|
}
|
|
|
|
|
|
class DestinataerNotizForm(forms.ModelForm):
|
|
class Meta:
|
|
model = DestinataerNotiz
|
|
fields = ['titel', 'text', 'datei']
|
|
widgets = {
|
|
'titel': forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'z.B. Telefonat vom 29.08.2025'}),
|
|
'text': forms.Textarea(attrs={'class': 'form-control', 'rows': 5, 'placeholder': 'Notiztext...'}),
|
|
'datei': forms.ClearableFileInput(attrs={'class': 'form-control'}),
|
|
}
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
# Make all fields optional
|
|
self.fields['datei'].required = False
|
|
self.fields['titel'].required = False
|
|
self.fields['text'].required = False
|
|
|
|
def clean(self):
|
|
cleaned = super().clean()
|
|
titel = cleaned.get('titel', '').strip()
|
|
text = cleaned.get('text', '').strip()
|
|
if not (titel or text):
|
|
raise forms.ValidationError('Bitte geben Sie einen Titel oder einen Text ein.')
|
|
return cleaned
|
|
|
|
|
|
class BankImportForm(forms.Form):
|
|
"""Form für den Import von Bankdaten"""
|
|
|
|
konto = forms.ModelChoiceField(
|
|
queryset=StiftungsKonto.objects.filter(aktiv=True),
|
|
widget=forms.Select(attrs={'class': 'form-select'}),
|
|
label="Zielkonto"
|
|
)
|
|
|
|
datei = forms.FileField(
|
|
widget=forms.FileInput(attrs={'class': 'form-control', 'accept': '.csv,.txt'}),
|
|
label="Bankdatei",
|
|
help_text="Unterstützte Formate: CSV, TXT (Sparkasse, Volksbank, etc.)"
|
|
)
|
|
|
|
encoding = forms.ChoiceField(
|
|
choices=[
|
|
('utf-8', 'UTF-8'),
|
|
('latin1', 'Latin-1 / ISO-8859-1'),
|
|
('cp1252', 'Windows-1252'),
|
|
],
|
|
initial='utf-8',
|
|
widget=forms.Select(attrs={'class': 'form-select'}),
|
|
label="Zeichenkodierung"
|
|
)
|
|
|
|
delimiter = forms.ChoiceField(
|
|
choices=[
|
|
(';', 'Semikolon (;)'),
|
|
(',', 'Komma (,)'),
|
|
('\t', 'Tab'),
|
|
],
|
|
initial=';',
|
|
widget=forms.Select(attrs={'class': 'form-select'}),
|
|
label="Trennzeichen"
|
|
)
|
|
|
|
skip_header = forms.BooleanField(
|
|
initial=True,
|
|
required=False,
|
|
widget=forms.CheckboxInput(attrs={'class': 'form-check-input'}),
|
|
label="Erste Zeile überspringen (Spaltenüberschriften)"
|
|
)
|
|
|
|
|
|
# =============================================================================
|
|
# USER MANAGEMENT FORMS
|
|
# =============================================================================
|
|
|
|
class UserCreationForm(forms.Form):
|
|
"""Form für die Erstellung neuer Benutzer"""
|
|
username = forms.CharField(
|
|
label="Benutzername",
|
|
max_length=150,
|
|
help_text="Eindeutiger Benutzername für die Anmeldung",
|
|
widget=forms.TextInput(attrs={'class': 'form-control'})
|
|
)
|
|
|
|
email = forms.EmailField(
|
|
label="E-Mail-Adresse",
|
|
help_text="E-Mail-Adresse des Benutzers",
|
|
widget=forms.EmailInput(attrs={'class': 'form-control'})
|
|
)
|
|
|
|
first_name = forms.CharField(
|
|
label="Vorname",
|
|
max_length=30,
|
|
required=False,
|
|
widget=forms.TextInput(attrs={'class': 'form-control'})
|
|
)
|
|
|
|
last_name = forms.CharField(
|
|
label="Nachname",
|
|
max_length=150,
|
|
required=False,
|
|
widget=forms.TextInput(attrs={'class': 'form-control'})
|
|
)
|
|
|
|
password1 = forms.CharField(
|
|
label="Passwort",
|
|
widget=forms.PasswordInput(attrs={'class': 'form-control'}),
|
|
help_text="Mindestens 8 Zeichen"
|
|
)
|
|
|
|
password2 = forms.CharField(
|
|
label="Passwort bestätigen",
|
|
widget=forms.PasswordInput(attrs={'class': 'form-control'}),
|
|
help_text="Geben Sie das Passwort zur Bestätigung erneut ein"
|
|
)
|
|
|
|
is_active = forms.BooleanField(
|
|
label="Aktiv",
|
|
required=False,
|
|
initial=True,
|
|
help_text="Benutzer kann sich anmelden",
|
|
widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
|
)
|
|
|
|
is_staff = forms.BooleanField(
|
|
label="Staff-Status",
|
|
required=False,
|
|
help_text="Benutzer kann auf Django Admin zugreifen",
|
|
widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
|
)
|
|
|
|
def clean_username(self):
|
|
username = self.cleaned_data['username']
|
|
from django.contrib.auth.models import User
|
|
if User.objects.filter(username=username).exists():
|
|
raise forms.ValidationError("Ein Benutzer mit diesem Namen existiert bereits.")
|
|
return username
|
|
|
|
def clean_email(self):
|
|
email = self.cleaned_data['email']
|
|
from django.contrib.auth.models import User
|
|
if User.objects.filter(email=email).exists():
|
|
raise forms.ValidationError("Ein Benutzer mit dieser E-Mail-Adresse existiert bereits.")
|
|
return email
|
|
|
|
def clean(self):
|
|
cleaned_data = super().clean()
|
|
password1 = cleaned_data.get("password1")
|
|
password2 = cleaned_data.get("password2")
|
|
|
|
if password1 and password2:
|
|
if password1 != password2:
|
|
raise forms.ValidationError("Die Passwörter stimmen nicht überein.")
|
|
if len(password1) < 8:
|
|
raise forms.ValidationError("Das Passwort muss mindestens 8 Zeichen lang sein.")
|
|
|
|
return cleaned_data
|
|
|
|
|
|
class UserUpdateForm(forms.ModelForm):
|
|
"""Form für die Bearbeitung bestehender Benutzer"""
|
|
|
|
class Meta:
|
|
from django.contrib.auth.models import User
|
|
model = User
|
|
fields = ['username', 'email', 'first_name', 'last_name', 'is_active', 'is_staff']
|
|
widgets = {
|
|
'username': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'email': forms.EmailInput(attrs={'class': 'form-control'}),
|
|
'first_name': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'last_name': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'is_active': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
|
|
'is_staff': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
|
|
}
|
|
labels = {
|
|
'username': 'Benutzername',
|
|
'email': 'E-Mail-Adresse',
|
|
'first_name': 'Vorname',
|
|
'last_name': 'Nachname',
|
|
'is_active': 'Aktiv',
|
|
'is_staff': 'Staff-Status',
|
|
}
|
|
help_texts = {
|
|
'username': 'Eindeutiger Benutzername für die Anmeldung',
|
|
'email': 'E-Mail-Adresse des Benutzers',
|
|
'is_active': 'Benutzer kann sich anmelden',
|
|
'is_staff': 'Benutzer kann auf Django Admin zugreifen',
|
|
}
|
|
|
|
|
|
class PasswordChangeForm(forms.Form):
|
|
"""Form für Passwort-Änderungen"""
|
|
new_password1 = forms.CharField(
|
|
label="Neues Passwort",
|
|
widget=forms.PasswordInput(attrs={'class': 'form-control'}),
|
|
help_text="Mindestens 8 Zeichen"
|
|
)
|
|
|
|
new_password2 = forms.CharField(
|
|
label="Neues Passwort bestätigen",
|
|
widget=forms.PasswordInput(attrs={'class': 'form-control'}),
|
|
help_text="Geben Sie das neue Passwort zur Bestätigung erneut ein"
|
|
)
|
|
|
|
def clean(self):
|
|
cleaned_data = super().clean()
|
|
password1 = cleaned_data.get("new_password1")
|
|
password2 = cleaned_data.get("new_password2")
|
|
|
|
if password1 and password2:
|
|
if password1 != password2:
|
|
raise forms.ValidationError("Die Passwörter stimmen nicht überein.")
|
|
if len(password1) < 8:
|
|
raise forms.ValidationError("Das Passwort muss mindestens 8 Zeichen lang sein.")
|
|
|
|
return cleaned_data
|
|
|
|
|
|
class UserPermissionForm(forms.Form):
|
|
"""Form für die Zuweisung von Berechtigungen"""
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
user = kwargs.pop('user', None)
|
|
super().__init__(*args, **kwargs)
|
|
|
|
from django.contrib.auth.models import Permission
|
|
|
|
# Get all custom permissions for stiftung app
|
|
app_permissions = Permission.objects.filter(content_type__app_label='stiftung').order_by('name')
|
|
|
|
# Create checkbox fields for each permission
|
|
for perm in app_permissions:
|
|
field_name = f'perm_{perm.id}'
|
|
self.fields[field_name] = forms.BooleanField(
|
|
label=perm.name,
|
|
required=False,
|
|
widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
|
)
|
|
|
|
# Set initial values if user is provided
|
|
if user:
|
|
self.fields[field_name].initial = user.has_perm(f'stiftung.{perm.codename}')
|
|
|
|
def get_permission_groups(self):
|
|
"""Group permissions by functionality for template rendering"""
|
|
from django.contrib.auth.models import Permission
|
|
|
|
groups = {
|
|
'entities': {
|
|
'name': 'Entitäten verwalten',
|
|
'permissions': [],
|
|
'icon': 'fas fa-users'
|
|
},
|
|
'documents': {
|
|
'name': 'Dokumentenverwaltung',
|
|
'permissions': [],
|
|
'icon': 'fas fa-folder-open'
|
|
},
|
|
'financial': {
|
|
'name': 'Finanzverwaltung',
|
|
'permissions': [],
|
|
'icon': 'fas fa-euro-sign'
|
|
},
|
|
'administration': {
|
|
'name': 'Administration',
|
|
'permissions': [],
|
|
'icon': 'fas fa-cogs'
|
|
},
|
|
'system': {
|
|
'name': 'System',
|
|
'permissions': [],
|
|
'icon': 'fas fa-server'
|
|
}
|
|
}
|
|
|
|
# Get all permissions to properly categorize them
|
|
for field_name, field in self.fields.items():
|
|
if field_name.startswith('perm_'):
|
|
# Extract permission ID from field name
|
|
perm_id = field_name.replace('perm_', '')
|
|
try:
|
|
permission = Permission.objects.get(id=perm_id)
|
|
label = permission.name.lower()
|
|
codename = permission.codename.lower()
|
|
|
|
# More precise categorization based on both name and codename
|
|
if any(word in codename for word in ['destinataer', 'land', 'paechter', 'verpachtung', 'foerderung']) and 'manage_' in codename or 'view_' in codename:
|
|
groups['entities']['permissions'].append((field_name, field, permission))
|
|
elif any(word in codename for word in ['documents', 'link_documents']) or 'dokument' in label:
|
|
groups['documents']['permissions'].append((field_name, field, permission))
|
|
elif any(word in codename for word in ['verwaltungskosten', 'konten', 'rentmeister', 'approve_payments']) or any(word in label for word in ['verwaltungskosten', 'konto', 'rentmeister', 'zahlung']):
|
|
groups['financial']['permissions'].append((field_name, field, permission))
|
|
elif any(word in codename for word in ['administration', 'audit', 'backup', 'manage_users', 'manage_permissions']) or any(word in label for word in ['administration', 'audit', 'backup', 'benutzer', 'berechtigung']):
|
|
groups['administration']['permissions'].append((field_name, field, permission))
|
|
else:
|
|
groups['system']['permissions'].append((field_name, field, permission))
|
|
except Permission.DoesNotExist:
|
|
# Fallback for permissions that don't exist
|
|
groups['system']['permissions'].append((field_name, field, None))
|
|
|
|
return groups |