from django import forms from django.utils import timezone from ..models import (Destinataer, DestinataerNotiz, DestinataerUnterstuetzung, UnterstuetzungWiederkehrend, VierteljahresNachweis) from django.core.exceptions import ValidationError 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} ), "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"}, choices=[(None, "---")] + [(c.pk, str(c)) for c in getattr(Destinataer, 'konten_queryset', lambda: [])()]), "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"} ), "familienzweig": forms.Select(attrs={"class": "form-select"}), "berufsgruppe": forms.Select(attrs={"class": "form-select"}), } def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) for field_name, field in self.fields.items(): if field_name not in ["vorname", "nachname"]: field.required = False # Set choices for familienzweig and berufsgruppe to match model self.fields["familienzweig"].choices = [("", "Bitte wählen...")] + list(Destinataer.FAMILIENZWIG_CHOICES) self.fields["berufsgruppe"].choices = [("", "Bitte wählen...")] + list(Destinataer.BERUFSGRUPPE_CHOICES) # Set choices for standard_konto to allow blank self.fields["standard_konto"].empty_label = "---" def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) for field_name, field in self.fields.items(): if field_name not in ["vorname", "nachname"]: field.required = False 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", "empfaenger_iban", "empfaenger_name", "verwendungszweck", ] 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"}), "empfaenger_iban": forms.TextInput( attrs={"class": "form-control", "placeholder": "DE89 3704 0044 0532 0130 00"} ), "empfaenger_name": forms.TextInput( attrs={"class": "form-control", "placeholder": "Max Mustermann"} ), "verwendungszweck": forms.TextInput( attrs={"class": "form-control", "placeholder": "Vierteljährliche Unterstützung Q1/2025"} ), } def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # Make faellig_am read-only for automatically generated quarterly payments self.is_auto_generated = False if self.instance and self.instance.pk and self.instance.beschreibung: if "Vierteljährliche Unterstützung" in self.instance.beschreibung and "(automatisch erstellt)" in self.instance.beschreibung: self.is_auto_generated = True # Use a TextInput widget with readonly attribute to display the date from django import forms current_date = self.instance.faellig_am if current_date: self.fields['faellig_am'].widget = forms.TextInput( attrs={ "class": "form-control", "readonly": True, "value": current_date.strftime('%d.%m.%Y'), # German date format "style": "background-color: #f8f9fa; cursor: not-allowed;" } ) self.fields['faellig_am'].initial = current_date self.fields['faellig_am'].help_text = "Fälligkeitsdatum wird automatisch basierend auf Quartal berechnet" def clean(self): cleaned_data = super().clean() # For auto-generated payments, preserve the original due date if self.is_auto_generated and self.instance and self.instance.pk: cleaned_data['faellig_am'] = self.instance.faellig_am return cleaned_data 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 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 VierteljahresNachweisForm(forms.ModelForm): """Form for quarterly confirmations (Vierteljahresnachweise)""" class Meta: model = VierteljahresNachweis fields = [ 'studiennachweis_eingereicht', 'studiennachweis_datei', 'studiennachweis_bemerkung', 'einkommenssituation_bestaetigt', 'einkommenssituation_text', 'einkommenssituation_datei', 'vermogenssituation_bestaetigt', 'vermogenssituation_text', 'vermogenssituation_datei', 'weitere_dokumente', 'weitere_dokumente_beschreibung', 'interne_notizen', ] widgets = { 'studiennachweis_eingereicht': forms.CheckboxInput(attrs={'class': 'form-check-input'}), 'studiennachweis_datei': forms.FileInput(attrs={'class': 'form-control', 'accept': '.pdf,.jpg,.jpeg,.png,.doc,.docx'}), 'studiennachweis_bemerkung': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}), 'einkommenssituation_bestaetigt': forms.CheckboxInput(attrs={'class': 'form-check-input'}), 'einkommenssituation_text': forms.Textarea(attrs={'class': 'form-control', 'rows': 3, 'placeholder': 'Z.B. "Keine Änderungen seit letzter Meldung"'}), 'einkommenssituation_datei': forms.FileInput(attrs={'class': 'form-control', 'accept': '.pdf,.jpg,.jpeg,.png,.doc,.docx'}), 'vermogenssituation_bestaetigt': forms.CheckboxInput(attrs={'class': 'form-check-input'}), 'vermogenssituation_text': forms.Textarea(attrs={'class': 'form-control', 'rows': 3, 'placeholder': 'Z.B. "Keine Änderungen seit letzter Meldung"'}), 'vermogenssituation_datei': forms.FileInput(attrs={'class': 'form-control', 'accept': '.pdf,.jpg,.jpeg,.png,.doc,.docx'}), 'weitere_dokumente': forms.FileInput(attrs={'class': 'form-control', 'accept': '.pdf,.jpg,.jpeg,.png,.doc,.docx'}), 'weitere_dokumente_beschreibung': forms.Textarea(attrs={'class': 'form-control', 'rows': 2}), 'interne_notizen': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}), } labels = { 'studiennachweis_erforderlich': 'Studiennachweis erforderlich', 'studiennachweis_eingereicht': 'Studiennachweis eingereicht', 'studiennachweis_datei': 'Studiennachweis (Datei)', 'studiennachweis_bemerkung': 'Bemerkung zum Studiennachweis', 'einkommenssituation_bestaetigt': 'Einkommenssituation bestätigt', 'einkommenssituation_text': 'Einkommenssituation (Text)', 'einkommenssituation_datei': 'Einkommenssituation (Datei)', 'vermogenssituation_bestaetigt': 'Vermögenssituation bestätigt', 'vermogenssituation_text': 'Vermögenssituation (Text)', 'vermogenssituation_datei': 'Vermögenssituation (Datei)', 'weitere_dokumente': 'Weitere Dokumente', 'weitere_dokumente_beschreibung': 'Beschreibung weitere Dokumente', 'interne_notizen': 'Interne Notizen (nur für Verwaltung)', } help_texts = { 'einkommenssituation_text': 'Z.B. "Keine Änderungen seit letzter Meldung" oder Details zu Änderungen', 'vermogenssituation_text': 'Z.B. "Keine Änderungen seit letzter Meldung" oder Details zu Änderungen', 'interne_notizen': 'Diese Notizen sind nur für die interne Verwaltung sichtbar', } def clean(self): cleaned_data = super().clean() # Validate that at least one form of confirmation is provided for income situation einkommenssituation_text = cleaned_data.get('einkommenssituation_text') einkommenssituation_datei = cleaned_data.get('einkommenssituation_datei') einkommenssituation_bestaetigt = cleaned_data.get('einkommenssituation_bestaetigt') if einkommenssituation_bestaetigt and not einkommenssituation_text and not einkommenssituation_datei: raise ValidationError( 'Wenn die Einkommenssituation bestätigt wird, muss entweder ein Text oder eine Datei angegeben werden.' ) # Validate that at least one form of confirmation is provided for asset situation vermogenssituation_text = cleaned_data.get('vermogenssituation_text') vermogenssituation_datei = cleaned_data.get('vermogenssituation_datei') vermogenssituation_bestaetigt = cleaned_data.get('vermogenssituation_bestaetigt') if vermogenssituation_bestaetigt and not vermogenssituation_text and not vermogenssituation_datei: raise ValidationError( 'Wenn die Vermögenssituation bestätigt wird, muss entweder ein Text oder eine Datei angegeben werden.' ) # Validate study proof if required and marked as submitted studiennachweis_erforderlich = cleaned_data.get('studiennachweis_erforderlich') studiennachweis_eingereicht = cleaned_data.get('studiennachweis_eingereicht') studiennachweis_datei = cleaned_data.get('studiennachweis_datei') studiennachweis_bemerkung = cleaned_data.get('studiennachweis_bemerkung') if studiennachweis_erforderlich and studiennachweis_eingereicht: if not studiennachweis_datei and not studiennachweis_bemerkung: raise ValidationError( 'Wenn der Studiennachweis als eingereicht markiert wird, muss entweder eine Datei oder eine Bemerkung angegeben werden.' ) return cleaned_data