Corp/backend/procurement/signals.py.old
lizerk 46e7059a40 refactor(procurement): удаление дублирующего WebSocket модуля
Проблема:
- procurement/notifications/websocket.py дублировал функциональность
- Прямые вызовы channel_layer.group_send() обходили универсальную систему
- Нарушение единообразия использования notifications

Решение:
- Удален websocket.py модуль
- WebSocket уведомления теперь отправляются через универсальную систему:
  notify.send() → channels.py → Celery → WebSocketNotificationSender

Преимущества:
- ✅ Единообразие архитектуры (как в requests_app)
- ✅ Все каналы (WebSocket, Email, Push) через одну систему
- ✅ Автоматический retry и rate limiting
- ✅ Централизованное логирование
- ✅ Меньше кода для поддержки

Изменения:
- Удален: notifications/websocket.py
- Обновлен: notifications/signals.py (удалены broadcast вызовы)
- Обновлен: notifications/__init__.py (удалены импорты websocket)
2026-03-10 12:37:49 +03:00

305 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
Сигналы для модуля закупок.
Автоматическая отправка уведомлений при изменении статусов.
WebSocket события для real-time обновлений.
"""
import logging
from asgiref.sync import async_to_sync
from channels.layers import get_channel_layer
from django.db.models.signals import post_save, pre_save
from django.dispatch import receiver
from notifications.signals import notify
from .constants import ApprovalStatus, ProcurementStatus
from .models import Approval, ProcurementRequest
logger = logging.getLogger(__name__)
def broadcast_request_update(request, event_type='request_updated'):
"""Отправить WebSocket событие об обновлении заявки."""
try:
channel_layer = get_channel_layer()
if not channel_layer:
return
# Данные заявки для broadcast
data = {
'id': request.id,
'title': request.title,
'status': request.status,
'status_display': request.get_status_display(),
'department_id': request.department_id,
'requestor_id': request.requestor_id,
}
# Отправляем всем пользователям отдела
group_name = f'procurement_dept_{request.department_id}'
async_to_sync(channel_layer.group_send)(
group_name,
{
'type': 'procurement_update',
'event': event_type,
'data': data,
}
)
# Отправляем создателю заявки
async_to_sync(channel_layer.group_send)(
f'user_{request.requestor_id}',
{
'type': 'procurement_update',
'event': event_type,
'data': data,
}
)
logger.debug(
f"[Procurement WS] Broadcast {event_type} for request {request.id}"
)
except Exception as e:
logger.warning(f"[Procurement WS] Broadcast failed: {e}")
@receiver(post_save, sender=ProcurementRequest)
def notify_on_request_status_change(sender, instance, created, **kwargs):
"""Отправить уведомление при изменении статуса заявки."""
if created:
# Новая заявка создана - broadcast
broadcast_request_update(instance, 'request_created')
if instance.department.head:
notify.send(
sender=None,
recipient=instance.department.head,
verb='procurement_new_request',
action_object=instance,
description=(
f'Создана заявка "{instance.title}" '
f'на сумму {instance.total_cost}₽'
),
action_url='/procurement',
data={'title': 'Новая заявка на закупку'},
)
return
# Проверяем изменение статуса
if hasattr(instance, '_original_status'):
old_status = instance._original_status
new_status = instance.status
if old_status == new_status:
return
# Broadcast обновления статуса
broadcast_request_update(instance, 'request_status_changed')
# Статус изменился
if new_status == ProcurementStatus.PENDING:
# Заявка отправлена на согласование
notify_approvers(instance)
elif new_status == ProcurementStatus.APPROVED:
# Заявка одобрена
notify_requestor(
instance,
'procurement_approved',
'Заявка одобрена',
f'Ваша заявка "{instance.title}" была одобрена. '
f'Можно приступать к закупке.'
)
elif new_status == ProcurementStatus.REJECTED:
# Заявка отклонена
notify_requestor(
instance,
'procurement_rejected',
'Заявка отклонена',
f'Ваша заявка "{instance.title}" была отклонена. '
f'Проверьте комментарии согласующих.'
)
elif new_status == ProcurementStatus.COMPLETED:
# Уведомляем создателя заявки
notify_requestor(
instance,
'procurement_completed',
'Заявка завершена',
f'Закупка по заявке "{instance.title}" завершена.'
)
# Уведомляем всех одобривших согласующих
for approval in instance.approvals.filter(status=ApprovalStatus.APPROVED):
notify.send(
sender=None,
recipient=approval.approver,
verb='procurement_completed',
action_object=instance,
description=f'Заявка "{instance.title}" успешно завершена.',
action_url='/procurement',
data={'title': 'Заявка завершена'},
)
elif new_status == ProcurementStatus.IN_PROGRESS:
# Заявка взята в работу
executor = instance.executor
executor_name = executor.get_full_name() if executor else 'Сотрудник'
# Уведомляем создателя заявки (если он не сам взял в работу)
if executor and instance.requestor != executor:
notify.send(
sender=executor,
recipient=instance.requestor,
verb='procurement_in_progress',
action_object=instance,
description=(
f'Заявка "{instance.title}" взята в работу '
f'пользователем {executor_name}.'
),
action_url='/procurement',
data={'title': 'Ваша заявка взята в работу'},
)
# Уведомляем всех одобривших согласующих
for approval in instance.approvals.filter(status=ApprovalStatus.APPROVED):
notify.send(
sender=executor,
recipient=approval.approver,
verb='procurement_in_progress',
action_object=instance,
description=(
f'Заявка "{instance.title}" взята в работу '
f'пользователем {executor_name}.'
),
action_url='/procurement',
data={'title': 'Заявка взята в работу'},
)
elif new_status == ProcurementStatus.CANCELLED:
# Заявка отменена - уведомляем всех причастных согласующих
reason = getattr(instance, 'cancellation_reason', 'не указана')
for approval in instance.approvals.all():
notify.send(
sender=None,
recipient=approval.approver,
verb='procurement_cancelled',
action_object=instance,
description=(
f'Заявка "{instance.title}" была отменена автором. '
f'Причина: {reason}'
),
action_url='/procurement',
data={'title': 'Заявка отменена'},
)
@receiver(post_save, sender=Approval)
def notify_on_approval_change(sender, instance, created, **kwargs):
"""Отправить уведомление при изменении согласования."""
if created:
# Новое согласование создано
notify_approver(instance)
return
# Проверяем изменение статуса согласования
if hasattr(instance, '_original_approval_status'):
old_status = instance._original_approval_status
new_status = instance.status
if old_status == new_status:
return
# Статус согласования изменился
if new_status == ApprovalStatus.APPROVED:
notify_requestor(
instance.request,
'procurement_stage_approved',
'Этап согласования пройден',
f'{instance.approver.get_full_name()} одобрил '
f'заявку "{instance.request.title}".'
)
elif new_status == ApprovalStatus.REJECTED:
notify_requestor(
instance.request,
'procurement_rejected',
'Заявка отклонена',
f'{instance.approver.get_full_name()} отклонил '
f'заявку "{instance.request.title}". '
f'Причина: {instance.comment}'
)
def notify_approvers(request):
"""Уведомить всех согласующих о новой заявке."""
for approval in request.approvals.filter(
status=ApprovalStatus.PENDING
):
notify_approver(approval)
def notify_approver(approval):
"""Уведомить согласующего о необходимости согласования."""
notify.send(
sender=None,
recipient=approval.approver,
verb='procurement_pending_approval',
action_object=approval.request,
description=(
f'Заявка "{approval.request.title}" '
f'ожидает вашего согласования. '
f'Сумма: {approval.request.total_cost}₽'
),
action_url='/procurement',
data={'title': 'Требуется согласование'},
)
def notify_requestor(request, verb, title, message):
"""Уведомить создателя заявки."""
notify.send(
sender=None,
recipient=request.requestor,
verb=verb,
action_object=request,
description=message,
action_url='/procurement',
data={'title': title},
)
# Сохраняем исходный статус перед сохранением
@receiver(pre_save, sender=ProcurementRequest)
def save_original_status(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 save_original_approval_status(sender, instance, **kwargs):
"""Сохранить оригинальный статус согласования для отслеживания изменений."""
if instance.pk:
try:
original = Approval.objects.get(pk=instance.pk)
instance._original_approval_status = original.status
except Approval.DoesNotExist:
pass
@receiver(pre_save, sender=Approval)
def save_original_approval_status(sender, instance, **kwargs):
"""Сохранить оригинальный статус согласования."""
if instance.pk:
try:
original = Approval.objects.get(pk=instance.pk)
instance._original_approval_status = original.status
except Approval.DoesNotExist:
pass