HH/apps/appreciation/tasks.py
2026-07-19 12:27:55 +03:00

203 lines
9.1 KiB
Python

"""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"""
<div style="font-family: 'Segoe UI', Tahoma, sans-serif; max-width: 600px; margin: 0 auto; border: 1px solid #e5e7eb; border-radius: 8px; overflow: hidden;">
{get_email_header_html()}
<div style="padding: 20px;">
<h2 style="color: #005696; font-size: 18px; margin: 0 0 12px 0;">You've Been Appreciated!</h2>
<p>An appreciation <strong>#{appreciation.reference_number}</strong> has been submitted about you.</p>
<p><strong>Message:</strong> {appreciation.message_en or 'N/A'}</p>
{f'<p><strong>Note:</strong> {note}</p>' if note else ''}
<p><a href="{link}">View Appreciation</a></p>
</div>
</div>
""",
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"""
<div style="font-family: 'Segoe UI', Tahoma, sans-serif; max-width: 600px; margin: 0 auto; border: 1px solid #e5e7eb; border-radius: 8px; overflow: hidden;">
{get_email_header_html()}
<div style="padding: 20px;">
<h2 style="color: #005696; font-size: 18px; margin: 0 0 12px 0;">Appreciation Sent to Department</h2>
<p>Appreciation <strong>#{appreciation.reference_number}</strong> has been sent to your department <strong>({department.name})</strong>.</p>
<p><strong>Message:</strong> {appreciation.message_en or 'N/A'}</p>
{f'<p><strong>Note:</strong> {note}</p>' if note else ''}
<p><a href="{link}">View Appreciation</a></p>
</div>
</div>
""",
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"""
<div style="font-family: 'Segoe UI', Tahoma, sans-serif; max-width: 600px; margin: 0 auto; border: 1px solid #e5e7eb; border-radius: 8px; overflow: hidden;">
{get_email_header_html()}
<div style="padding: 20px;">
<h2 style="color: #005696; font-size: 18px; margin: 0 0 12px 0;">Reminder: Acknowledgment Required</h2>
<p style="margin: 0 0 12px 0;">This is a reminder that appreciation <strong>{appreciation.reference_number}</strong> is awaiting acknowledgment from your department.</p>
<p style="margin: 0; color: #6b7280;">Please acknowledge it as soon as possible.</p>
</div>
</div>
""",
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}")