Enhanced quarterly confirmation system with approval workflow and export improvements
Features added: - ✅ Fixed quarterly confirmation approval system with URL pattern - ✅ Added re-approval and status reset functionality for quarterly confirmations - ✅ Synchronized quarterly approval status with support payment system - ✅ Enhanced Destinataer export with missing fields (anrede, titel, mobil) - ✅ Added quarterly confirmation data and documents to export system - ✅ Fixed address field display issues in destinataer template - ✅ Added quarterly statistics dashboard to support payment lists - ✅ Implemented duplicate support payment prevention and cleanup - ✅ Added visual indicators for quarterly-linked support payments Technical improvements: - Enhanced create_quarterly_support_payment() with duplicate detection - Added get_related_support_payment() method to VierteljahresNachweis model - Improved quarterly confirmation workflow with proper status transitions - Added computed address property to Destinataer model - Fixed template field mismatches (anrede, titel, mobil vs strasse, plz, ort) - Enhanced backup system with operation tracking and cancellation Workflow enhancements: - Quarterly confirmations now properly sync with support payments - Single support payment per destinataer per quarter (no duplicates) - Approval button works for both eingereicht and geprueft status - Reset functionality allows workflow restart - Export includes complete quarterly data with uploaded documents
This commit is contained in:
@@ -1705,16 +1705,23 @@ def land_list(request):
|
||||
sum_wald_qm=Sum("wald_qm"),
|
||||
sum_sonstiges_qm=Sum("sonstiges_qm"),
|
||||
)
|
||||
sum_groesse_qm = float(aggregates.get("sum_groesse_qm") or 0)
|
||||
sum_gruenland_qm = float(aggregates.get("sum_gruenland_qm") or 0)
|
||||
sum_acker_qm = float(aggregates.get("sum_acker_qm") or 0)
|
||||
sum_wald_qm = float(aggregates.get("sum_wald_qm") or 0)
|
||||
sum_sonstiges_qm = float(aggregates.get("sum_sonstiges_qm") or 0)
|
||||
sum_total_use_qm = sum_gruenland_qm + sum_acker_qm + sum_wald_qm + sum_sonstiges_qm
|
||||
|
||||
# Calculate verpachtung statistics
|
||||
total_plots = lands.count()
|
||||
verpachtete_plots = lands.filter(verp_flaeche_aktuell__gt=0).count()
|
||||
unveerpachtete_plots = total_plots - verpachtete_plots
|
||||
|
||||
def pct(part, total):
|
||||
return round((part / total) * 100, 1) if total and part is not None else 0.0
|
||||
|
||||
stats = {
|
||||
"sum_groesse_qm": sum_groesse_qm,
|
||||
"sum_gruenland_qm": sum_gruenland_qm,
|
||||
"sum_acker_qm": sum_acker_qm,
|
||||
"sum_wald_qm": sum_wald_qm,
|
||||
@@ -1723,6 +1730,11 @@ def land_list(request):
|
||||
"pct_gruenland": pct(sum_gruenland_qm, sum_total_use_qm),
|
||||
"pct_acker": pct(sum_acker_qm, sum_total_use_qm),
|
||||
"pct_wald": pct(sum_wald_qm, sum_total_use_qm),
|
||||
"total_plots": total_plots,
|
||||
"verpachtete_plots": verpachtete_plots,
|
||||
"unveerpachtete_plots": unveerpachtete_plots,
|
||||
"pct_verpachtet": pct(verpachtete_plots, total_plots),
|
||||
"pct_unveerpachtet": pct(unveerpachtete_plots, total_plots),
|
||||
}
|
||||
|
||||
# Prepare size chart data (top 30 by size)
|
||||
@@ -1783,10 +1795,31 @@ def land_detail(request, pk):
|
||||
def land_create(request):
|
||||
if request.method == "POST":
|
||||
form = LandForm(request.POST)
|
||||
|
||||
# Debug: Print form data
|
||||
print("=== LAND CREATE DEBUG ===")
|
||||
print(f"POST data: {dict(request.POST)}")
|
||||
print(f"Form is valid: {form.is_valid()}")
|
||||
|
||||
if not form.is_valid():
|
||||
print(f"Form errors: {form.errors}")
|
||||
print(f"Form non-field errors: {form.non_field_errors()}")
|
||||
# Add error messages for debugging
|
||||
for field, errors in form.errors.items():
|
||||
for error in errors:
|
||||
messages.error(request, f"{field}: {error}")
|
||||
|
||||
if form.is_valid():
|
||||
land = form.save()
|
||||
messages.success(request, f'Länderei "{land}" wurde erfolgreich erstellt.')
|
||||
return redirect("stiftung:land_detail", pk=land.pk)
|
||||
try:
|
||||
land = form.save()
|
||||
messages.success(request, f'Länderei "{land}" wurde erfolgreich erstellt.')
|
||||
print(f"Successfully created land: {land}")
|
||||
return redirect("stiftung:land_detail", pk=land.pk)
|
||||
except Exception as e:
|
||||
print(f"Error saving land: {e}")
|
||||
messages.error(request, f"Fehler beim Speichern: {str(e)}")
|
||||
else:
|
||||
messages.error(request, "Bitte korrigieren Sie die Fehler im Formular.")
|
||||
else:
|
||||
form = LandForm()
|
||||
|
||||
@@ -4456,9 +4489,22 @@ def unterstuetzungen_list(request):
|
||||
# Enhanced PDF export with corporate identity
|
||||
elif export_format == "pdf":
|
||||
return export_unterstuetzungen_pdf(request, qs, selected_ids)
|
||||
|
||||
# Get quarterly confirmation statistics
|
||||
quarterly_stats = {}
|
||||
total_quarterly = VierteljahresNachweis.objects.count()
|
||||
for status_code, status_name in VierteljahresNachweis.STATUS_CHOICES:
|
||||
count = VierteljahresNachweis.objects.filter(status=status_code).count()
|
||||
quarterly_stats[status_code] = {
|
||||
'name': status_name,
|
||||
'count': count
|
||||
}
|
||||
|
||||
context = {
|
||||
"unterstuetzungen": qs,
|
||||
"status_filter": status,
|
||||
"quarterly_stats": quarterly_stats,
|
||||
"total_quarterly": total_quarterly,
|
||||
}
|
||||
return render(request, "stiftung/unterstuetzungen_list.html", context)
|
||||
|
||||
@@ -5193,6 +5239,8 @@ def destinataer_export(request, pk):
|
||||
# 1. Entity data as JSON
|
||||
entity_data = {
|
||||
"id": str(destinataer.id),
|
||||
"anrede": destinataer.get_anrede_display() if hasattr(destinataer, 'anrede') and destinataer.anrede else None,
|
||||
"titel": destinataer.titel if hasattr(destinataer, 'titel') else None,
|
||||
"vorname": destinataer.vorname,
|
||||
"nachname": destinataer.nachname,
|
||||
"geburtsdatum": (
|
||||
@@ -5202,6 +5250,7 @@ def destinataer_export(request, pk):
|
||||
),
|
||||
"email": destinataer.email,
|
||||
"telefon": destinataer.telefon,
|
||||
"mobil": destinataer.mobil if hasattr(destinataer, 'mobil') else None,
|
||||
"iban": destinataer.iban,
|
||||
"strasse": destinataer.strasse,
|
||||
"plz": destinataer.plz,
|
||||
@@ -5340,6 +5389,69 @@ def destinataer_export(request, pk):
|
||||
json.dumps(docs_data, indent=2, ensure_ascii=False),
|
||||
)
|
||||
|
||||
# 4. Quarterly Confirmations with documents
|
||||
quarterly_confirmations = destinataer.quartalseinreichungen.all().order_by("-jahr", "-quartal")
|
||||
quarterly_data = []
|
||||
|
||||
for confirmation in quarterly_confirmations:
|
||||
confirmation_data = {
|
||||
"id": str(confirmation.id),
|
||||
"jahr": confirmation.jahr,
|
||||
"quartal": confirmation.quartal,
|
||||
"quartal_display": confirmation.get_quartal_display(),
|
||||
"status": confirmation.status,
|
||||
"status_display": confirmation.get_status_display(),
|
||||
"studiennachweis_erforderlich": confirmation.studiennachweis_erforderlich,
|
||||
"studiennachweis_eingereicht": confirmation.studiennachweis_eingereicht,
|
||||
"studiennachweis_bemerkung": confirmation.studiennachweis_bemerkung,
|
||||
"einkommenssituation_bestaetigt": confirmation.einkommenssituation_bestaetigt,
|
||||
"einkommenssituation_text": confirmation.einkommenssituation_text,
|
||||
"vermogenssituation_bestaetigt": confirmation.vermogenssituation_bestaetigt,
|
||||
"vermogenssituation_text": confirmation.vermogenssituation_text,
|
||||
"weitere_dokumente_beschreibung": confirmation.weitere_dokumente_beschreibung,
|
||||
"interne_notizen": confirmation.interne_notizen,
|
||||
"erstellt_am": confirmation.erstellt_am.isoformat(),
|
||||
"aktualisiert_am": confirmation.aktualisiert_am.isoformat(),
|
||||
"eingereicht_am": confirmation.eingereicht_am.isoformat() if confirmation.eingereicht_am else None,
|
||||
"geprueft_am": confirmation.geprueft_am.isoformat() if confirmation.geprueft_am else None,
|
||||
"geprueft_von": confirmation.geprueft_von.username if confirmation.geprueft_von else None,
|
||||
"faelligkeitsdatum": confirmation.faelligkeitsdatum.isoformat() if confirmation.faelligkeitsdatum else None,
|
||||
"completion_percentage": confirmation.completion_percentage(),
|
||||
"uploaded_files": []
|
||||
}
|
||||
|
||||
# Add uploaded files from quarterly confirmation
|
||||
quarterly_files = [
|
||||
("studiennachweis", confirmation.studiennachweis_datei),
|
||||
("einkommenssituation", confirmation.einkommenssituation_datei),
|
||||
("vermogenssituation", confirmation.vermogenssituation_datei),
|
||||
("weitere_dokumente", confirmation.weitere_dokumente),
|
||||
]
|
||||
|
||||
for file_type, file_field in quarterly_files:
|
||||
if file_field and os.path.exists(file_field.path):
|
||||
file_info = {
|
||||
"type": file_type,
|
||||
"name": os.path.basename(file_field.name),
|
||||
"path": file_field.name
|
||||
}
|
||||
confirmation_data["uploaded_files"].append(file_info)
|
||||
|
||||
# Add file to ZIP
|
||||
safe_filename = f"quarterly_{confirmation.jahr}_Q{confirmation.quartal}_{file_type}_{os.path.basename(file_field.name)}"
|
||||
zipf.write(
|
||||
file_field.path,
|
||||
f"vierteljahresnachweis/{safe_filename}"
|
||||
)
|
||||
|
||||
quarterly_data.append(confirmation_data)
|
||||
|
||||
if quarterly_data:
|
||||
zipf.writestr(
|
||||
"vierteljahresnachweis.json",
|
||||
json.dumps(quarterly_data, indent=2, ensure_ascii=False),
|
||||
)
|
||||
|
||||
# Prepare response
|
||||
with open(temp_file.name, "rb") as f:
|
||||
response = HttpResponse(f.read(), content_type="application/zip")
|
||||
@@ -5888,59 +6000,119 @@ def backup_restore(request):
|
||||
messages.error(request, "Bitte wählen Sie eine Backup-Datei aus.")
|
||||
return redirect("stiftung:backup_management")
|
||||
|
||||
# Validate file
|
||||
# Validate file format
|
||||
if not backup_file.name.endswith(".tar.gz"):
|
||||
messages.error(
|
||||
request, "Ungültiges Backup-Format. Nur .tar.gz Dateien sind erlaubt."
|
||||
)
|
||||
return redirect("stiftung:backup_management")
|
||||
|
||||
# Save uploaded file
|
||||
# Save uploaded file to temporary location
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
temp_dir = tempfile.mkdtemp()
|
||||
backup_path = os.path.join(temp_dir, backup_file.name)
|
||||
|
||||
with open(backup_path, "wb+") as destination:
|
||||
for chunk in backup_file.chunks():
|
||||
destination.write(chunk)
|
||||
try:
|
||||
with open(backup_path, "wb+") as destination:
|
||||
for chunk in backup_file.chunks():
|
||||
destination.write(chunk)
|
||||
|
||||
# Create restore job
|
||||
restore_job = BackupJob.objects.create(
|
||||
backup_type="full",
|
||||
created_by=request.user,
|
||||
backup_filename=backup_file.name,
|
||||
)
|
||||
# Validate the backup file
|
||||
from stiftung.backup_utils import validate_backup_file
|
||||
|
||||
is_valid, message = validate_backup_file(backup_path)
|
||||
if not is_valid:
|
||||
messages.error(request, f"Ungültiges Backup: {message}")
|
||||
return redirect("stiftung:backup_management")
|
||||
|
||||
# Show validation success
|
||||
messages.info(request, f"Backup validiert: {message}")
|
||||
|
||||
# Log restore initiation
|
||||
# Create restore job
|
||||
restore_job = BackupJob.objects.create(
|
||||
operation="restore",
|
||||
backup_type="full",
|
||||
created_by=request.user,
|
||||
backup_filename=backup_file.name,
|
||||
)
|
||||
|
||||
# Log restore initiation
|
||||
from stiftung.audit import log_system_action
|
||||
|
||||
log_system_action(
|
||||
request=request,
|
||||
action="restore",
|
||||
description=f"Wiederherstellung gestartet von: {backup_file.name}",
|
||||
details={
|
||||
"restore_job_id": str(restore_job.id),
|
||||
"filename": backup_file.name,
|
||||
},
|
||||
)
|
||||
|
||||
# Start restore process
|
||||
import threading
|
||||
|
||||
from stiftung.backup_utils import run_restore
|
||||
|
||||
restore_thread = threading.Thread(
|
||||
target=run_restore, args=(str(restore_job.id), backup_path)
|
||||
)
|
||||
restore_thread.start()
|
||||
|
||||
messages.success(
|
||||
request, f'Wiederherstellung von "{backup_file.name}" wurde gestartet. '
|
||||
f'Überwachen Sie den Fortschritt in der Backup-Historie.'
|
||||
)
|
||||
return redirect("stiftung:backup_management")
|
||||
|
||||
except Exception as e:
|
||||
messages.error(request, f"Fehler beim Verarbeiten der Backup-Datei: {e}")
|
||||
return redirect("stiftung:backup_management")
|
||||
|
||||
return redirect("stiftung:backup_management")
|
||||
|
||||
|
||||
@login_required
|
||||
def backup_cancel(request, backup_id):
|
||||
"""Cancel a running backup job"""
|
||||
try:
|
||||
backup_job = BackupJob.objects.get(id=backup_id)
|
||||
|
||||
# Only allow cancelling running or pending jobs
|
||||
if backup_job.status not in ['running', 'pending']:
|
||||
messages.error(request, "Nur laufende oder wartende Backups können abgebrochen werden.")
|
||||
return redirect("stiftung:backup_management")
|
||||
|
||||
# Check if user has permission to cancel (either own job or admin)
|
||||
if backup_job.created_by != request.user and not request.user.is_staff:
|
||||
messages.error(request, "Sie können nur Ihre eigenen Backup-Jobs abbrechen.")
|
||||
return redirect("stiftung:backup_management")
|
||||
|
||||
# Mark as cancelled
|
||||
from django.utils import timezone
|
||||
backup_job.status = "cancelled"
|
||||
backup_job.completed_at = timezone.now()
|
||||
backup_job.error_message = f"Abgebrochen von {request.user.username}"
|
||||
backup_job.save()
|
||||
|
||||
# Log the cancellation
|
||||
from stiftung.audit import log_system_action
|
||||
|
||||
log_system_action(
|
||||
request=request,
|
||||
action="restore",
|
||||
description=f"Wiederherstellung gestartet von: {backup_file.name}",
|
||||
details={
|
||||
"restore_job_id": str(restore_job.id),
|
||||
"filename": backup_file.name,
|
||||
},
|
||||
action="backup_cancel",
|
||||
description=f"Backup-Job abgebrochen: {backup_job.get_backup_type_display()}",
|
||||
details={"backup_job_id": str(backup_job.id)},
|
||||
)
|
||||
|
||||
# Start restore process
|
||||
import threading
|
||||
|
||||
from stiftung.backup_utils import run_restore
|
||||
|
||||
restore_thread = threading.Thread(
|
||||
target=run_restore, args=(str(restore_job.id), backup_path)
|
||||
)
|
||||
restore_thread.start()
|
||||
|
||||
messages.success(
|
||||
request, f'Wiederherstellung von "{backup_file.name}" wurde gestartet.'
|
||||
)
|
||||
return redirect("stiftung:backup_management")
|
||||
|
||||
|
||||
messages.success(request, f"Backup-Job wurde abgebrochen.")
|
||||
|
||||
except BackupJob.DoesNotExist:
|
||||
messages.error(request, "Backup-Job nicht gefunden.")
|
||||
except Exception as e:
|
||||
messages.error(request, f"Fehler beim Abbrechen des Backup-Jobs: {e}")
|
||||
|
||||
return redirect("stiftung:backup_management")
|
||||
|
||||
|
||||
@@ -6857,7 +7029,16 @@ def unterstuetzungen_all(request):
|
||||
|
||||
# Statistics
|
||||
total_betrag = unterstuetzungen.aggregate(total=Sum("betrag"))["total"] or 0
|
||||
avg_betrag = unterstuetzungen.aggregate(avg=Avg("betrag"))["avg"] or 0
|
||||
|
||||
# Get quarterly confirmation statistics
|
||||
quarterly_stats = {}
|
||||
total_quarterly = VierteljahresNachweis.objects.count()
|
||||
for status_code, status_name in VierteljahresNachweis.STATUS_CHOICES:
|
||||
count = VierteljahresNachweis.objects.filter(status=status_code).count()
|
||||
quarterly_stats[status_code] = {
|
||||
'name': status_name,
|
||||
'count': count
|
||||
}
|
||||
|
||||
# Available destinataer for filter
|
||||
destinataer = Destinataer.objects.all().order_by("nachname", "vorname")
|
||||
@@ -6868,7 +7049,8 @@ def unterstuetzungen_all(request):
|
||||
"title": "Alle Unterstützungen",
|
||||
"status_filter": status,
|
||||
"total_betrag": total_betrag,
|
||||
"avg_betrag": avg_betrag,
|
||||
"quarterly_stats": quarterly_stats,
|
||||
"total_quarterly": total_quarterly,
|
||||
"status_choices": DestinataerUnterstuetzung.STATUS_CHOICES,
|
||||
"destinataer": destinataer,
|
||||
}
|
||||
@@ -7325,7 +7507,8 @@ def quarterly_confirmation_update(request, pk):
|
||||
|
||||
def create_quarterly_support_payment(nachweis):
|
||||
"""
|
||||
Create an automatic support payment when all quarterly requirements are met
|
||||
Get or create a single support payment for this quarterly confirmation
|
||||
Ensures only one payment exists per destinataer per quarter
|
||||
"""
|
||||
destinataer = nachweis.destinataer
|
||||
|
||||
@@ -7340,18 +7523,31 @@ def create_quarterly_support_payment(nachweis):
|
||||
if not destinataer.iban:
|
||||
return None
|
||||
|
||||
# Check if a payment for this quarter already exists
|
||||
# Calculate quarter date range for more robust search
|
||||
quarter_start = datetime(nachweis.jahr, (nachweis.quartal - 1) * 3 + 1, 1).date()
|
||||
quarter_end = datetime(nachweis.jahr, nachweis.quartal * 3, 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 - use broader criteria to catch all possibilities
|
||||
existing_payment = DestinataerUnterstuetzung.objects.filter(
|
||||
destinataer=destinataer,
|
||||
faellig_am__gte=quarter_start,
|
||||
faellig_am__lt=quarter_end,
|
||||
beschreibung__contains=f"Q{nachweis.quartal}/{nachweis.jahr}"
|
||||
faellig_am__lt=quarter_end
|
||||
).filter(
|
||||
Q(beschreibung__contains=f"Q{nachweis.quartal}/{nachweis.jahr}") |
|
||||
Q(beschreibung__contains=f"Vierteljährliche Unterstützung")
|
||||
).first()
|
||||
|
||||
if existing_payment:
|
||||
# Update existing payment to ensure it matches current requirements
|
||||
existing_payment.betrag = destinataer.vierteljaehrlicher_betrag
|
||||
existing_payment.empfaenger_iban = destinataer.iban
|
||||
existing_payment.empfaenger_name = destinataer.get_full_name()
|
||||
existing_payment.verwendungszweck = f"Vierteljährliche Unterstützung Q{nachweis.quartal}/{nachweis.jahr} - {destinataer.get_full_name()}"
|
||||
existing_payment.beschreibung = f"Vierteljährliche Unterstützung Q{nachweis.quartal}/{nachweis.jahr} (automatisch erstellt)"
|
||||
existing_payment.save()
|
||||
return existing_payment
|
||||
|
||||
# Get default payment account
|
||||
@@ -7363,8 +7559,6 @@ def create_quarterly_support_payment(nachweis):
|
||||
return None
|
||||
|
||||
# Calculate payment due date (last day of quarter)
|
||||
# Quarter end months and their last days:
|
||||
# Q1: March (31), Q2: June (30), Q3: September (30), Q4: December (31)
|
||||
quarter_end_month = nachweis.quartal * 3
|
||||
|
||||
if nachweis.quartal == 1: # Q1: January-March (ends March 31)
|
||||
@@ -7544,21 +7738,103 @@ def quarterly_confirmation_approve(request, pk):
|
||||
nachweis = get_object_or_404(VierteljahresNachweis, pk=pk)
|
||||
|
||||
if request.method == "POST":
|
||||
if nachweis.status == 'eingereicht':
|
||||
nachweis.status = 'geprueft'
|
||||
nachweis.geprueft_am = timezone.now()
|
||||
nachweis.geprueft_von = request.user
|
||||
nachweis.save()
|
||||
if nachweis.status in ['eingereicht', 'geprueft']:
|
||||
# Check if we need to create or update support payment
|
||||
related_payment = nachweis.get_related_support_payment()
|
||||
|
||||
messages.success(
|
||||
request,
|
||||
f"Vierteljahresnachweis für {nachweis.destinataer.get_full_name()} "
|
||||
f"({nachweis.jahr} Q{nachweis.quartal}) wurde freigegeben."
|
||||
)
|
||||
if nachweis.status == 'eingereicht' or (nachweis.status == 'geprueft' and not related_payment):
|
||||
# Approve the quarterly confirmation
|
||||
nachweis.status = 'geprueft'
|
||||
nachweis.geprueft_am = timezone.now()
|
||||
nachweis.geprueft_von = request.user
|
||||
nachweis.save()
|
||||
|
||||
# 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()
|
||||
|
||||
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(
|
||||
request,
|
||||
f"Vierteljahresnachweis für {nachweis.destinataer.get_full_name()} "
|
||||
f"({nachweis.jahr} Q{nachweis.quartal}) wurde freigegeben."
|
||||
)
|
||||
else:
|
||||
messages.error(
|
||||
request,
|
||||
"Nur eingereichte Nachweise können freigegeben werden."
|
||||
"Nur eingereichte oder bereits genehmigte Nachweise können verarbeitet werden."
|
||||
)
|
||||
|
||||
return redirect("stiftung:destinataer_detail", pk=nachweis.destinataer.pk)
|
||||
|
||||
|
||||
@login_required
|
||||
def quarterly_confirmation_reset(request, pk):
|
||||
"""Reset quarterly confirmation status (staff only)"""
|
||||
if not request.user.is_staff:
|
||||
messages.error(request, "Sie haben keine Berechtigung für diese Aktion.")
|
||||
return redirect("stiftung:destinataer_list")
|
||||
|
||||
nachweis = get_object_or_404(VierteljahresNachweis, pk=pk)
|
||||
|
||||
if request.method == "POST":
|
||||
if nachweis.status in ['geprueft', 'eingereicht']:
|
||||
# Reset the quarterly confirmation status
|
||||
nachweis.status = 'eingereicht' if nachweis.is_complete() else 'teilweise'
|
||||
nachweis.geprueft_am = None
|
||||
nachweis.geprueft_von = None
|
||||
nachweis.aktualisiert_am = timezone.now()
|
||||
nachweis.save()
|
||||
|
||||
# Reset related support payment status if it exists
|
||||
related_payment = nachweis.get_related_support_payment()
|
||||
if related_payment and related_payment.status == 'in_bearbeitung':
|
||||
related_payment.status = 'geplant'
|
||||
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 zurückgesetzt."
|
||||
)
|
||||
else:
|
||||
messages.success(
|
||||
request,
|
||||
f"Vierteljahresnachweis für {nachweis.destinataer.get_full_name()} "
|
||||
f"({nachweis.jahr} Q{nachweis.quartal}) wurde zurückgesetzt."
|
||||
)
|
||||
else:
|
||||
messages.error(
|
||||
request,
|
||||
"Nur genehmigte oder eingereichte Nachweise können zurückgesetzt werden."
|
||||
)
|
||||
|
||||
return redirect("stiftung:destinataer_detail", pk=nachweis.destinataer.pk)
|
||||
|
||||
Reference in New Issue
Block a user