- forms.py → forms/ Package (8 Domänen: destinataere, land, finanzen, foerderung, dokumente, veranstaltung, system, geschichte) - admin.py → admin/ Package (7 Domänen, alle 22 @admin.register dekoriert) - views.py (8845 Zeilen) → views/ Package (10 Domänen: dashboard, destinataere, land, paechter, finanzen, foerderung, dokumente, unterstuetzungen, veranstaltung, geschichte, system) - __init__.py in jedem Package re-exportiert alle Symbole für Rückwärtskompatibilität - urls.py bleibt unverändert (funktioniert durch Re-Exports) - Django system check: 0 Fehler, alle URL-Auflösungen funktionieren Keine funktionalen Änderungen – reine Strukturverbesserung für Vision 2026. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
352 lines
13 KiB
Python
352 lines
13 KiB
Python
import re
|
|
|
|
from django import forms
|
|
from django.core.exceptions import ValidationError
|
|
|
|
from ..models import BankTransaction, Rentmeister, StiftungsKonto, Verwaltungskosten
|
|
|
|
|
|
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 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 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)",
|
|
)
|