- Создана папка 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 (новый импорт сигналов)
341 lines
10 KiB
Python
341 lines
10 KiB
Python
"""
|
||
Обработчики (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
|
||
)
|