Getrennte Fristen für Studiennachweis und Zahlung implementieren
Some checks failed
Code Quality / quality (push) Has been cancelled
CI/CD Pipeline / test (push) Has been cancelled
CI/CD Pipeline / deploy (push) Has been cancelled

- Neue Felder: studiennachweis_faelligkeitsdatum (semesterbasiert) und zahlung_faelligkeitsdatum (vierteljährlich im Voraus)
- Studiennachweis-Fristen: Q1/Q2 → 15. März, Q3/Q4 → 15. September
- Zahlungsfälligkeiten: Q1 → 15. Dez (Vorjahr), Q2 → 15. Mär, Q3 → 15. Jun, Q4 → 15. Sep
- Auto-Freigabe: Q1 freigeben → Q2 Studiennachweis auto-freigegeben, Q3 → Q4
- Unterstützungserstellung: Verhindert Duplikate durch präzise Suche nach zahlung_faelligkeitsdatum
- Quartalserstellung: Modal-Formular funktioniert korrekt
- UI: Beide Fristen in Tabelle angezeigt, separate Überfälligkeits-Indikatoren
- Migration: Neue Felder hinzugefügt und bestehende Datensätze befüllt
This commit is contained in:
2025-12-30 20:20:33 +01:00
parent 24435660f5
commit 6c8ddbb4f0
6 changed files with 542 additions and 187 deletions

View File

