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

@@ -0,0 +1,84 @@
# Generated migration for separate study proof and payment deadlines
from django.db import migrations, models
from datetime import date
def calculate_study_proof_deadline(jahr, quartal):
"""Calculate semester-based study proof deadline"""
# Q1, Q2 → March 15 (same year)
# Q3, Q4 → September 15 (same year)
if quartal in [1, 2]:
return date(jahr, 3, 15)
else: # Q3, Q4
return date(jahr, 9, 15)
def calculate_payment_due_date(jahr, quartal):
"""Calculate quarterly payment due date (paid in advance)"""
# Q1 → December 15 (previous year)
# Q2 → March 15 (same year)
# Q3 → June 15 (same year)
# Q4 → September 15 (same year)
if quartal == 1:
return date(jahr - 1, 12, 15)
elif quartal == 2:
return date(jahr, 3, 15)
elif quartal == 3:
return date(jahr, 6, 15)
else: # Q4
return date(jahr, 9, 15)
def populate_deadlines(apps, schema_editor):
"""Populate new deadline fields for existing records"""
VierteljahresNachweis = apps.get_model('stiftung', 'VierteljahresNachweis')
for nachweis in VierteljahresNachweis.objects.all():
# Calculate and set study proof deadline
if not nachweis.studiennachweis_faelligkeitsdatum:
nachweis.studiennachweis_faelligkeitsdatum = calculate_study_proof_deadline(
nachweis.jahr, nachweis.quartal
)
# Calculate and set payment due date
if not nachweis.zahlung_faelligkeitsdatum:
nachweis.zahlung_faelligkeitsdatum = calculate_payment_due_date(
nachweis.jahr, nachweis.quartal
)
nachweis.save(update_fields=['studiennachweis_faelligkeitsdatum', 'zahlung_faelligkeitsdatum'])
class Migration(migrations.Migration):
dependencies = [
('stiftung', '0041_alter_geschichteseite_inhalt'),
]
operations = [
# Add new fields
migrations.AddField(
model_name='vierteljahresnachweis',
name='studiennachweis_faelligkeitsdatum',
field=models.DateField(
blank=True,
help_text='Semesterbasierte Frist: Q1/Q2 → 15. März, Q3/Q4 → 15. September',
null=True,
verbose_name='Studiennachweis Fälligkeitsdatum'
),
),
migrations.AddField(
model_name='vierteljahresnachweis',
name='zahlung_faelligkeitsdatum',
field=models.DateField(
blank=True,
help_text='Vierteljährliche Zahlungsfälligkeit im Voraus: Q1→15. Dez (Vorjahr), Q2→15. Mär, Q3→15. Jun, Q4→15. Sep',
null=True,
verbose_name='Zahlungsfälligkeit'
),
),
# Populate existing records
migrations.RunPython(populate_deadlines, migrations.RunPython.noop),
]

View File

