refactor(actions): упростить EmployeeActionViewSet - удалить LDAP логику, использовать сигналы

Изменения:
- EmployeeActionViewSet: 240 → 115 строк (-125 строк, -52%)
- _apply_effects(): удалена вся LDAP логика (~140 строк)

Удалено:
- Прямые вызовы DirectoryService
- Обработка LdapSyncState
- Логика перемещения в OU=Dismissed и обратно
- Удаление из департаментов в LDAP
- Все try/except блоки для LDAP ошибок
- Импорты: DirectoryService, DirectoryLdapError, DirectoryServiceError, DirectoryDbError, LdapSyncState, settings, _is_ldap_enabled

Осталось только работа с БД:
- При ACTION_DISMISSED: is_active=False + деактивация EmployeeDepartment
- При других действиях: is_active=True

LDAP синхронизация через существующие сигналы:
- sync_employee_to_ldap_on_save: обрабатывает is_active (активация/деактивация)
- sync_member (signals_department.py): обрабатывает EmployeeDepartment (удаление из отделов)

Функционал сохранен, код стал в 2 раза короче
This commit is contained in:
lizerk 2026-03-18 18:19:58 +03:00
parent 18ca956348
commit e606a6ce2b

View File

@ -4,21 +4,17 @@ from __future__ import annotations
import logging
from django.conf import settings
from django.db import transaction
from django.utils import timezone
from employees.constants import ACTION_DISMISSED
from employees.ldap.directory_service import DirectoryService
from employees.ldap.errors import (DirectoryDbError, DirectoryLdapError,
DirectoryServiceError)
from employees.models import EmployeeAction, EmployeeDepartment, LdapSyncState
from employees.models import EmployeeAction, EmployeeDepartment
from rest_framework import filters, viewsets
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from ...permissions import AdminOrActionOrModelPerms
from ..serializers import EmployeeActionSerializer
from ._helpers import HistoryActionMixin, _is_ldap_enabled
from ._helpers import HistoryActionMixin
logger = logging.getLogger(__name__)
@ -82,153 +78,32 @@ class EmployeeActionViewSet(HistoryActionMixin, viewsets.ModelViewSet):
# --- бизнес-эффекты после записи события ---
def _apply_effects(self, action_obj: EmployeeAction):
"""Применяет эффекты кадрового события.
Работает только с БД. LDAP синхронизация через сигналы:
- sync_employee_to_ldap_on_save: активация/деактивация в LDAP
- sync_member (signals_department.py): удаление из отделов в LDAP
"""
emp = action_obj.employee
ldap_enabled = _is_ldap_enabled()
if action_obj.action == ACTION_DISMISSED:
# Сначала деактивируем сотрудника
# Деактивируем сотрудника (сигнал синхронизирует в LDAP)
if emp.is_active:
emp.is_active = False
emp._ldap_changes = {"is_active": False}
emp.save(update_fields=["is_active"])
# Деактивируем связи с отделами в БД
# Деактивируем связи с отделами (сигналы синхронизируют в LDAP)
EmployeeDepartment.objects.filter(employee=emp, is_active=True).update(
is_active=False, date_to=timezone.now().date()
)
# Синхронизируем с LDAP
if ldap_enabled:
try:
svc = DirectoryService()
has_ldap_dn = LdapSyncState.objects.filter(
model="employee", object_pk=str(emp.pk), ldap_dn__isnull=False
).exists()
if has_ldap_dn:
svc.update_user(emp, changes={"is_active": False})
active_departments = EmployeeDepartment.objects.filter(
employee=emp
).select_related("department")
departments_processed = False
for emp_dept in active_departments:
try:
svc.remove_member(emp_dept.department, emp)
departments_processed = True
except (
DirectoryLdapError,
DirectoryDbError,
DirectoryServiceError,
) as e:
logger.error(
f"Failed to remove dismissed employee from department: "
f"employee_id={emp.id}, department_id={emp_dept.department.id}, error={e}"
)
if not departments_processed:
try:
from employees.ldap.utils.ldap_utils import \
get_base_dn_for_employee
sync_state = LdapSyncState.objects.filter(
model="employee", object_pk=str(emp.pk)
).first()
if sync_state and sync_state.ldap_dn:
target_base = get_base_dn_for_employee(emp)
current_dn = sync_state.ldap_dn
if not current_dn.lower().endswith(
target_base.lower()
):
try:
new_dn = svc.move_user_to_base(
emp, target_base
)
logger.info(
f"Dismissed employee without department moved to OU=Dismissed: "
f"employee_id={emp.id}, new_dn={new_dn}"
)
except Exception as move_err:
logger.error(
f"Failed to move dismissed employee: {move_err}"
)
except Exception as e:
logger.error(
f"Failed to move dismissed employee without department to OU=Dismissed: "
f"employee_id={emp.id}, error={e}"
)
except (
DirectoryLdapError,
DirectoryDbError,
DirectoryServiceError,
) as e:
logger.error(
f"Failed to disable user in LDAP during dismissal: "
f"employee_id={emp.id}, error={e}"
)
else:
# Любое иное событие делает сотрудника активным
was_inactive = not emp.is_active
if was_inactive:
# Любое иное событие делает сотрудника активным (сигнал синхронизирует в LDAP)
if not emp.is_active:
emp.is_active = True
emp._ldap_changes = {"is_active": True}
emp.save(update_fields=["is_active"])
if ldap_enabled:
try:
svc = DirectoryService()
has_ldap_dn = LdapSyncState.objects.filter(
model="employee", object_pk=str(emp.pk), ldap_dn__isnull=False
).exists()
if has_ldap_dn:
svc.update_user(emp, changes={"is_active": True})
if was_inactive:
try:
sync_state = LdapSyncState.objects.get(
model="employee", object_pk=str(emp.pk)
)
current_dn = sync_state.ldap_dn
dismissed_base = getattr(
settings, "LDAP_DISMISSED_BASE", ""
)
if dismissed_base and current_dn.lower().endswith(
dismissed_base.lower()
):
users_base = getattr(
settings, "LDAP_USERS_BASE", None
) or getattr(settings, "LDAP_USER_BASE", None)
if users_base:
try:
new_dn = svc.move_user_to_base(
emp, users_base
)
logger.info(
f"Restored employee moved from Dismissed to Users: "
f"employee_id={emp.id}, new_dn={new_dn}"
)
except Exception as move_err:
logger.error(
f"Failed to move restored employee: {move_err}"
)
except Exception as e:
logger.error(
f"Failed to move restored employee from Dismissed to Users: "
f"employee_id={emp.id}, error={e}"
)
except (
DirectoryLdapError,
DirectoryDbError,
DirectoryServiceError,
) as e:
logger.error(
f"Failed to enable user in LDAP during action: "
f"employee_id={emp.id}, action={action_obj.action}, error={e}"
)
@transaction.atomic
def perform_create(self, serializer):
obj = serializer.save()