2026-09-07 19:43:48 +03:00

178 lines
6.0 KiB
Python
Raw Permalink 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.

"""Django signals для синхронизации дней рождений.
Интеграция выполняется с django-scheduler.
Использует Service Layer паттерн через django-service-objects.
Файл: employees/signals/birthday.py
"""
from __future__ import annotations
import logging
from django.db.models.signals import post_delete, post_save, pre_save
from django.dispatch import receiver
from employees.constants import ACTIVATING_MARKER_ACTIONS, PERMANENT_ACTIONS
from employees.models import Employee
from employees.services import (
DeleteBirthdayEventService,
UpsertBirthdayEventService,
)
logger = logging.getLogger(__name__)
def _action_affects_birthday_sync(action: str | None) -> bool:
return action in PERMANENT_ACTIONS or action in ACTIVATING_MARKER_ACTIONS
def _sync_birthday_for_employee_id(employee_id: int | None) -> None:
if not employee_id:
return
employee = Employee.objects.filter(pk=employee_id).first()
if not employee:
return
UpsertBirthdayEventService.execute({"employee": employee})
@receiver(post_save)
def sync_birthday_event_on_employee_save(sender, instance, created, **kwargs):
"""
Автоматически создает/обновляет событие дня рождения
при сохранении сотрудника.
Использует Service Layer для инкапсуляции бизнес-логики.
Подключается динамически в AppConfig.ready()
только для Employee модели.
"""
if sender._meta.label != "employees.Employee":
return
try:
result = UpsertBirthdayEventService.execute({"employee": instance})
if not result["success"]:
logger.debug(
"Birthday event synchronization skipped",
extra={
"event": "employee.birthday_event.skipped",
"employee_id": instance.pk,
"reason": result.get("reason"),
},
)
except Exception as error:
logger.exception(
"Birthday event synchronization failed",
extra={
"event": "employee.birthday_event.sync_failed",
"employee_id": instance.pk,
"error_type": type(error).__name__,
},
)
@receiver(pre_save)
def remember_employee_action_for_birthday_sync(sender, instance, **kwargs):
"""Запоминает прежнее кадровое действие перед изменением."""
if sender._meta.label != "employees.EmployeeAction":
return
if not instance.pk:
return
previous = (
sender.objects.filter(pk=instance.pk).values("action", "employee_id").first()
)
if not previous:
return
instance._birthday_previous_action = previous["action"]
instance._birthday_previous_employee_id = previous["employee_id"]
@receiver(post_save)
def sync_birthday_event_on_employee_action_save(sender, instance, **kwargs):
"""
Синхронизирует ДР при кадровых действиях, меняющих актуальность сотрудника.
"""
if sender._meta.label != "employees.EmployeeAction":
return
employee_ids = set()
if _action_affects_birthday_sync(instance.action):
employee_ids.add(instance.employee_id)
previous_action = getattr(instance, "_birthday_previous_action", None)
if _action_affects_birthday_sync(previous_action):
employee_ids.add(getattr(instance, "_birthday_previous_employee_id", None))
for employee_id in employee_ids:
try:
_sync_birthday_for_employee_id(employee_id)
except Exception as error:
logger.exception(
"Birthday event synchronization after personnel action failed",
extra={
"event": "employee.birthday_event.sync_failed",
"employee_id": employee_id,
"employee_action_id": instance.pk,
"error_type": type(error).__name__,
},
)
@receiver(post_delete)
def delete_birthday_event_on_employee_delete(sender, instance, **kwargs):
"""
Удаляет событие дня рождения при удалении сотрудника.
Использует Service Layer для инкапсуляции бизнес-логики.
"""
if sender._meta.label != "employees.Employee":
return
try:
result = DeleteBirthdayEventService.execute({"employee": instance})
if result["success"]:
logger.info(
"Birthday events deleted",
extra={
"event": "employee.birthday_event.deleted",
"employee_id": instance.pk,
"deleted_count": result["deleted_count"],
},
)
except Exception as error:
logger.exception(
"Birthday event deletion failed",
extra={
"event": "employee.birthday_event.deletion_failed",
"employee_id": instance.pk,
"error_type": type(error).__name__,
},
)
@receiver(post_delete)
def sync_birthday_event_on_employee_action_delete(sender, instance, **kwargs):
"""Пересинхронизирует ДР после удаления статусного кадрового действия."""
if sender._meta.label != "employees.EmployeeAction":
return
if not _action_affects_birthday_sync(instance.action):
return
try:
_sync_birthday_for_employee_id(instance.employee_id)
except Exception as error:
logger.exception(
"Birthday event synchronization after action deletion failed",
extra={
"event": "employee.birthday_event.sync_failed",
"employee_id": instance.employee_id,
"employee_action_id": instance.pk,
"error_type": type(error).__name__,
},
)