@@ -2665,7 +2665,23 @@ class VierteljahresNachweis(models.Model):
faelligkeitsdatum = models.DateField( faelligkeitsdatum = models.DateField(
null=True, null=True,
blank=True, blank=True,
verbose_name="Fälligkeitsdatum" verbose_name="Fälligkeitsdatum",
help_text="Veraltet - wird durch studiennachweis_faelligkeitsdatum und zahlung_faelligkeitsdatum ersetzt"
)
# Separate deadlines for study proof (semester-based) and payment (quarterly)
studiennachweis_faelligkeitsdatum = models.DateField(
null=True,
blank=True,
verbose_name="Studiennachweis Fälligkeitsdatum",
help_text="Semesterbasierte Frist: Q1/Q2 → 15. März, Q3/Q4 → 15. September"
)
zahlung_faelligkeitsdatum = models.DateField(
null=True,
blank=True,
verbose_name="Zahlungsfälligkeit",
help_text="Vierteljährliche Zahlungsfälligkeit im Voraus: Q1→15. Dez (Vorjahr), Q2→15. Mär, Q3→15. Jun, Q4→15. Sep"
) )
class Meta: class Meta:
@@ -2746,18 +2762,67 @@ class VierteljahresNachweis(models.Model):
return int((completed_requirements / total_requirements) * 100) if total_requirements > 0 else 0 return int((completed_requirements / total_requirements) * 100) if total_requirements > 0 else 0
def get_study_proof_deadline(self):
"""Calculate semester-based study proof deadline"""
from datetime import date
# Q1, Q2 → March 15 (same year)
# Q3, Q4 → September 15 (same year)
if self.quartal in [1, 2]:
return date(self.jahr, 3, 15)
else: # Q3, Q4
return date(self.jahr, 9, 15)
def get_payment_due_date(self):
"""Calculate quarterly payment due date (paid in advance)"""
from datetime import date
# Q1 → December 15 (previous year)
# Q2 → March 15 (same year)
# Q3 → June 15 (same year)
# Q4 → September 15 (same year)
if self.quartal == 1:
return date(self.jahr - 1, 12, 15)
elif self.quartal == 2:
return date(self.jahr, 3, 15)
elif self.quartal == 3:
return date(self.jahr, 6, 15)
else: # Q4
return date(self.jahr, 9, 15)
def is_study_proof_overdue(self):
"""Check if study proof deadline has passed"""
if not self.studiennachweis_faelligkeitsdatum:
return False
from django.utils import timezone
return timezone.now().date() > self.studiennachweis_faelligkeitsdatum and not self.studiennachweis_eingereicht
def is_payment_overdue(self):
"""Check if payment due date has passed"""
if not self.zahlung_faelligkeitsdatum:
return False
from django.utils import timezone
# Payment is overdue if due date passed and no payment exists or payment is not completed
payment = self.get_related_support_payment()
if payment and payment.status in ['bezahlt', 'in_bearbeitung']:
return False
return timezone.now().date() > self.zahlung_faelligkeitsdatum
def is_overdue(self):
"""Check if either deadline has passed"""
return self.is_study_proof_overdue() or self.is_payment_overdue()
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
"""Override save to auto-update status and timestamps""" """Override save to auto-update status and timestamps"""
# Auto-set deadline if not provided (semester-based deadlines) # Set study proof deadline (semester-based) if not provided
if not self.studiennachweis_faelligkeitsdatum:
self.studiennachweis_faelligkeitsdatum = self.get_study_proof_deadline()
# Set payment due date (quarterly, advance) if not provided
if not self.zahlung_faelligkeitsdatum:
self.zahlung_faelligkeitsdatum = self.get_payment_due_date()
# Backward compatibility: set faelligkeitsdatum from study proof deadline if not set
if not self.faelligkeitsdatum: if not self.faelligkeitsdatum:
from datetime import date self.faelligkeitsdatum = self.studiennachweis_faelligkeitsdatum
quarter_deadlines = {
1: date(self.jahr, 3, 15), # Q1 deadline: March 15 (Spring semester)
2: date(self.jahr, 3, 15), # Q2 deadline: March 15 (Spring semester, same as Q1)
3: date(self.jahr, 9, 15), # Q3 deadline: September 15 (Fall semester)
4: date(self.jahr, 9, 15), # Q4 deadline: September 15 (Fall semester, same as Q3)
}
self.faelligkeitsdatum = quarter_deadlines.get(self.quartal)
# Auto-update status based on completion # Auto-update status based on completion
if self.is_complete(): if self.is_complete():
@@ -2773,18 +2838,71 @@ class VierteljahresNachweis(models.Model):
def get_related_support_payment(self): def get_related_support_payment(self):
"""Get the related support payment for this quarterly confirmation""" """Get the related support payment for this quarterly confirmation"""
from datetime import datetime from datetime import date, timedelta
from django.db.models import Q
quarter_start = datetime(self.jahr, (self.quartal - 1) * 3 + 1, 1).date() # Use payment due date from quarterly confirmation for accurate search
quarter_end = datetime(self.jahr, self.quartal * 3, 1).date() # This is more accurate than using quarter date range, especially for Q1 (Dec 15 prev year)
payment_due_date = self.zahlung_faelligkeitsdatum
if not payment_due_date:
# Fallback: calculate if not set
if self.quartal == 1:
payment_due_date = date(self.jahr - 1, 12, 15)
elif self.quartal == 2:
payment_due_date = date(self.jahr, 3, 15)
elif self.quartal == 3:
payment_due_date = date(self.jahr, 6, 15)
else: # Q4
payment_due_date = date(self.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
date_start = payment_due_date - timedelta(days=30)
date_end = payment_due_date + timedelta(days=30)
return DestinataerUnterstuetzung.objects.filter( return DestinataerUnterstuetzung.objects.filter(
destinataer=self.destinataer, destinataer=self.destinataer,
faellig_am__gte=quarter_start, faellig_am__gte=date_start,
faellig_am__lt=quarter_end, faellig_am__lte=date_end
beschreibung__contains=f"Q{self.quartal}/{self.jahr}" ).filter(
Q(beschreibung__contains=f"Q{self.quartal}/{self.jahr}") |
Q(beschreibung__contains=f"Vierteljährliche Unterstützung Q{self.quartal}/{self.jahr}")
).first() ).first()
def auto_approve_next_quarter(self):
"""Auto-approve the next quarter when Q1 or Q3 is approved (semester-based logic)"""
if self.quartal in [1, 3] and self.status == "geprueft":
next_quarter = self.quartal + 1
try:
next_nachweis = VierteljahresNachweis.objects.get(
destinataer=self.destinataer,
jahr=self.jahr,
quartal=next_quarter
)
if next_nachweis.status in ["offen", "teilweise"]:
# Copy study proof confirmations from current quarter (semester-based)
next_nachweis.studiennachweis_eingereicht = self.studiennachweis_eingereicht
next_nachweis.studiennachweis_datei = self.studiennachweis_datei
next_nachweis.studiennachweis_bemerkung = self.studiennachweis_bemerkung
# Set study proof deadline for next quarter (same semester)
next_nachweis.studiennachweis_faelligkeitsdatum = next_nachweis.get_study_proof_deadline()
# Set auto-approved status
next_nachweis.status = "auto_geprueft"
next_nachweis.geprueft_am = timezone.now()
next_nachweis.geprueft_von = self.geprueft_von
next_nachweis.save(update_fields=[
'studiennachweis_eingereicht', 'studiennachweis_datei', 'studiennachweis_bemerkung',
'studiennachweis_faelligkeitsdatum', 'status', 'geprueft_am', 'geprueft_von'
])
return next_nachweis
except VierteljahresNachweis.DoesNotExist:
pass
return None
@classmethod @classmethod
def get_or_create_for_period(cls, destinataer, jahr, quartal): def get_or_create_for_period(cls, destinataer, jahr, quartal):
"""Get or create a quarterly confirmation for a specific period""" """Get or create a quarterly confirmation for a specific period"""

View File

@@ -7422,21 +7422,33 @@ def create_quarterly_support_payment(nachweis):
if not destinataer.iban: if not destinataer.iban:
return None return None
# Calculate quarter date range for more robust search # Search for existing payment using payment due date from quarterly confirmation
quarter_start = datetime(nachweis.jahr, (nachweis.quartal - 1) * 3 + 1, 1).date() # This is more accurate than using quarter date range, especially for Q1 (Dec 15 prev year)
if nachweis.quartal == 4: # Q4 special case payment_due_date = nachweis.zahlung_faelligkeitsdatum
quarter_end = datetime(nachweis.jahr + 1, 1, 1).date() if not payment_due_date:
else: # Fallback: calculate if not set
quarter_end = datetime(nachweis.jahr, nachweis.quartal * 3 + 1, 1).date() 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( existing_payment = DestinataerUnterstuetzung.objects.filter(
destinataer=destinataer, destinataer=destinataer,
faellig_am__gte=quarter_start, faellig_am__gte=date_start,
faellig_am__lt=quarter_end faellig_am__lte=date_end
).filter( ).filter(
Q(beschreibung__contains=f"Q{nachweis.quartal}/{nachweis.jahr}") | 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() ).first()
if existing_payment: if existing_payment:
@@ -7457,9 +7469,11 @@ def create_quarterly_support_payment(nachweis):
if not default_konto: if not default_konto:
return None return None
# Calculate payment due date (advance payments 3 months before quarter) # Use payment due date from quarterly confirmation (already calculated by model)
# Q1: December 15 (previous year), Q2: March 15, Q3: June 15, Q4: September 15 # 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 if nachweis.quartal == 1: # Q1 payment due December 15 of previous year
payment_due_date = date(nachweis.jahr - 1, 12, 15) payment_due_date = date(nachweis.jahr - 1, 12, 15)
elif nachweis.quartal == 2: # Q2 payment due March 15 elif nachweis.quartal == 2: # Q2 payment due March 15
@@ -7490,9 +7504,14 @@ def create_quarterly_support_payment(nachweis):
@login_required @login_required
def quarterly_confirmation_create(request, destinataer_id): def quarterly_confirmation_create(request, destinataer_id):
"""Create a new quarterly confirmation for a destinataer""" """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) destinataer = get_object_or_404(Destinataer, pk=destinataer_id)
if request.method == "POST": if request.method == "POST":
logger.info(f"POST data: {request.POST}")
jahr = request.POST.get('jahr') jahr = request.POST.get('jahr')
quartal = request.POST.get('quartal') quartal = request.POST.get('quartal')
@@ -7515,25 +7534,40 @@ def quarterly_confirmation_create(request, destinataer_id):
) )
else: else:
# Create new quarterly confirmation # Create new quarterly confirmation
try:
nachweis = VierteljahresNachweis.objects.create( nachweis = VierteljahresNachweis.objects.create(
destinataer=destinataer, destinataer=destinataer,
jahr=jahr, jahr=jahr,
quartal=quartal, quartal=quartal,
studiennachweis_erforderlich=True, # Always required now 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)
# Set deadline (15th of second month of quarter) # Refresh from database to ensure deadlines are set
deadline_months = {1: 5, 2: 8, 3: 11, 4: 2} # Q1->May, Q2->Aug, Q3->Nov, Q4->Feb(next year) nachweis.refresh_from_db()
deadline_month = deadline_months[quartal]
deadline_year = jahr if quartal != 4 else jahr + 1
from datetime import date studiennachweis_str = nachweis.studiennachweis_faelligkeitsdatum.strftime('%d.%m.%Y') if nachweis.studiennachweis_faelligkeitsdatum else "Nicht gesetzt"
nachweis.faelligkeitsdatum = date(deadline_year, deadline_month, 15) zahlung_str = nachweis.zahlung_faelligkeitsdatum.strftime('%d.%m.%Y') if nachweis.zahlung_faelligkeitsdatum else "Nicht gesetzt"
nachweis.save()
messages.success( messages.success(
request, request,
f"Quartal {jahr} Q{quartal} wurde erfolgreich für {destinataer.get_full_name()} erstellt." 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): except (ValueError, TypeError):
@@ -7655,14 +7689,25 @@ def quarterly_confirmation_approve(request, pk):
) )
# Handle support payment - create if missing, update if exists # Handle support payment - create if missing, update if exists
if not related_payment: # Check if payment already exists before calling create_quarterly_support_payment()
# Create new 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) related_payment = create_quarterly_support_payment(nachweis)
if related_payment: 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.status = 'in_bearbeitung'
related_payment.aktualisiert_am = timezone.now() related_payment.aktualisiert_am = timezone.now()
related_payment.save() 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( messages.success(
request, request,
f"Vierteljahresnachweis freigegeben und neue Unterstützung über {related_payment.betrag}€ für {nachweis.destinataer.get_full_name()} " f"Vierteljahresnachweis freigegeben und neue Unterstützung über {related_payment.betrag}€ für {nachweis.destinataer.get_full_name()} "
@@ -7674,23 +7719,6 @@ def quarterly_confirmation_approve(request, pk):
f"Vierteljahresnachweis freigegeben, aber Unterstützung konnte nicht erstellt werden. " f"Vierteljahresnachweis freigegeben, aber Unterstützung konnte nicht erstellt werden. "
f"Bitte prüfen Sie die Einstellungen für {nachweis.destinataer.get_full_name()}." 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(
request,
f"Vierteljahresnachweis für {nachweis.destinataer.get_full_name()} "
f"({nachweis.jahr} Q{nachweis.quartal}) wurde freigegeben."
)
else: else:
messages.error( messages.error(
request, request,

View File

@@ -466,7 +466,7 @@
<i class="fas fa-calendar-check me-2"></i>Vierteljährliche Nachweise <i class="fas fa-calendar-check me-2"></i>Vierteljährliche Nachweise
</h5> </h5>
<div> <div>
<small class="text-light me-3">Frist: jeweils 15. des zweiten Quartalsmonats</small> <small class="text-light me-3">Studiennachweis: 15. März / 15. September | Zahlung: vierteljährlich im Voraus</small>
<button type="button" class="btn btn-sm btn-light" <button type="button" class="btn btn-sm btn-light"
data-bs-toggle="modal" data-bs-toggle="modal"
data-bs-target="#addQuarterModal" data-bs-target="#addQuarterModal"
@@ -478,32 +478,76 @@
</div> </div>
<div class="card-body"> <div class="card-body">
{% if quarterly_confirmations %} {% if quarterly_confirmations %}
<style>
.quarterly-table {
font-size: 0.9rem;
table-layout: auto;
}
.quarterly-table th {
white-space: nowrap;
font-size: 0.85rem;
padding: 0.6rem 0.5rem;
}
.quarterly-table td {
padding: 0.6rem 0.5rem;
vertical-align: middle;
}
.quarterly-table .col-zeitraum {
width: 85px;
}
.quarterly-table .col-status {
width: 100px;
}
.quarterly-table .col-fortschritt {
width: 70px;
}
.quarterly-table .col-fristen {
width: 120px;
}
.quarterly-table .col-check {
width: 40px;
text-align: center;
padding: 0.6rem 0.3rem;
}
.quarterly-table .col-aktionen {
width: 110px;
}
.quarterly-table .progress {
height: 18px;
font-size: 0.75rem;
margin: 0;
}
.quarterly-table .badge {
font-size: 0.75rem;
padding: 0.3em 0.5em;
}
</style>
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-hover"> <table class="table table-hover table-sm quarterly-table">
<thead class="table-light"> <thead class="table-light">
<tr> <tr>
<th>Zeitraum</th> <th class="col-zeitraum">Zeitraum</th>
<th>Status</th> <th class="col-status">Status</th>
<th>Fortschritt</th> <th class="col-fortschritt">Fortschritt</th>
<th>Fälligkeit</th> <th class="col-fristen">Fristen</th>
<th>Studiennachweis</th> <th class="col-check" title="Studiennachweis"><i class="fas fa-graduation-cap"></i></th>
<th>Einkommen</th> <th class="col-check" title="Einkommen"><i class="fas fa-euro-sign"></i></th>
<th>Vermögen</th> <th class="col-check" title="Vermögen"><i class="fas fa-piggy-bank"></i></th>
<th>Aktionen</th> <th class="col-aktionen">Aktionen</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for nachweis in quarterly_confirmations %} {% for nachweis in quarterly_confirmations %}
<tr {% if nachweis.is_overdue %}class="table-warning"{% endif %}> <tr {% if nachweis.is_overdue %}class="table-warning"{% endif %}>
<td> <td class="col-zeitraum">
<strong>{{ nachweis.jahr }} {{ nachweis.get_quarter_display|truncatechars:3 }}</strong> <strong>{{ nachweis.jahr }} Q{{ nachweis.quartal }}</strong>
{% if nachweis.is_overdue %} {% if nachweis.is_overdue %}
<br><small class="text-danger"><i class="fas fa-exclamation-triangle"></i> Überfällig</small> <br><small class="text-danger"><i class="fas fa-exclamation-triangle"></i></small>
{% endif %} {% endif %}
</td> </td>
<td> <td class="col-status">
{% if nachweis.status == 'offen' %} {% if nachweis.status == 'offen' %}
<span class="badge bg-secondary">Ausstehend</span> <span class="badge bg-secondary">Offen</span>
{% elif nachweis.status == 'teilweise' %} {% elif nachweis.status == 'teilweise' %}
<span class="badge bg-warning">Teilweise</span> <span class="badge bg-warning">Teilweise</span>
{% elif nachweis.status == 'eingereicht' %} {% elif nachweis.status == 'eingereicht' %}
@@ -511,8 +555,8 @@
{% elif nachweis.status == 'geprueft' %} {% elif nachweis.status == 'geprueft' %}
<span class="badge bg-success">Freigegeben</span> <span class="badge bg-success">Freigegeben</span>
{% elif nachweis.status == 'auto_geprueft' %} {% elif nachweis.status == 'auto_geprueft' %}
<span class="badge bg-success"> <span class="badge bg-success" title="Auto-Freigabe">
<i class="fas fa-magic me-1"></i>Auto-Freigabe <i class="fas fa-magic"></i> Auto
</span> </span>
{% elif nachweis.status == 'nachbesserung' %} {% elif nachweis.status == 'nachbesserung' %}
<span class="badge bg-warning">Nachbesserung</span> <span class="badge bg-warning">Nachbesserung</span>
@@ -520,9 +564,9 @@
<span class="badge bg-danger">Abgelehnt</span> <span class="badge bg-danger">Abgelehnt</span>
{% endif %} {% endif %}
</td> </td>
<td> <td class="col-fortschritt">
{% with completion=nachweis.get_completion_percentage %} {% with completion=nachweis.get_completion_percentage %}
<div class="progress" style="height: 20px;"> <div class="progress" title="{{ completion }}%">
<div class="progress-bar <div class="progress-bar
{% if completion == 100 %}bg-success {% if completion == 100 %}bg-success
{% elif completion >= 70 %}bg-info {% elif completion >= 70 %}bg-info
@@ -539,14 +583,33 @@
</div> </div>
{% endwith %} {% endwith %}
</td> </td>
<td> <td class="col-fristen">
{% if nachweis.faelligkeitsdatum %} <div class="small" style="line-height: 1.4;">
<small>{{ nachweis.faelligkeitsdatum|date:"d.m.Y" }}</small> {% if nachweis.studiennachweis_faelligkeitsdatum %}
{% else %} <div class="mb-1">
<small class="text-muted">Nicht festgelegt</small> <i class="fas fa-graduation-cap text-primary" title="Studiennachweis"></i>
<span class="{% if nachweis.is_study_proof_overdue %}text-danger fw-bold{% endif %}">
{{ nachweis.studiennachweis_faelligkeitsdatum|date:"d.m.Y" }}
</span>
{% if nachweis.is_study_proof_overdue %}
<i class="fas fa-exclamation-triangle text-danger" title="Überfällig"></i>
{% endif %} {% endif %}
</div>
{% endif %}
{% if nachweis.zahlung_faelligkeitsdatum %}
<div>
<i class="fas fa-euro-sign text-success" title="Zahlung"></i>
<span class="{% if nachweis.is_payment_overdue %}text-danger fw-bold{% endif %}">
{{ nachweis.zahlung_faelligkeitsdatum|date:"d.m.Y" }}
</span>
{% if nachweis.is_payment_overdue %}
<i class="fas fa-exclamation-triangle text-danger" title="Überfällig"></i>
{% endif %}
</div>
{% endif %}
</div>
</td> </td>
<td class="text-center"> <td class="col-check">
{% if nachweis.studiennachweis_eingereicht %} {% if nachweis.studiennachweis_eingereicht %}
{% if nachweis.studiennachweis_datei %} {% if nachweis.studiennachweis_datei %}
<a href="{{ nachweis.studiennachweis_datei.url }}" target="_blank" class="text-success" title="Datei ansehen"> <a href="{{ nachweis.studiennachweis_datei.url }}" target="_blank" class="text-success" title="Datei ansehen">
@@ -555,13 +618,13 @@
{% elif nachweis.studiennachweis_bemerkung %} {% elif nachweis.studiennachweis_bemerkung %}
<i class="fas fa-comment text-info" title="Bemerkung vorhanden"></i> <i class="fas fa-comment text-info" title="Bemerkung vorhanden"></i>
{% else %} {% else %}
<i class="fas fa-check text-success"></i> <i class="fas fa-check text-success" title="Vorhanden"></i>
{% endif %} {% endif %}
{% else %} {% else %}
<i class="fas fa-times text-muted"></i> <i class="fas fa-times text-muted" title="Nicht vorhanden"></i>
{% endif %} {% endif %}
</td> </td>
<td class="text-center"> <td class="col-check">
{% if nachweis.einkommenssituation_bestaetigt %} {% if nachweis.einkommenssituation_bestaetigt %}
{% if nachweis.einkommenssituation_datei %} {% if nachweis.einkommenssituation_datei %}
<a href="{{ nachweis.einkommenssituation_datei.url }}" target="_blank" class="text-success" title="Datei ansehen"> <a href="{{ nachweis.einkommenssituation_datei.url }}" target="_blank" class="text-success" title="Datei ansehen">
@@ -570,13 +633,13 @@
{% elif nachweis.einkommenssituation_text %} {% elif nachweis.einkommenssituation_text %}
<i class="fas fa-comment text-info" title="{{ nachweis.einkommenssituation_text|truncatechars:50 }}"></i> <i class="fas fa-comment text-info" title="{{ nachweis.einkommenssituation_text|truncatechars:50 }}"></i>
{% else %} {% else %}
<i class="fas fa-check text-success"></i> <i class="fas fa-check text-success" title="Vorhanden"></i>
{% endif %} {% endif %}
{% else %} {% else %}
<i class="fas fa-times text-muted"></i> <i class="fas fa-times text-muted" title="Nicht vorhanden"></i>
{% endif %} {% endif %}
</td> </td>
<td class="text-center"> <td class="col-check">
{% if nachweis.vermogenssituation_bestaetigt %} {% if nachweis.vermogenssituation_bestaetigt %}
{% if nachweis.vermogenssituation_datei %} {% if nachweis.vermogenssituation_datei %}
<a href="{{ nachweis.vermogenssituation_datei.url }}" target="_blank" class="text-success" title="Datei ansehen"> <a href="{{ nachweis.vermogenssituation_datei.url }}" target="_blank" class="text-success" title="Datei ansehen">
@@ -585,38 +648,38 @@
{% elif nachweis.vermogenssituation_text %} {% elif nachweis.vermogenssituation_text %}
<i class="fas fa-comment text-info" title="{{ nachweis.vermogenssituation_text|truncatechars:50 }}"></i> <i class="fas fa-comment text-info" title="{{ nachweis.vermogenssituation_text|truncatechars:50 }}"></i>
{% else %} {% else %}
<i class="fas fa-check text-success"></i> <i class="fas fa-check text-success" title="Vorhanden"></i>
{% endif %} {% endif %}
{% else %} {% else %}
<i class="fas fa-times text-muted"></i> <i class="fas fa-times text-muted" title="Nicht vorhanden"></i>
{% endif %} {% endif %}
</td> </td>
<td> <td class="col-aktionen">
<div class="btn-group" role="group" aria-label="Aktionen"> <div class="btn-group btn-group-sm" role="group" aria-label="Aktionen">
<a href="{% url 'stiftung:quarterly_confirmation_edit' nachweis.id %}" <a href="{% url 'stiftung:quarterly_confirmation_edit' nachweis.id %}"
class="btn btn-sm btn-outline-primary" class="btn btn-outline-primary btn-sm"
title="Bearbeiten"> title="Bearbeiten">
<i class="fas fa-edit"></i> <i class="fas fa-edit"></i>
</a> </a>
{% if user.is_staff %} {% if user.is_staff %}
{% if nachweis.status == 'eingereicht' %} {% if nachweis.status == 'eingereicht' %}
<button type="button" <button type="button"
class="btn btn-sm btn-outline-success" class="btn btn-outline-success btn-sm"
onclick="approveQuarterly('{{ nachweis.id }}')" onclick="approveQuarterly('{{ nachweis.id }}')"
title="Freigeben"> title="Freigeben">
<i class="fas fa-check"></i> <i class="fas fa-check"></i>
</button> </button>
{% elif nachweis.status == 'geprueft' %} {% elif nachweis.status == 'geprueft' %}
<button type="button" <button type="button"
class="btn btn-sm btn-outline-success" class="btn btn-outline-success btn-sm"
onclick="approveQuarterly('{{ nachweis.id }}')" onclick="approveQuarterly('{{ nachweis.id }}')"
title="Erneut freigeben / Unterstützung synchronisieren"> title="Erneut freigeben">
<i class="fas fa-sync"></i> <i class="fas fa-sync"></i>
</button> </button>
<button type="button" <button type="button"
class="btn btn-sm btn-outline-warning" class="btn btn-outline-warning btn-sm"
onclick="resetQuarterly('{{ nachweis.id }}')" onclick="resetQuarterly('{{ nachweis.id }}')"
title="Status zurücksetzen"> title="Zurücksetzen">
<i class="fas fa-undo"></i> <i class="fas fa-undo"></i>
</button> </button>
{% endif %} {% endif %}
@@ -630,7 +693,40 @@
</div> </div>
<!-- Add Quarter Modal -->
{% else %}
<div class="text-center py-4">
<i class="fas fa-calendar-check fa-3x text-muted mb-3"></i>
<h5 class="text-muted">Keine vierteljährlichen Nachweise vorhanden</h5>
<p class="text-muted">Klicken Sie auf "Quartal hinzufügen", um einen neuen Nachweis zu erstellen.</p>
</div>
{% endif %}
</div>
</div>
<!-- Notes Section -->
<div class="card shadow mb-4">
<div class="card-header bg-secondary text-white">
<h5 class="card-title mb-0">
<i class="fas fa-sticky-note me-2"></i>Notizen
</h5>
</div>
<div class="card-body">
<p class="mb-0">
<span class="view-mode">
{% if destinataer.notizen %}
{{ destinataer.notizen|linebreaks }}
{% else %}
<em class="text-muted">Keine Notizen vorhanden</em>
{% endif %}
</span>
<textarea name="notizen" class="form-control edit-mode" style="display: none;" rows="4" placeholder="Notizen und Bemerkungen">{{ destinataer.notizen }}</textarea>
</p>
</div>
</div>
</form>
<!-- Add Quarter Modal (außerhalb des edit-form) -->
<div class="modal fade" id="addQuarterModal" tabindex="-1" aria-labelledby="addQuarterModalLabel" aria-hidden="true"> <div class="modal fade" id="addQuarterModal" tabindex="-1" aria-labelledby="addQuarterModalLabel" aria-hidden="true">
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">
@@ -640,7 +736,7 @@
</h5> </h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Schließen"></button> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Schließen"></button>
</div> </div>
<form method="post" action="{% url 'stiftung:quarterly_confirmation_create' destinataer.pk %}"> <form method="post" action="{% url 'stiftung:quarterly_confirmation_create' destinataer.pk %}" id="addQuarterForm">
{% csrf_token %} {% csrf_token %}
<div class="modal-body"> <div class="modal-body">
<div class="row"> <div class="row">
@@ -683,38 +779,6 @@
</div> </div>
</div> </div>
{% else %}
<div class="text-center py-4">
<i class="fas fa-calendar-check fa-3x text-muted mb-3"></i>
<h5 class="text-muted">Keine vierteljährlichen Nachweise vorhanden</h5>
<p class="text-muted">Klicken Sie auf "Quartal hinzufügen", um einen neuen Nachweis zu erstellen.</p>
</div>
{% endif %}
</div>
</div>
<!-- Notes Section -->
<div class="card shadow mb-4">
<div class="card-header bg-secondary text-white">
<h5 class="card-title mb-0">
<i class="fas fa-sticky-note me-2"></i>Notizen
</h5>
</div>
<div class="card-body">
<p class="mb-0">
<span class="view-mode">
{% if destinataer.notizen %}
{{ destinataer.notizen|linebreaks }}
{% else %}
<em class="text-muted">Keine Notizen vorhanden</em>
{% endif %}
</span>
<textarea name="notizen" class="form-control edit-mode" style="display: none;" rows="4" placeholder="Notizen und Bemerkungen">{{ destinataer.notizen }}</textarea>
</p>
</div>
</div>
</form>
<!-- Förderungen --> <!-- Förderungen -->
{% if foerderungen %} {% if foerderungen %}
<div class="card shadow mb-4"> <div class="card shadow mb-4">
@@ -1268,5 +1332,25 @@ function resetQuarterly(nachweisId) {
}); });
} }
} }
// Handle add quarter form submission using event delegation
document.addEventListener('DOMContentLoaded', function() {
// Use event delegation to catch form submission
document.addEventListener('submit', function(e) {
const form = e.target;
if (form && form.id === 'addQuarterForm') {
console.log('Add quarter form submitted');
console.log('Form action:', form.action);
// Form will submit normally, page will reload after redirect
// Modal will be closed automatically when page reloads
const submitBtn = form.querySelector('button[type="submit"]');
if (submitBtn) {
submitBtn.disabled = true;
submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin me-2"></i>Wird erstellt...';
}
}
});
});
</script> </script>
{% endblock %} {% endblock %}

View File

@@ -155,8 +155,15 @@
{% endif %}"> {% endif %}">
{{ nachweis.get_status_display }} {{ nachweis.get_status_display }}
</span> </span>
{% if nachweis.faelligkeitsdatum %} {% if nachweis.studiennachweis_faelligkeitsdatum or nachweis.zahlung_faelligkeitsdatum %}
<br><small class="opacity-75">Fällig: {{ nachweis.faelligkeitsdatum|date:"d.m.Y" }}</small> <br><small class="opacity-75">
{% if nachweis.studiennachweis_faelligkeitsdatum %}
Studiennachweis: {{ nachweis.studiennachweis_faelligkeitsdatum|date:"d.m.Y" }}
{% endif %}
{% if nachweis.zahlung_faelligkeitsdatum %}
| Zahlung: {{ nachweis.zahlung_faelligkeitsdatum|date:"d.m.Y" }}
{% endif %}
</small>
{% endif %} {% endif %}
</div> </div>
</div> </div>
@@ -175,8 +182,25 @@
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<p class="mb-2"><strong>Zeitraum:</strong> {{ nachweis.jahr }} {{ nachweis.get_quarter_display }}</p> <p class="mb-2"><strong>Zeitraum:</strong> {{ nachweis.jahr }} {{ nachweis.get_quarter_display }}</p>
{% if nachweis.faelligkeitsdatum %} {% if nachweis.studiennachweis_faelligkeitsdatum %}
<p class="mb-0"><strong>Fälligkeitsdatum:</strong> {{ nachweis.faelligkeitsdatum|date:"d.m.Y" }}</p> <p class="mb-1"><strong>Studiennachweis fällig:</strong>
<span class="{% if nachweis.is_study_proof_overdue %}text-danger{% endif %}">
{{ nachweis.studiennachweis_faelligkeitsdatum|date:"d.m.Y" }}
</span>
{% if nachweis.is_study_proof_overdue %}
<i class="fas fa-exclamation-triangle text-danger" title="Überfällig"></i>
{% endif %}
</p>
{% endif %}
{% if nachweis.zahlung_faelligkeitsdatum %}
<p class="mb-0"><strong>Zahlung fällig:</strong>
<span class="{% if nachweis.is_payment_overdue %}text-danger{% endif %}">
{{ nachweis.zahlung_faelligkeitsdatum|date:"d.m.Y" }}
</span>
{% if nachweis.is_payment_overdue %}
<i class="fas fa-exclamation-triangle text-danger" title="Überfällig"></i>
{% endif %}
</p>
{% endif %} {% endif %}
</div> </div>
</div> </div>
@@ -420,8 +444,25 @@
</span> </span>
</p> </p>
{% if nachweis.faelligkeitsdatum %} {% if nachweis.studiennachweis_faelligkeitsdatum %}
<p class="mb-2"><strong>Fälligkeit:</strong> {{ nachweis.faelligkeitsdatum|date:"d.m.Y" }}</p> <p class="mb-1"><strong>Studiennachweis fällig:</strong>
<span class="{% if nachweis.is_study_proof_overdue %}text-danger{% endif %}">
{{ nachweis.studiennachweis_faelligkeitsdatum|date:"d.m.Y" }}
</span>
{% if nachweis.is_study_proof_overdue %}
<i class="fas fa-exclamation-triangle text-danger" title="Überfällig"></i>
{% endif %}
</p>
{% endif %}
{% if nachweis.zahlung_faelligkeitsdatum %}
<p class="mb-2"><strong>Zahlung fällig:</strong>
<span class="{% if nachweis.is_payment_overdue %}text-danger{% endif %}">
{{ nachweis.zahlung_faelligkeitsdatum|date:"d.m.Y" }}
</span>
{% if nachweis.is_payment_overdue %}
<i class="fas fa-exclamation-triangle text-danger" title="Überfällig"></i>
{% endif %}
</p>
{% endif %} {% endif %}
{% if nachweis.eingereicht_am %} {% if nachweis.eingereicht_am %}

View File

@@ -51,7 +51,7 @@ services:
- GRAMPS_USERNAME=admin@localhost - GRAMPS_USERNAME=admin@localhost
- GRAMPS_PASSWORD=gramps_dev_password - GRAMPS_PASSWORD=gramps_dev_password
ports: ports:
- "8081:8000" - "18081:8000"
volumes: volumes:
- ./app:/app - ./app:/app
command: ["python", "manage.py", "runserver", "0.0.0.0:8000"] command: ["python", "manage.py", "runserver", "0.0.0.0:8000"]
@@ -86,7 +86,7 @@ services:
grampsweb: grampsweb:
image: ghcr.io/gramps-project/grampsweb:latest image: ghcr.io/gramps-project/grampsweb:latest
ports: ports:
- "8090:5000" - "18090:5000"
environment: environment:
- GRAMPSWEB_SECRET_KEY=dev-grampsweb-secret-key-not-for-production - GRAMPSWEB_SECRET_KEY=dev-grampsweb-secret-key-not-for-production
- GRAMPSWEB_ADMIN_EMAIL=admin@localhost - GRAMPSWEB_ADMIN_EMAIL=admin@localhost