""" Django сигналы для автоматической генерации уведомлений в модуле Procurement. Обрабатывает события: - Создание новой заявки на закупку - Изменение статуса заявки - Изменение статуса согласования Все уведомления отправляются через универсальную систему notifications: notify.send() → channels.py → Celery → WebSocket/Email/Push """ import logging from django.db.models.signals import post_save, pre_save from django.dispatch import receiver from ..constants import ApprovalStatus, ProcurementStatus from ..models import Approval, ProcurementRequest from .handlers import ( notify_approvers, notify_processing_department_request, notify_request_approved, notify_request_cancelled, notify_request_completed, notify_request_in_progress, notify_request_rejected, notify_request_returned_to_processing, notify_stage_approved, notify_stage_rejected, ) logger = logging.getLogger(__name__) @receiver(post_save, sender=ProcurementRequest) def create_request_notifications(sender, instance, created, **kwargs): """ Создает уведомления при создании или изменении заявки. Обрабатывает: 1. Новая адресная заявка - уведомление отделу-исполнителю 2. Изменение статуса - уведомления соответствующим получателям """ try: request = instance if created: if ( request.status == ProcurementStatus.WAITING and request.processing_department_id ): notify_processing_department_request( request, actor=getattr(request, "_notification_actor", None), ) return # Проверяем изменение статуса if hasattr(request, "_original_status"): old_status = request._original_status new_status = request.status if old_status == new_status: return _handle_status_change(request, new_status) except Exception as error: logger.exception( "Procurement request notification signal failed", extra={ "event": "procurement.notification_signal.failed", "procurement_request_id": getattr(instance, "id", None), "signal": "request_saved", "error_type": type(error).__name__, }, ) def _handle_status_change(request, new_status): """ Обработать изменение статуса заявки. Args: request: Объект ProcurementRequest new_status: Новый статус """ if new_status == ProcurementStatus.PENDING: # Заявка отправлена на согласование notify_approvers(request) elif ( getattr(request, "_original_status", None) == ProcurementStatus.PENDING and request.processing_department_id and new_status in { ProcurementStatus.WAITING, ProcurementStatus.IN_PROGRESS, } ): # Адресная заявка после согласования возвращается в тот же отдел. notify_request_approved(request) notify_request_returned_to_processing(request) elif new_status == ProcurementStatus.APPROVED: # Заявка одобрена notify_request_approved(request) elif new_status == ProcurementStatus.REJECTED: # Заявка отклонена if not getattr(request, "_requestor_rejected_notified", False): notify_request_rejected(request) elif new_status == ProcurementStatus.COMPLETED: # Заявка завершена notify_request_completed(request) elif new_status == ProcurementStatus.IN_PROGRESS: # Заявка взята в работу executor = request.executor notify_request_in_progress(request, executor) elif new_status == ProcurementStatus.CANCELLED: # Заявка отменена notify_request_cancelled(request) @receiver(post_save, sender=Approval) def create_approval_notifications(sender, instance, created, **kwargs): """ Создает уведомления при создании или изменении согласования. Обрабатывает: 1. Одобрение - уведомление создателю заявки и следующему этапу 2. Отклонение - уведомление создателю заявки """ try: approval = instance if created: # Нотификацию при submit запускает сигнал заявки, когда уже # созданы все Approval и можно определить только текущий этап. return # Проверяем изменение статуса согласования if hasattr(approval, "_original_approval_status"): old_status = approval._original_approval_status new_status = approval.status if old_status == new_status: return # Обработка изменения статуса согласования if new_status == ApprovalStatus.APPROVED: notify_stage_approved(approval) elif new_status == ApprovalStatus.REJECTED: notify_stage_rejected(approval) except Exception as error: logger.exception( "Procurement approval notification signal failed", extra={ "event": "procurement.notification_signal.failed", "approval_id": getattr(instance, "id", None), "signal": "approval_saved", "error_type": type(error).__name__, }, ) @receiver(pre_save, sender=ProcurementRequest) def track_request_status_change(sender, instance, **kwargs): """ Сохранить оригинальный статус для отслеживания изменений. """ if instance.pk: try: original = ProcurementRequest.objects.get(pk=instance.pk) instance._original_status = original.status except ProcurementRequest.DoesNotExist: pass @receiver(pre_save, sender=Approval) def track_approval_status_change(sender, instance, **kwargs): """ Сохранить оригинальный статус согласования для отслеживания изменений. """ if instance.pk: try: original = Approval.objects.get(pk=instance.pk) instance._original_approval_status = original.status except Approval.DoesNotExist: pass