"""Celery tasks for the appreciation module.""" import logging from celery import shared_task logger = logging.getLogger(__name__) @shared_task def send_appreciation_notifications( appreciation_id, staff_id=None, note="", email_subject="", email_body="", host="", ): """Send the appreciation email to the appreciated staff member with the department champion and manager CC'd. Behaviour: - If ``staff_id`` is provided AND the staff member has an email: ONE email is sent To: the staff member with champion + manager as CC: (deduplicated). - If no staff member is selected (or the staff has no email): separate emails are sent To: each of the champion and manager (current behaviour). - In-app notifications are created for every recipient (To and CC) whose email maps to a User account. - SMS notifications are NOT sent (email-only flow). """ from apps.appreciation.models import Appreciation from apps.notifications.services import NotificationService, get_email_header_html from apps.organizations.department_contacts import get_champion_and_manager, _staff_contact from apps.organizations.models import Staff try: appreciation = Appreciation.objects.select_related("department", "hospital").get(pk=appreciation_id) except Appreciation.DoesNotExist: logger.error(f"Appreciation {appreciation_id} not found for notification task") return department = appreciation.department if not department: logger.warning(f"Appreciation {appreciation_id} has no department, skipping notifications") return targets = get_champion_and_manager(department) link = f"https://{host}/appreciation/detail/{appreciation.pk}/" notified = [] # Generate the appreciation letter PDF once and reuse for all email notifications. # Wrapped in try/except so email delivery still succeeds if PDF rendering fails. pdf_attachment = None try: from apps.appreciation.ui_views import _render_appreciation_pdf_bytes, _appreciation_pdf_context ctx = _appreciation_pdf_context(appreciation) pdf_bytes = _render_appreciation_pdf_bytes( "appreciation/appreciation_letter.html", ctx, obj=appreciation, ) pdf_attachment = ( f"appreciation_letter_{appreciation.reference_number}.pdf", pdf_bytes, "application/pdf", ) except Exception: logger.exception( f"Failed to generate appreciation letter PDF for {appreciation_id}; " f"sending notifications without attachment" ) # Build the champion + manager email list (in order: champion first, then manager) target_emails_raw = [t["email"] for t in targets if t.get("email")] # Resolve the appreciated staff member (if any) and their email staff_email = None appreciated_staff = None if staff_id: try: appreciated_staff = Staff.objects.get(id=staff_id) s_email, _ = _staff_contact(appreciated_staff) staff_email = s_email or None except Staff.DoesNotExist: appreciated_staff = None # Build the deduplicated CC list (exclude the staff member's own email) seen_lower = {staff_email.lower()} if staff_email else set() cc_emails = [] for email in target_emails_raw: key = email.lower() if key not in seen_lower: seen_lower.add(key) cc_emails.append(email) attachments_arg = [pdf_attachment] if pdf_attachment else None if staff_email: # CASE 1 (primary): ONE email to the staff member, CC champion + manager. send_subject = email_subject or f"An Appreciation Has Been Submitted About You - {appreciation.reference_number}" send_body = email_body or ( f"An appreciation (#{appreciation.reference_number}) has been submitted about you." ) try: NotificationService.send_email( email=staff_email, subject=send_subject, message=send_body + f"\n\n{link}", html_message=f"""
{get_email_header_html()}

You've Been Appreciated!

An appreciation #{appreciation.reference_number} has been submitted about you.

Message: {appreciation.message_en or 'N/A'}

{f'

Note: {note}

' if note else ''}

View Appreciation

""", related_object=appreciation, attachments=attachments_arg, cc=cc_emails, ) notified.append(staff_email) for cc_addr in cc_emails: notified.append(f"CC: {cc_addr}") except Exception: logger.exception(f"Failed to send staff email to {staff_email} with CC {cc_emails}") elif cc_emails: # CASE 2 (fallback): no specific staff — separate emails to champion + manager. send_subject = email_subject or f"Appreciation Sent to Department - {appreciation.reference_number}" send_body = email_body or ( f"Appreciation #{appreciation.reference_number} has been sent to your department ({department.name})." ) for email in cc_emails: try: NotificationService.send_email( email=email, subject=send_subject, message=send_body + f"\n\n{link}", html_message=f"""
{get_email_header_html()}

Appreciation Sent to Department

Appreciation #{appreciation.reference_number} has been sent to your department ({department.name}).

Message: {appreciation.message_en or 'N/A'}

{f'

Note: {note}

' if note else ''}

View Appreciation

""", related_object=appreciation, attachments=attachments_arg, ) notified.append(email) except Exception: logger.exception(f"Failed to send department email to {email}") else: logger.warning( f"Appreciation {appreciation_id}: no recipients to notify " f"(no staff email and no champion/manager emails)" ) logger.info(f"Appreciation {appreciation_id} notifications sent to: {', '.join(notified)}") @shared_task def send_appreciation_reminder_email(appreciation_id, reminder_type, recipients): """Send a reminder email to department champion and manager for an appreciation.""" import logging logger = logging.getLogger(__name__) try: from apps.appreciation.models import Appreciation from apps.notifications.services import NotificationService, get_email_header_html appreciation = Appreciation.objects.get(pk=appreciation_id) for recipient in recipients: NotificationService.send_email( email=recipient["email"], subject=f"Reminder: Appreciation {appreciation.reference_number} - Acknowledgment Required", message=f"This is a reminder that appreciation {appreciation.reference_number} is awaiting acknowledgment. Please acknowledge it as soon as possible.", html_message=f"""
{get_email_header_html()}

Reminder: Acknowledgment Required

This is a reminder that appreciation {appreciation.reference_number} is awaiting acknowledgment from your department.

Please acknowledge it as soon as possible.

""", related_object=appreciation, ) logger.info(f"Appreciation {appreciation_id} {reminder_type} reminder sent to {len(recipients)} recipients") except Exception as e: logger.error(f"Failed to send appreciation reminder email for {appreciation_id}: {e}")