Files
SysAdmin Agent 3ca2706e5d Phase 0: forms.py, admin.py und views.py in Domain-Packages aufteilen
- 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>
2026-03-11 09:55:15 +00:00

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)",
)