@@ -7422,21 +7422,33 @@ def create_quarterly_support_payment(nachweis):
if not destinataer.iban:
return None
# Calculate quarter date range for more robust search
quarter_start = datetime(nachweis.jahr, (nachweis.quartal - 1) * 3 + 1, 1).date()
if nachweis.quartal == 4: # Q4 special case
quarter_end = datetime(nachweis.jahr + 1, 1, 1).date()
else:
quarter_end = datetime(nachweis.jahr, nachweis.quartal * 3 + 1, 1).date()
# Search for existing payment using payment due date from quarterly confirmation
# This is more accurate than using quarter date range, especially for Q1 (Dec 15 prev year)
payment_due_date = nachweis.zahlung_faelligkeitsdatum
if not payment_due_date:
# Fallback: calculate if not set
if nachweis.quartal == 1:
payment_due_date = date(nachweis.jahr - 1, 12, 15)
elif nachweis.quartal == 2:
payment_due_date = date(nachweis.jahr, 3, 15)
elif nachweis.quartal == 3:
payment_due_date = date(nachweis.jahr, 6, 15)
else: # Q4
payment_due_date = date(nachweis.jahr, 9, 15)
# Search for existing payment - match by payment due date and description
# Use a date range around the due date (±30 days) to catch any variations
from datetime import timedelta
date_start = payment_due_date - timedelta(days=30)
date_end = payment_due_date + timedelta(days=30)
# Search for existing payment - use broader criteria to catch all possibilities
existing_payment = DestinataerUnterstuetzung.objects.filter(
destinataer=destinataer,
faellig_am__gte=quarter_start,
faellig_am__lt=quarter_end
faellig_am__gte=date_start,
faellig_am__lte=date_end
).filter(
Q(beschreibung__contains=f"Q{nachweis.quartal}/{nachweis.jahr}") |
Q(beschreibung__contains=f"Vierteljährliche Unterstützung")
Q(beschreibung__contains=f"Vierteljährliche Unterstützung Q{nachweis.quartal}/{nachweis.jahr}")
).first()
if existing_payment:
@@ -7457,17 +7469,19 @@ def create_quarterly_support_payment(nachweis):
if not default_konto:
return None
# Calculate payment due date (advance payments 3 months before quarter)
# Q1: December 15 (previous year), Q2: March 15, Q3: June 15, Q4: September 15
if nachweis.quartal == 1: # Q1 payment due December 15 of previous year
payment_due_date = date(nachweis.jahr - 1, 12, 15)
elif nachweis.quartal == 2: # Q2 payment due March 15
payment_due_date = date(nachweis.jahr, 3, 15)
elif nachweis.quartal == 3: # Q3 payment due June 15
payment_due_date = date(nachweis.jahr, 6, 15)
else: # Q4 payment due September 15
payment_due_date = date(nachweis.jahr, 9, 15)
# Use payment due date from quarterly confirmation (already calculated by model)
# This ensures consistency with zahlung_faelligkeitsdatum
payment_due_date = nachweis.zahlung_faelligkeitsdatum
if not payment_due_date:
# Fallback: calculate if not set (should not happen, but safety check)
if nachweis.quartal == 1: # Q1 payment due December 15 of previous year
payment_due_date = date(nachweis.jahr - 1, 12, 15)
elif nachweis.quartal == 2: # Q2 payment due March 15
payment_due_date = date(nachweis.jahr, 3, 15)
elif nachweis.quartal == 3: # Q3 payment due June 15
payment_due_date = date(nachweis.jahr, 6, 15)
else: # Q4 payment due September 15
payment_due_date = date(nachweis.jahr, 9, 15)
# Create the support payment
payment = DestinataerUnterstuetzung.objects.create(
@@ -7490,9 +7504,14 @@ def create_quarterly_support_payment(nachweis):
@login_required
def quarterly_confirmation_create(request, destinataer_id):
"""Create a new quarterly confirmation for a destinataer"""
import logging
logger = logging.getLogger(__name__)
logger.info(f"quarterly_confirmation_create called: method={request.method}, destinataer_id={destinataer_id}")
destinataer = get_object_or_404(Destinataer, pk=destinataer_id)
if request.method == "POST":
logger.info(f"POST data: {request.POST}")
jahr = request.POST.get('jahr')
quartal = request.POST.get('quartal')
@@ -7515,26 +7534,41 @@ def quarterly_confirmation_create(request, destinataer_id):
)
else:
# Create new quarterly confirmation
nachweis = VierteljahresNachweis.objects.create(
destinataer=destinataer,
jahr=jahr,
quartal=quartal,
studiennachweis_erforderlich=True, # Always required now
)
# Set deadline (15th of second month of quarter)
deadline_months = {1: 5, 2: 8, 3: 11, 4: 2} # Q1->May, Q2->Aug, Q3->Nov, Q4->Feb(next year)
deadline_month = deadline_months[quartal]
deadline_year = jahr if quartal != 4 else jahr + 1
from datetime import date
nachweis.faelligkeitsdatum = date(deadline_year, deadline_month, 15)
nachweis.save()
messages.success(
request,
f"Quartal {jahr} Q{quartal} wurde erfolgreich für {destinataer.get_full_name()} erstellt."
)
try:
nachweis = VierteljahresNachweis.objects.create(
destinataer=destinataer,
jahr=jahr,
quartal=quartal,
studiennachweis_erforderlich=True, # Always required now
)
# Deadlines are automatically set by the model's save() method
# studiennachweis_faelligkeitsdatum: semester-based (Q1/Q2→Mar 15, Q3/Q4→Sep 15)
# zahlung_faelligkeitsdatum: quarterly advance (Q1→Dec 15 prev year, Q2→Mar 15, Q3→Jun 15, Q4→Sep 15)
# Refresh from database to ensure deadlines are set
nachweis.refresh_from_db()
studiennachweis_str = nachweis.studiennachweis_faelligkeitsdatum.strftime('%d.%m.%Y') if nachweis.studiennachweis_faelligkeitsdatum else "Nicht gesetzt"
zahlung_str = nachweis.zahlung_faelligkeitsdatum.strftime('%d.%m.%Y') if nachweis.zahlung_faelligkeitsdatum else "Nicht gesetzt"
messages.success(
request,
f"Quartal {jahr} Q{quartal} wurde erfolgreich für {destinataer.get_full_name()} erstellt. "
f"Studiennachweis fällig: {studiennachweis_str}, "
f"Zahlung fällig: {zahlung_str}."
)
except Exception as e:
from django.db import IntegrityError
if isinstance(e, IntegrityError):
messages.error(
request,
f"Quartal {jahr} Q{quartal} existiert bereits für {destinataer.get_full_name()}."
)
else:
messages.error(
request,
f"Fehler beim Erstellen des Quartals: {str(e)}"
)
except (ValueError, TypeError):
messages.error(request, "Ungültige Jahr- oder Quartalswerte.")
@@ -7655,41 +7689,35 @@ def quarterly_confirmation_approve(request, pk):
)
# Handle support payment - create if missing, update if exists
if not related_payment:
# Create new support payment
related_payment = create_quarterly_support_payment(nachweis)
if related_payment:
related_payment.status = 'in_bearbeitung'
related_payment.aktualisiert_am = timezone.now()
related_payment.save()
# Check if payment already exists before calling create_quarterly_support_payment()
payment_existed_before = related_payment is not None
# Use create_quarterly_support_payment() which handles both cases (find existing or create new)
related_payment = create_quarterly_support_payment(nachweis)
if related_payment:
# Update status to 'in_bearbeitung' for both new and existing payments
old_status = related_payment.status
related_payment.status = 'in_bearbeitung'
related_payment.aktualisiert_am = timezone.now()
related_payment.save()
if payment_existed_before:
messages.success(
request,
f"Vierteljahresnachweis freigegeben und bestehende Unterstützung für {nachweis.destinataer.get_full_name()} "
f"({nachweis.jahr} Q{nachweis.quartal}) wurde von '{old_status}' auf 'in Bearbeitung' aktualisiert."
)
else:
messages.success(
request,
f"Vierteljahresnachweis freigegeben und neue Unterstützung über {related_payment.betrag}€ für {nachweis.destinataer.get_full_name()} "
f"({nachweis.jahr} Q{nachweis.quartal}) wurde erstellt."
)
else:
messages.warning(
request,
f"Vierteljahresnachweis freigegeben, aber Unterstützung konnte nicht erstellt werden. "
f"Bitte prüfen Sie die Einstellungen für {nachweis.destinataer.get_full_name()}."
)
elif related_payment.status == 'geplant':
# Update existing payment
related_payment.status = 'in_bearbeitung'
related_payment.aktualisiert_am = timezone.now()
related_payment.save()
messages.success(
request,
f"Vierteljahresnachweis und zugehörige Unterstützung für {nachweis.destinataer.get_full_name()} "
f"({nachweis.jahr} Q{nachweis.quartal}) wurden freigegeben."
)
else:
messages.success(
messages.warning(
request,
f"Vierteljahresnachweis für {nachweis.destinataer.get_full_name()} "
f"({nachweis.jahr} Q{nachweis.quartal}) wurde freigegeben."
f"Vierteljahresnachweis freigegeben, aber Unterstützung konnte nicht erstellt werden. "
f"Bitte prüfen Sie die Einstellungen für {nachweis.destinataer.get_full_name()}."
)
else:
messages.error(