""" 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}")