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