HH/apps/complaints/signals.py
2026-07-11 19:24:28 +03:00

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/email task when a complaint is created."""
if not created:
return
if not instance.contact_phone and not instance.contact_email:
logger.info(f"Complaint #{instance.id} created but no phone/email provided. Skipping notification.")
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}")