128 lines
4.8 KiB
Python
128 lines
4.8 KiB
Python
"""
|
|
Complaint signals.
|
|
|
|
Heavy notification work (SMS/email to complainants and champions) has been moved
|
|
off these signals into Celery tasks (see ``apps.complaints.tasks``) so it never
|
|
blocks the request. The signals below now just dispatch those tasks.
|
|
|
|
The pre_save department-sync and the ComplaintUpdate logging remain inline
|
|
(they are cheap, in-process work).
|
|
"""
|
|
import logging
|
|
|
|
from django.db.models.signals import post_save, pre_save
|
|
from django.dispatch import receiver
|
|
|
|
from .models import Complaint, ComplaintInvolvedDepartment, ComplaintUpdate
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
@receiver(pre_save, sender=Complaint)
|
|
def sync_department_from_staff(sender, instance, **kwargs):
|
|
"""
|
|
Automatically set complaint.department from staff.department when staff is assigned.
|
|
|
|
Ensures the department stays in sync with the assigned staff member regardless
|
|
of how the complaint is saved (API, admin, forms, etc.).
|
|
"""
|
|
if instance.staff:
|
|
staff_department = instance.staff.department
|
|
if staff_department and instance.department_id != staff_department.id:
|
|
instance.department = staff_department
|
|
logger.info(
|
|
f"Complaint #{instance.id}: Auto-synced department to '{staff_department.name}' "
|
|
f"from staff '{instance.staff.name}'"
|
|
)
|
|
elif instance.pk:
|
|
# Staff being removed (set to None); department kept as-is.
|
|
pass
|
|
|
|
|
|
@receiver(post_save, sender=Complaint)
|
|
def send_complaint_creation_sms(sender, instance, created, **kwargs):
|
|
"""Dispatch the complaint-received SMS task when a complaint is created."""
|
|
if not created:
|
|
return
|
|
if not instance.contact_phone:
|
|
logger.info(f"Complaint #{instance.id} created but no phone number provided. Skipping SMS.")
|
|
return
|
|
try:
|
|
from .tasks import send_complaint_creation_sms_task
|
|
|
|
send_complaint_creation_sms_task.delay(str(instance.id))
|
|
except Exception as e:
|
|
# Log error but don't fail the complaint save
|
|
logger.error(f"Failed to dispatch creation SMS for complaint #{instance.id}: {e}")
|
|
|
|
|
|
@receiver(post_save, sender=Complaint)
|
|
def send_complaint_status_change_sms(sender, instance, created, **kwargs):
|
|
"""Dispatch SMS/email when a complaint status changes to resolved or closed."""
|
|
# Skip on creation (handled by the creation signal)
|
|
if created:
|
|
return
|
|
|
|
# Detect an actual status change using the in-memory previous-status set in Complaint.save()
|
|
if not hasattr(instance, "_status_was"):
|
|
return
|
|
|
|
old_status = instance._status_was
|
|
new_status = instance.status
|
|
|
|
if new_status not in ("resolved", "closed"):
|
|
return
|
|
if old_status == new_status:
|
|
return
|
|
if not instance.contact_phone and not instance.contact_email:
|
|
logger.info(
|
|
f"Complaint #{instance.id} status changed to {new_status} but no contact info. Skipping notification."
|
|
)
|
|
return
|
|
|
|
try:
|
|
from .tasks import send_complaint_status_change_task
|
|
|
|
send_complaint_status_change_task.delay(str(instance.id), old_status, new_status)
|
|
except Exception as e:
|
|
# Log error but don't fail the complaint save
|
|
logger.error(f"Failed to dispatch status change notification for complaint #{instance.id}: {e}")
|
|
|
|
|
|
@receiver(post_save, sender=ComplaintUpdate)
|
|
def track_manual_sms(sender, instance, created, **kwargs):
|
|
"""Track manually sent SMS notifications recorded as ComplaintUpdate rows."""
|
|
if not created:
|
|
return
|
|
if instance.update_type == "communication":
|
|
logger.info(
|
|
f"Manual communication update created for complaint #{instance.complaint.id}: "
|
|
f"{instance.message[:50]}..."
|
|
)
|
|
|
|
|
|
@receiver(post_save, sender=ComplaintInvolvedDepartment)
|
|
def notify_champion_on_department_assignment(sender, instance, created, **kwargs):
|
|
"""Dispatch the champion-notification email when a complaint is sent to a department."""
|
|
if not created:
|
|
return
|
|
# Only notify when this department is actually being sent to (not just added)
|
|
if not instance.sent:
|
|
return
|
|
# Only notify if the department champion has a user with an email
|
|
champion = instance.department.champion
|
|
champion_user = champion.user if champion else None
|
|
if not champion_user or not champion_user.email:
|
|
logger.info(
|
|
f"ComplaintInvolvedDepartment #{instance.id}: No respondent email configured for department "
|
|
f"'{instance.department.name}'. Skipping notification."
|
|
)
|
|
return
|
|
|
|
try:
|
|
from .tasks import notify_champion_on_dept_assignment_task
|
|
|
|
notify_champion_on_dept_assignment_task.delay(str(instance.id))
|
|
except Exception as e:
|
|
logger.error(f"Failed to dispatch champion notification for ComplaintInvolvedDepartment #{instance.id}: {e}")
|