HH/apps/projects/signals.py
2026-07-05 14:49:45 +03:00

62 lines
2.0 KiB
Python

"""
Signals for QI Projects.
- Sends a notification when a task is assigned.
- Keeps `team_members` in sync with the champion + manager of each linked
department whenever the `departments` M2M changes.
"""
import logging
from django.db.models.signals import m2m_changed, post_save
from django.dispatch import receiver
from apps.projects.models import QIProject
logger = logging.getLogger(__name__)
@receiver(post_save, sender="projects.QIProjectTask")
def notify_task_assignment(sender, instance, created, **kwargs):
"""Send an in-app notification when a QI task is assigned to a staff member."""
if not instance.assigned_to:
return
# Only notify on new assignments (creation with an assignee)
if not created:
return
user = getattr(instance.assigned_to, "user", None)
if not user:
return
try:
from apps.notifications.services import create_in_app_notification
create_in_app_notification(
user=user,
title=f"New QI Task: {instance.title}",
message=f"You have been assigned a task in project '{instance.project.name}'.",
notification_type="qi_task_assigned",
action_url=f"/projects/{instance.project.id}/",
)
except Exception as e:
logger.warning(f"Failed to send QI task notification: {e}")
@receiver(m2m_changed, sender=QIProject.departments.through)
def sync_team_on_departments_change(sender, instance, action, **kwargs):
"""Rebuild the derived `team_members` set whenever the project's departments change.
Fires on `post_add` / `post_remove` / `post_clear` so that programmatic,
admin, and form-driven edits all stay consistent. The sync is idempotent.
"""
if action not in ("post_add", "post_remove", "post_clear"):
return
if not instance.pk:
return
try:
instance.sync_team_members_from_departments()
except Exception as e: # defensive: never break an M2M write on sync failure
logger.warning(f"Failed to sync QI team members for project {instance.pk}: {e}")