diff --git a/app/stiftung/tasks.py b/app/stiftung/tasks.py index 30b812f..a7ca3bd 100644 --- a/app/stiftung/tasks.py +++ b/app/stiftung/tasks.py @@ -702,19 +702,16 @@ def send_onboarding_einladung(self, einladung_id, base_url=None): raise self.retry(exc=exc) -@shared_task(bind=True, max_retries=3, default_retry_delay=300) -def send_bestaetigung(self, destinataer_id, base_url=None): +def _send_bestaetigung_sync(destinataer_id): """ Generiert ein Bestätigungsschreiben (PDF) für einen Destinatär und sendet es per E-Mail. Das PDF wird zusätzlich im DMS unter Kontext "korrespondenz" abgelegt. - Args: - destinataer_id: UUID des Destinatärs - base_url: Basis-URL der Anwendung (für Konsistenz mit anderen Tasks) + Kann direkt (synchron) oder via Celery-Task aufgerufen werden. + Bei Fehlern wird eine Exception geworfen (kein stilles Verschlucken). """ from decimal import Decimal from django.core.files.base import ContentFile - from django.template.loader import render_to_string from django.core.mail import EmailMultiAlternatives from django.utils import timezone @@ -772,7 +769,7 @@ def send_bestaetigung(self, destinataer_id, base_url=None): pdf_bytes = HTML(string=html_content).write_pdf() except Exception as exc: logger.error("send_bestaetigung: PDF-Generierung fehlgeschlagen: %s", exc) - raise self.retry(exc=exc) + raise # PDF im DMS ablegen filename = ( @@ -803,17 +800,23 @@ def send_bestaetigung(self, destinataer_id, base_url=None): from_email = _get_smtp_from_email() to_email = destinataer.email + connection = _get_smtp_connection() + msg = EmailMultiAlternatives(subject, "", from_email, [to_email], connection=connection) + msg.attach_alternative(html_body, "text/html") + if pdf_bytes: + msg.attach(filename, pdf_bytes, "application/pdf") + msg.send() + logger.info("Bestätigung gesendet an %s (Destinatär %s)", to_email, destinataer_id) + return {"status": "sent", "destinataer_id": str(destinataer_id), "email": to_email} + + +@shared_task(bind=True, max_retries=3, default_retry_delay=300) +def send_bestaetigung(self, destinataer_id, base_url=None): + """Celery-Wrapper für _send_bestaetigung_sync (für asynchronen Aufruf).""" try: - connection = _get_smtp_connection() - msg = EmailMultiAlternatives(subject, "", from_email, [to_email], connection=connection) - msg.attach_alternative(html_body, "text/html") - if pdf_bytes: - msg.attach(filename, pdf_bytes, "application/pdf") - msg.send() - logger.info("Bestätigung gesendet an %s (Destinatär %s)", to_email, destinataer_id) - return {"status": "sent", "destinataer_id": str(destinataer_id), "email": to_email} + return _send_bestaetigung_sync(destinataer_id) except Exception as exc: - logger.exception("send_bestaetigung: E-Mail-Versand fehlgeschlagen für %s: %s", to_email, exc) + logger.exception("send_bestaetigung task fehlgeschlagen: %s", exc) raise self.retry(exc=exc) diff --git a/app/stiftung/views/destinataere.py b/app/stiftung/views/destinataere.py index 4542955..96fbf58 100644 --- a/app/stiftung/views/destinataere.py +++ b/app/stiftung/views/destinataere.py @@ -827,9 +827,9 @@ def bestaetigung_vorschau(request, pk): def bestaetigung_versenden(request, pk): """ Sendet das Bestätigungsschreiben per E-Mail an den Destinatär. - POST-only (CSRF-geschützt). Startet asynchronen Celery-Task. + POST-only (CSRF-geschützt). Sendet synchron für direktes Feedback. """ - from stiftung.tasks import send_bestaetigung + from stiftung.tasks import _send_bestaetigung_sync if request.method != "POST": return redirect("stiftung:destinataer_detail", pk=pk) @@ -843,8 +843,24 @@ def bestaetigung_versenden(request, pk): ) return redirect("stiftung:destinataer_detail", pk=pk) - base_url = request.build_absolute_uri("/").rstrip("/") - send_bestaetigung.delay(str(destinataer.id), base_url=base_url) + try: + result = _send_bestaetigung_sync(str(destinataer.id)) + except Exception as exc: + import logging + logging.getLogger(__name__).exception("Bestätigung versenden fehlgeschlagen: %s", exc) + messages.error( + request, + f"Bestätigungsschreiben konnte nicht gesendet werden: {exc}", + ) + return redirect("stiftung:destinataer_detail", pk=pk) + + if result and result.get("status") == "skipped": + messages.warning(request, "Versand übersprungen: Keine E-Mail-Adresse hinterlegt.") + return redirect("stiftung:destinataer_detail", pk=pk) + + if result and result.get("status") == "error": + messages.error(request, f"Fehler: {result.get('message', 'Unbekannter Fehler')}") + return redirect("stiftung:destinataer_detail", pk=pk) log_action( request, @@ -857,7 +873,7 @@ def bestaetigung_versenden(request, pk): messages.success( request, - f"Bestätigungsschreiben wird per E-Mail an {destinataer.email} gesendet.", + f"Bestätigungsschreiben wurde erfolgreich an {destinataer.email} gesendet.", ) return redirect("stiftung:destinataer_detail", pk=pk)