diff --git a/backend/documents/notification_signals.py b/backend/documents/notification_signals.py index b763a5ea..bf96ed9b 100644 --- a/backend/documents/notification_signals.py +++ b/backend/documents/notification_signals.py @@ -23,13 +23,16 @@ Employee = get_user_model() @receiver(post_save, sender=Document) def create_document_notification(sender, instance, created, **kwargs): """ - Создает уведомления при создании нового документа. + Создает уведомления при создании нового документа асинхронно. Уведомления отправляются: - Всем активным сотрудникам (если sent_to_all=True) - Сотрудникам выбранных отделов (через m2m_changed signal) - Выбранным получателям (через m2m_changed signal) """ + from django.db import transaction + from notifications.tasks import process_document_notifications_task + logger.info( f"[notification_signals] post_save Document id={instance.pk} " f"created={created} sent_to_all={instance.sent_to_all}" @@ -40,12 +43,25 @@ def create_document_notification(sender, instance, created, **kwargs): document = instance - # Если документ отправляется всем - создаем уведомления сразу + # Если документ отправляется всем - создаем уведомления сразу асинхронно if document.sent_to_all: logger.info( - f"[notification_signals] Calling notify_all_employees for doc={document.pk}" + f"[notification_signals] Queuing task for doc={document.pk} (sent_to_all=True)" ) - notify_all_employees(document) + + def send_task(): + """Отложенная отправка задачи после commit транзакции""" + try: + process_document_notifications_task.delay(document_id=document.pk) + except Exception as e: + logger.warning( + f"Failed to queue document notifications task: {e}, " + f"falling back to sync" + ) + # Fallback на старую логику + notify_all_employees(document) + + transaction.on_commit(send_task) else: logger.info( f"[notification_signals] Skipping notifications (sent_to_all=False), " diff --git a/backend/notifications/tasks.py b/backend/notifications/tasks.py index b1cbaf5a..ede79c3c 100644 --- a/backend/notifications/tasks.py +++ b/backend/notifications/tasks.py @@ -732,3 +732,87 @@ def process_request_notifications_task( except Exception as exc: logger.exception(f"Failed to process request notifications: {exc}") raise self.retry(exc=exc) + + +@shared_task( + name="documents.process_document_notifications", + bind=True, + max_retries=3, + default_retry_delay=30, +) +def process_document_notifications_task(self, document_id: int): + """ + Обрабатывает уведомления о новом документе асинхронно. + + Args: + document_id: ID документа + """ + try: + from documents.models import Document + from notifications.services import NotificationService + + document = Document.objects.select_related( + 'uploaded_by' + ).prefetch_related('recipients', 'departments').get(id=document_id) + + recipients = set() + + # Определяем получателей + if document.sent_to_all: + # Все активные сотрудники + recipients = set(User.objects.filter(is_active=True)) + else: + # Конкретные получатели + recipients.update(document.recipients.filter(is_active=True)) + + # Сотрудники отделов + for dept in document.departments.all(): + dept_employees = dept.employees.filter( + departments_links__is_active=True, + is_active=True + ) + recipients.update(dept_employees) + + # Исключаем загрузившего + if document.uploaded_by: + recipients.discard(document.uploaded_by) + + if not recipients: + return {"status": "success", "notifications_sent": 0} + + notifications_count = 0 + + # Отправляем уведомления + for recipient in recipients: + NotificationService.create_notification( + recipient=recipient, + notification_type_code='document_new', + title='Новый документ для ознакомления', + message=f'Документ "{document.title}" требует ознакомления', + content_object=document, + action_url=f'/documents/{document.id}/', + metadata={ + 'document_id': document.id, + 'document_title': document.title, + 'uploaded_by_id': document.uploaded_by.id if document.uploaded_by else None, + } + ) + notifications_count += 1 + + logger.info( + f"Processed document {document_id}: sent {notifications_count} notifications" + ) + + return { + "status": "success", + "document_id": document_id, + "notifications_sent": notifications_count + } + + except Document.DoesNotExist: + logger.error(f"Document {document_id} not found") + return {"status": "error", "reason": "document_not_found"} + + except Exception as exc: + logger.exception(f"Failed to process document notifications: {exc}") + raise self.retry(exc=exc) diff --git a/backend/requests_app/notification_signals.py b/backend/requests_app/notification_signals.py index aa9f7566..ef3f0cff 100644 --- a/backend/requests_app/notification_signals.py +++ b/backend/requests_app/notification_signals.py @@ -63,8 +63,28 @@ def create_request_notifications(sender, instance, created, **kwargs): new_status = request_obj.status if old_status != new_status: - # Передаем текущий request_obj с актуальным approver_id - notify_status_change(request_obj, old_status, new_status) + from notifications.tasks import process_request_notifications_task + + # Определяем action по статусу + action = 'approved' if new_status == 'approved' else \ + 'rejected' if new_status == 'rejected' else 'updated' + + def send_task(): + """Отложенная отправка задачи после commit транзакции""" + try: + process_request_notifications_task.delay( + request_id=request_obj.id, + action=action + ) + except Exception as e: + logger.warning( + f"Failed to queue status change notifications task: {e}, " + f"falling back to sync" + ) + # Fallback на старую логику + notify_status_change(request_obj, old_status, new_status) + + transaction.on_commit(send_task) except Exception as e: logger.exception(f"[SIGNAL ERROR] create_request_notifications: {e}") print(f"❌ [SIGNAL ERROR] create_request_notifications: {e}") @@ -91,13 +111,31 @@ def notify_on_recipients_changed(sender, instance, action, **kwargs): Срабатывает когда recipients устанавливаются через .set(), .add() и т.д. """ try: + from notifications.tasks import process_request_notifications_task + # Проверяем что это завершение операции установки recipients if action == "post_add" and instance.id in _pending_new_requests: print( f"🎯 [M2M_SIGNAL] Recipients установлены для заявления #{instance.id}, отправляем уведомления" ) _pending_new_requests.discard(instance.id) - notify_new_request(instance) + + def send_task(): + """Отложенная отправка задачи после commit транзакции""" + try: + process_request_notifications_task.delay( + request_id=instance.id, + action='created' + ) + except Exception as e: + logger.warning( + f"Failed to queue new request notifications task: {e}, " + f"falling back to sync" + ) + # Fallback на старую логику + notify_new_request(instance) + + transaction.on_commit(send_task) except Exception as e: logger.exception(f"[SIGNAL ERROR] notify_on_recipients_changed: {e}") print(f"❌ [SIGNAL ERROR] notify_on_recipients_changed: {e}") @@ -112,6 +150,8 @@ def notify_on_cc_users_changed(sender, instance, action, **kwargs): также отправляем уведомления. """ try: + from notifications.tasks import process_request_notifications_task + # Если это завершение добавления cc_users И заявление все еще ожидает уведомлений if action == "post_add" and instance.id in _pending_new_requests: # Проверяем что recipients пусты (значит уведомления еще не отправлены) @@ -120,7 +160,23 @@ def notify_on_cc_users_changed(sender, instance, action, **kwargs): f"🎯 [M2M_SIGNAL] CC users установлены для заявления #{instance.id} (без recipients), отправляем уведомления" ) _pending_new_requests.discard(instance.id) - notify_new_request(instance) + + def send_task(): + """Отложенная отправка задачи после commit транзакции""" + try: + process_request_notifications_task.delay( + request_id=instance.id, + action='created' + ) + except Exception as e: + logger.warning( + f"Failed to queue new request notifications task: {e}, " + f"falling back to sync" + ) + # Fallback на старую логику + notify_new_request(instance) + + transaction.on_commit(send_task) except Exception as e: logger.exception(f"[SIGNAL ERROR] notify_on_cc_users_changed: {e}") print(f"❌ [SIGNAL ERROR] notify_on_cc_users_changed: {e}") @@ -129,7 +185,7 @@ def notify_on_cc_users_changed(sender, instance, action, **kwargs): @receiver(post_save, sender=RequestComment) def create_comment_notification(sender, instance, created, **kwargs): """ - Создает уведомление при добавлении комментария к заявлению. + Создает уведомления при добавлении комментария к заявлению асинхронно. Уведомляет: - Автора заявления - Всех получателей @@ -139,9 +195,26 @@ def create_comment_notification(sender, instance, created, **kwargs): """ if not created: return - - comment = instance - request_obj = comment.request + + from notifications.tasks import process_request_notifications_task + + def send_task(): + """Отложенная отправка задачи после commit транзакции""" + try: + process_request_notifications_task.delay( + request_id=instance.request.id, + action='comment', + comment_id=instance.id + ) + except Exception as e: + logger.warning( + f"Failed to queue comment notifications task: {e}, " + f"falling back to sync" + ) + # Fallback на старую логику + notify_request_comment(instance) + + transaction.on_commit(send_task) author = comment.author recipients_set = set()