lizerk 16c5f2023a refactor(procurement): реструктуризация notifications в модульную архитектуру
- Создана папка backend/procurement/notifications/ вместо одного signals.py
- Разделена логика на специализированные модули:
  * config.py - типы уведомлений (verbs) и шаблоны сообщений
  * handlers.py - бизнес-логика отправки уведомлений
  * signals.py - только Django сигналы
  * websocket.py - WebSocket broadcast для real-time обновлений
  * __init__.py - публичный API модуля

Преимущества новой структуры:
- ✅ Четкое разделение ответственности (SRP)
- ✅ WebSocket логика отделена от уведомлений
- ✅ Все шаблоны сообщений в одном месте (MessageTemplates)
- ✅ Константы verbs централизованы (NotificationVerbs)
- ✅ Легче тестировать и расширять
- ✅ Обратная совместимость через __init__.py

Изменения:
- Удален: signals.py (сохранен как .old для истории)
- Добавлен: notifications/ модуль с 5 файлами
- Обновлен: apps.py (новый импорт сигналов)
2026-03-10 12:34:05 +03:00

341 lines
10 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.

"""
Обработчики (handlers) для отправки уведомлений о заявках на закупку.
Содержит бизнес-логику формирования и отправки уведомлений
для различных событий в модуле Procurement.
"""
import logging
from notifications.signals import notify
from .config import NotificationVerbs, MessageTemplates, ActionURLs
logger = logging.getLogger(__name__)
def notify_new_request(request):
"""
Уведомить о новой заявке на закупку.
Отправляет уведомление руководителю отдела.
Args:
request: Объект ProcurementRequest
"""
if not request.department.head:
logger.warning(
f"[Procurement] Нет руководителя для отдела {request.department}"
)
return
notification_title, description = MessageTemplates.new_request(
request.title, request.total_cost
)
notify.send(
sender=None,
recipient=request.department.head,
verb=NotificationVerbs.NEW_REQUEST,
action_object=request,
description=description,
action_url=ActionURLs.PROCUREMENT_LIST,
data={
'title': notification_title,
'request_id': request.id,
'total_cost': float(request.total_cost),
},
)
logger.info(
f"[Procurement] Отправлено уведомление о новой заявке #{request.id} "
f"руководителю {request.department.head.username}"
)
def notify_approvers(request):
"""
Уведомить всех согласующих о необходимости согласования заявки.
Args:
request: Объект ProcurementRequest
"""
from ..constants import ApprovalStatus
pending_approvals = request.approvals.filter(
status=ApprovalStatus.PENDING
)
if not pending_approvals.exists():
logger.warning(
f"[Procurement] Нет ожидающих согласований "
f"для заявки #{request.id}"
)
return
for approval in pending_approvals:
notify_approver(approval)
def notify_approver(approval):
"""
Уведомить конкретного согласующего о необходимости согласования.
Args:
approval: Объект Approval
"""
notification_title, description = MessageTemplates.pending_approval(
approval.request.title, approval.request.total_cost
)
notify.send(
sender=None,
recipient=approval.approver,
verb=NotificationVerbs.PENDING_APPROVAL,
action_object=approval.request,
description=description,
action_url=ActionURLs.PROCUREMENT_LIST,
data={
'title': notification_title,
'request_id': approval.request.id,
'approval_id': approval.id,
'total_cost': float(approval.request.total_cost),
},
)
logger.info(
f"[Procurement] Отправлено уведомление о согласовании "
f"заявки #{approval.request.id} для {approval.approver.username}"
)
def notify_requestor(request, verb, title, message):
"""
Уведомить создателя заявки.
Args:
request: Объект ProcurementRequest
verb: Тип уведомления (из NotificationVerbs)
title: Заголовок уведомления
message: Текст сообщения
"""
notify.send(
sender=None,
recipient=request.requestor,
verb=verb,
action_object=request,
description=message,
action_url=ActionURLs.PROCUREMENT_LIST,
data={
'title': title,
'request_id': request.id,
},
)
logger.info(
f"[Procurement] Отправлено уведомление '{verb}' "
f"создателю заявки #{request.id}"
)
def notify_request_approved(request):
"""
Уведомить о полном одобрении заявки.
Args:
request: Объект ProcurementRequest
"""
notification_title, description = MessageTemplates.approved(request.title)
notify_requestor(
request,
NotificationVerbs.APPROVED,
notification_title,
description
)
def notify_request_rejected(request):
"""
Уведомить об отклонении заявки.
Args:
request: Объект ProcurementRequest
"""
notification_title, description = MessageTemplates.rejected(request.title)
notify_requestor(
request,
NotificationVerbs.REJECTED,
notification_title,
description
)
def notify_request_completed(request):
"""
Уведомить о завершении заявки.
Отправляет уведомления:
- Создателю заявки
- Всем одобрившим согласующим
Args:
request: Объект ProcurementRequest
"""
from ..constants import ApprovalStatus
# Уведомляем создателя
notification_title, description = MessageTemplates.completed(request.title)
notify_requestor(
request,
NotificationVerbs.COMPLETED,
notification_title,
description
)
# Уведомляем всех одобривших согласующих
notification_title, description = MessageTemplates.completed_approver(
request.title
)
for approval in request.approvals.filter(status=ApprovalStatus.APPROVED):
notify.send(
sender=None,
recipient=approval.approver,
verb=NotificationVerbs.COMPLETED,
action_object=request,
description=description,
action_url=ActionURLs.PROCUREMENT_LIST,
data={
'title': notification_title,
'request_id': request.id,
},
)
def notify_request_in_progress(request, executor):
"""
Уведомить о взятии заявки в работу.
Отправляет уведомления:
- Создателю заявки (если он не сам взял в работу)
- Всем одобрившим согласующим
Args:
request: Объект ProcurementRequest
executor: Пользователь, взявший заявку в работу
"""
from ..constants import ApprovalStatus
executor_name = executor.get_full_name() if executor else 'Сотрудник'
# Уведомляем создателя (если он не сам взял в работу)
if executor and request.requestor != executor:
notification_title, description = (
MessageTemplates.in_progress_requestor(
request.title, executor_name
)
)
notify.send(
sender=executor,
recipient=request.requestor,
verb=NotificationVerbs.IN_PROGRESS,
action_object=request,
description=description,
action_url=ActionURLs.PROCUREMENT_LIST,
data={
'title': notification_title,
'request_id': request.id,
'executor_id': executor.id,
},
)
# Уведомляем всех одобривших согласующих
notification_title, description = MessageTemplates.in_progress(
request.title, executor_name
)
for approval in request.approvals.filter(status=ApprovalStatus.APPROVED):
notify.send(
sender=executor,
recipient=approval.approver,
verb=NotificationVerbs.IN_PROGRESS,
action_object=request,
description=description,
action_url=ActionURLs.PROCUREMENT_LIST,
data={
'title': notification_title,
'request_id': request.id,
'executor_id': executor.id if executor else None,
},
)
def notify_request_cancelled(request):
"""
Уведомить об отмене заявки.
Отправляет уведомления всем причастным согласующим.
Args:
request: Объект ProcurementRequest
"""
reason = getattr(request, 'cancellation_reason', 'не указана')
notification_title, description = MessageTemplates.cancelled(
request.title, reason
)
for approval in request.approvals.all():
notify.send(
sender=None,
recipient=approval.approver,
verb=NotificationVerbs.CANCELLED,
action_object=request,
description=description,
action_url=ActionURLs.PROCUREMENT_LIST,
data={
'title': notification_title,
'request_id': request.id,
'reason': reason,
},
)
def notify_stage_approved(approval):
"""
Уведомить о прохождении этапа согласования.
Args:
approval: Объект Approval
"""
approver_name = approval.approver.get_full_name()
notification_title, description = MessageTemplates.stage_approved(
approver_name, approval.request.title
)
notify_requestor(
approval.request,
NotificationVerbs.STAGE_APPROVED,
notification_title,
description
)
def notify_stage_rejected(approval):
"""
Уведомить об отклонении на этапе согласования.
Args:
approval: Объект Approval
"""
approver_name = approval.approver.get_full_name()
notification_title, description = MessageTemplates.rejected_by_approver(
approver_name, approval.request.title, approval.comment
)
notify_requestor(
approval.request,
NotificationVerbs.REJECTED,
notification_title,
description
)