- ApiClient теперь обрабатывает 401 и 403 ошибки для обновления токена - Добавлен JWTRefreshMiddleware для проактивного обновления токенов за 5 минут до истечения - Добавлена настройка JWT_REFRESH_THRESHOLD_MINUTES (по умолчанию 5) - Исправлена проблема с периодическими 403 ошибками после истечения access token
228 lines
8.8 KiB
Python
228 lines
8.8 KiB
Python
"""
|
||
Signals для автоматической генерации уведомлений в модуле Requests.
|
||
|
||
Обрабатывает события:
|
||
- Новая заявка (для ответственных/руководителей)
|
||
- Одобрение заявки
|
||
- Отклонение заявки
|
||
- Комментарий к заявке
|
||
- Изменение статуса заявки
|
||
"""
|
||
|
||
from django.db.models.signals import post_save, pre_save
|
||
from django.dispatch import receiver
|
||
from django.contrib.auth import get_user_model
|
||
|
||
from .models import Request, RequestComment
|
||
from notifications.services import NotificationService
|
||
|
||
Employee = get_user_model()
|
||
|
||
|
||
@receiver(post_save, sender=Request)
|
||
def create_request_notifications(sender, instance, created, **kwargs):
|
||
"""
|
||
Создает уведомления при создании или изменении заявки.
|
||
|
||
Обрабатывает:
|
||
1. Новая заявка - уведомление руководителю/ответственным
|
||
2. Изменение статуса - уведомление автору
|
||
"""
|
||
request_obj = instance
|
||
|
||
if created and request_obj.status != 'draft':
|
||
# Новая заявка - уведомляем ответственных
|
||
notify_new_request(request_obj)
|
||
elif not created:
|
||
# Проверяем изменение статуса
|
||
# Получаем предыдущее состояние из БД
|
||
try:
|
||
old_request = Request.objects.get(pk=request_obj.pk)
|
||
if hasattr(request_obj, '_old_status'):
|
||
old_status = request_obj._old_status
|
||
new_status = request_obj.status
|
||
|
||
if old_status != new_status:
|
||
notify_status_change(request_obj, old_status, new_status)
|
||
except Request.DoesNotExist:
|
||
pass
|
||
|
||
|
||
@receiver(pre_save, sender=Request)
|
||
def track_status_change(sender, instance, **kwargs):
|
||
"""
|
||
Сохраняем старый статус перед обновлением для отслеживания изменений.
|
||
"""
|
||
if instance.pk:
|
||
try:
|
||
old_instance = Request.objects.get(pk=instance.pk)
|
||
instance._old_status = old_instance.status
|
||
except Request.DoesNotExist:
|
||
instance._old_status = None
|
||
|
||
|
||
@receiver(post_save, sender=RequestComment)
|
||
def create_comment_notification(sender, instance, created, **kwargs):
|
||
"""
|
||
Создает уведомление при добавлении комментария к заявке.
|
||
"""
|
||
if not created:
|
||
return
|
||
|
||
comment = instance
|
||
request_obj = comment.request
|
||
author = comment.author
|
||
|
||
# Уведомляем автора заявки (если комментарий не от него)
|
||
if request_obj.employee.id != author.id:
|
||
NotificationService.create_notification(
|
||
recipient=request_obj.employee,
|
||
notification_type_code='request_comment',
|
||
title='Новый комментарий к вашей заявке',
|
||
message=(
|
||
f'{author.get_full_name() or author.username} '
|
||
f'прокомментировал заявку: {comment.text[:100]}'
|
||
),
|
||
content_object=request_obj,
|
||
action_url=f'/requests/{request_obj.id}/',
|
||
metadata={
|
||
'request_id': request_obj.id,
|
||
'request_type': request_obj.type,
|
||
'comment_id': comment.id,
|
||
'author_id': author.id,
|
||
}
|
||
)
|
||
|
||
# Также уведомляем согласующего (если есть и это не он)
|
||
if (request_obj.approver and
|
||
request_obj.approver.id != author.id and
|
||
request_obj.approver.id != request_obj.employee.id):
|
||
NotificationService.create_notification(
|
||
recipient=request_obj.approver,
|
||
notification_type_code='request_comment',
|
||
title='Комментарий к заявке',
|
||
message=(
|
||
f'{author.get_full_name() or author.username} '
|
||
f'прокомментировал заявку: {comment.text[:100]}'
|
||
),
|
||
content_object=request_obj,
|
||
action_url=f'/requests/{request_obj.id}/',
|
||
metadata={
|
||
'request_id': request_obj.id,
|
||
'request_type': request_obj.type,
|
||
'comment_id': comment.id,
|
||
'author_id': author.id,
|
||
}
|
||
)
|
||
|
||
|
||
# ===== Вспомогательные функции =====
|
||
|
||
def notify_new_request(request_obj):
|
||
"""
|
||
Отправляет уведомление о новой заявке ответственным лицам.
|
||
"""
|
||
# Определяем получателей в зависимости от типа заявки
|
||
recipients = []
|
||
|
||
# 1. Руководитель отдела сотрудника
|
||
if request_obj.department and request_obj.department.head:
|
||
head = request_obj.department.head
|
||
if head.id != request_obj.employee.id:
|
||
recipients.append(head)
|
||
|
||
# 2. Если есть назначенный согласующий
|
||
if request_obj.approver:
|
||
if request_obj.approver not in recipients:
|
||
recipients.append(request_obj.approver)
|
||
|
||
# 3. Пользователи с правом обрабатывать заявки
|
||
users_with_permission = Employee.objects.filter(
|
||
user_permissions__codename='can_process_requests'
|
||
).exclude(id=request_obj.employee.id)
|
||
|
||
for user in users_with_permission:
|
||
if user not in recipients:
|
||
recipients.append(user)
|
||
|
||
# Отправляем уведомления
|
||
for recipient in recipients:
|
||
NotificationService.create_notification(
|
||
recipient=recipient,
|
||
notification_type_code='request_new',
|
||
title=f'Новая заявка от {request_obj.employee.get_full_name()}',
|
||
message=(
|
||
f'Тип: {request_obj.get_type_display()}. '
|
||
f'{request_obj.comment[:100] if request_obj.comment else ""}'
|
||
),
|
||
content_object=request_obj,
|
||
action_url=f'/requests/{request_obj.id}/',
|
||
metadata={
|
||
'request_id': request_obj.id,
|
||
'request_type': request_obj.type,
|
||
'employee_id': request_obj.employee.id,
|
||
'department_id': (
|
||
request_obj.department.id
|
||
if request_obj.department else None
|
||
),
|
||
}
|
||
)
|
||
|
||
|
||
def notify_status_change(request_obj, old_status, new_status):
|
||
"""
|
||
Отправляет уведомление при изменении статуса заявки.
|
||
"""
|
||
# Уведомляем автора заявки
|
||
recipient = request_obj.employee
|
||
|
||
# Определяем тип уведомления и сообщение
|
||
if new_status == 'approved':
|
||
notification_type = 'request_approved'
|
||
title = 'Ваша заявка одобрена'
|
||
approver_name = (
|
||
request_obj.approver.get_full_name()
|
||
if request_obj.approver else 'Руководитель'
|
||
)
|
||
message = (
|
||
f'Заявка "{request_obj.get_type_display()}" '
|
||
f'одобрена пользователем {approver_name}'
|
||
)
|
||
elif new_status == 'rejected':
|
||
notification_type = 'request_rejected'
|
||
title = 'Ваша заявка отклонена'
|
||
approver_name = (
|
||
request_obj.approver.get_full_name()
|
||
if request_obj.approver else 'Руководитель'
|
||
)
|
||
message = (
|
||
f'Заявка "{request_obj.get_type_display()}" '
|
||
f'отклонена пользователем {approver_name}'
|
||
)
|
||
else:
|
||
# Общее уведомление об изменении статуса
|
||
notification_type = 'request_status_changed'
|
||
title = 'Статус заявки изменен'
|
||
message = (
|
||
f'Статус заявки "{request_obj.get_type_display()}" '
|
||
f'изменен: {old_status} → {new_status}'
|
||
)
|
||
|
||
NotificationService.create_notification(
|
||
recipient=recipient,
|
||
notification_type_code=notification_type,
|
||
title=title,
|
||
message=message,
|
||
content_object=request_obj,
|
||
action_url=f'/requests/{request_obj.id}/',
|
||
metadata={
|
||
'request_id': request_obj.id,
|
||
'request_type': request_obj.type,
|
||
'old_status': old_status,
|
||
'new_status': new_status,
|
||
'approver_id': (
|
||
request_obj.approver.id if request_obj.approver else None
|
||
),
|
||
}
|
||
)
|