feat: add comprehensive GitHub workflow and development tools
This commit is contained in:
801
app/stiftung/forms.py
Normal file
801
app/stiftung/forms.py
Normal file
@@ -0,0 +1,801 @@
|
||||
from django import forms
|
||||
from django.core.exceptions import ValidationError
|
||||
from .models import (
|
||||
Rentmeister, StiftungsKonto, Verwaltungskosten, Person,
|
||||
Paechter, Destinataer, Land, Verpachtung, DokumentLink, Foerderung, BankTransaction,
|
||||
DestinataerUnterstuetzung, 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"""
|
||||
cleaned_data = super().clean()
|
||||
seit_datum = cleaned_data.get('seit_datum')
|
||||
bis_datum = cleaned_data.get('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 VerpachtungForm(forms.ModelForm):
|
||||
"""Form für das Erstellen und Bearbeiten von Verpachtungen"""
|
||||
|
||||
class Meta:
|
||||
model = Verpachtung
|
||||
fields = '__all__'
|
||||
widgets = {
|
||||
'paechter': forms.Select(attrs={'class': 'form-select'}),
|
||||
'land': forms.Select(attrs={'class': 'form-select'}),
|
||||
'pacht_pro_qm': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'}),
|
||||
'pachtbeginn': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
|
||||
'pachtende': 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 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"""
|
||||
|
||||
class Meta:
|
||||
model = Foerderung
|
||||
fields = '__all__'
|
||||
widgets = {
|
||||
'person': forms.Select(attrs={'class': 'form-select'}),
|
||||
'destinataer': forms.Select(attrs={'class': 'form-select'}),
|
||||
'jahr': forms.NumberInput(attrs={'class': 'form-control'}),
|
||||
'betrag': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'}),
|
||||
'zweck': forms.TextInput(attrs={'class': 'form-control'}),
|
||||
'kategorie': forms.Select(attrs={'class': 'form-select'}),
|
||||
'status': forms.Select(attrs={'class': 'form-select'}),
|
||||
'ausgezahlt_am': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
|
||||
'notizen': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
Reference in New Issue
Block a user