203 lines
9.1 KiB
Python
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}")
|