diff --git a/backend/api/v1/employees/views/actions.py b/backend/api/v1/employees/views/actions.py index 36189417..dc9e1c41 100644 --- a/backend/api/v1/employees/views/actions.py +++ b/backend/api/v1/employees/views/actions.py @@ -128,10 +128,6 @@ class EmployeeActionViewSet(HistoryActionMixin, viewsets.ModelViewSet): if not departments_processed: try: - from employees.ldap.infrastructure.connections import \ - _ldap - from employees.ldap.repositories.ldap_repository import \ - ensure_container_exists from employees.ldap.utils.ldap_utils import \ get_base_dn_for_employee @@ -146,21 +142,18 @@ class EmployeeActionViewSet(HistoryActionMixin, viewsets.ModelViewSet): if not current_dn.lower().endswith( target_base.lower() ): - with _ldap() as conn: - ensure_container_exists( - conn, target_base) - new_dn = ( - svc._user_service._move_user_to_base( - conn, current_dn, target_base - ) - ) - sync_state.touch( - ldap_dn=new_dn, sync_dir="ldap" + 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: " @@ -209,26 +202,18 @@ class EmployeeActionViewSet(HistoryActionMixin, viewsets.ModelViewSet): settings, "LDAP_USERS_BASE", None ) or getattr(settings, "LDAP_USER_BASE", None) if users_base: - from employees.ldap.infrastructure.connections import \ - _ldap - from employees.ldap.repositories.ldap_repository import \ - ensure_container_exists - - with _ldap() as conn: - ensure_container_exists( - conn, users_base) - new_dn = ( - svc._user_service._move_user_to_base( - conn, current_dn, users_base - ) - ) - sync_state.touch( - ldap_dn=new_dn, sync_dir="ldap" + 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: " diff --git a/backend/api/v1/employees/views/roles.py b/backend/api/v1/employees/views/roles.py index 5cc445f8..3b903f4d 100644 --- a/backend/api/v1/employees/views/roles.py +++ b/backend/api/v1/employees/views/roles.py @@ -89,28 +89,19 @@ class DepartmentRoleViewSet(viewsets.ModelViewSet): ldap_enabled = _is_ldap_enabled() if ldap_enabled: - from employees.ldap.services.department_service import \ - DepartmentService - from employees.ldap.services.group_service import GroupService - from employees.ldap.services.user_service import UserService + svc = DirectoryService() - group_service = GroupService() - user_service = UserService(group_service) - dept_service = DepartmentService(group_service, user_service) - - scoped_permissions = None + permission_codes = None if codes is not None: - scoped_permissions = list( - DepartmentPermission.objects.filter(code__in=codes) - ) + permission_codes = codes elif perms is not None: - scoped_permissions = list(perms) + permission_codes = [p.code for p in perms] try: - role = dept_service.create_role( - department=dept, + role = svc.create_role( + dept=dept, name=name, - scoped_permissions=scoped_permissions, + permission_codes=permission_codes, ) return Response( self.get_serializer( @@ -149,27 +140,12 @@ class DepartmentRoleViewSet(viewsets.ModelViewSet): ldap_enabled = _is_ldap_enabled() if ldap_enabled: - from employees.ldap.services.department_service import \ - DepartmentService - from employees.ldap.services.group_service import GroupService - from employees.ldap.services.user_service import UserService + svc = DirectoryService() - group_service = GroupService() - user_service = UserService(group_service) - dept_service = DepartmentService(group_service, user_service) - - changes = {} - if "name" in ser.validated_data: - changes["name"] = ser.validated_data["name"] - if "scoped_permissions" in ser.validated_data: - changes["scoped_permissions"] = ser.validated_data["scoped_permissions"] - if "scoped_permission_codes" in ser.validated_data: - changes["scoped_permission_codes"] = ser.validated_data[ - "scoped_permission_codes" - ] + new_name = ser.validated_data.get("name") try: - role = dept_service.update_role(instance, changes) + role = svc.update_role(instance, name=new_name) return Response(self.get_serializer(role).data) except DirectoryLdapError as e: return Response( @@ -195,17 +171,10 @@ class DepartmentRoleViewSet(viewsets.ModelViewSet): ldap_enabled = _is_ldap_enabled() if ldap_enabled: - from employees.ldap.services.department_service import \ - DepartmentService - from employees.ldap.services.group_service import GroupService - from employees.ldap.services.user_service import UserService - - group_service = GroupService() - user_service = UserService(group_service) - dept_service = DepartmentService(group_service, user_service) + svc = DirectoryService() try: - dept_service.delete_role(instance) + svc.delete_role(instance) return Response(status=status.HTTP_204_NO_CONTENT) except DirectoryLdapError as e: return Response( @@ -312,16 +281,9 @@ class DepartmentRoleViewSet(viewsets.ModelViewSet): if ldap_enabled: try: - from employees.ldap.services.department_service import \ - DepartmentService - from employees.ldap.services.group_service import GroupService - from employees.ldap.services.user_service import UserService + svc = DirectoryService() - group_service = GroupService() - user_service = UserService(group_service) - dept_service = DepartmentService(group_service, user_service) - - assignment = dept_service.assign_role( + assignment = svc.assign_role( employee, role, assigned_by) return Response( @@ -384,16 +346,9 @@ class DepartmentRoleViewSet(viewsets.ModelViewSet): if ldap_enabled: try: - from employees.ldap.services.department_service import \ - DepartmentService - from employees.ldap.services.group_service import GroupService - from employees.ldap.services.user_service import UserService + svc = DirectoryService() - group_service = GroupService() - user_service = UserService(group_service) - dept_service = DepartmentService(group_service, user_service) - - dept_service.revoke_role(employee, role) + svc.revoke_role(employee, role) return Response(status=204) except DirectoryLdapError as e: diff --git a/backend/employees/ldap/directory_service.py b/backend/employees/ldap/directory_service.py index 91462c50..3ff862c4 100644 --- a/backend/employees/ldap/directory_service.py +++ b/backend/employees/ldap/directory_service.py @@ -169,6 +169,132 @@ class DirectoryService: """Меняет роль участника с синхронизацией LDAP-групп Roles.""" return self._department_service.set_member_role(dept, employee, role) + def assign_role( + self, + employee: Employee, + role: DepartmentRole, + assigned_by: Optional[Employee] = None, + ): + """Назначает роль сотруднику (не требует членства в отделе). + + NOTE: Метод делегирует к DepartmentService. + + Args: + employee: Сотрудник. + role: Роль для назначения. + assigned_by: Кто назначил (опционально). + + Returns: + RoleAssignment: Созданное/обновлённое назначение. + """ + return self._department_service.assign_role(employee, role, assigned_by) + + def revoke_role(self, employee: Employee, role: DepartmentRole) -> None: + """Отзывает роль у сотрудника. + + NOTE: Метод делегирует к DepartmentService. + + Args: + employee: Сотрудник. + role: Роль для отзыва. + """ + return self._department_service.revoke_role(employee, role) + + def create_role( + self, + dept: Department, + name: str, + permission_codes: Optional[List[str]] = None, + ) -> DepartmentRole: + """Создаёт роль отдела с LDAP-группой. + + NOTE: Метод делегирует к DepartmentService. + + Args: + dept: Отдел. + name: Название роли. + permission_codes: Коды прав (опционально). + + Returns: + DepartmentRole: Созданная роль. + """ + from employees.models import DepartmentPermission + + scoped_permissions = None + if permission_codes: + scoped_permissions = list( + DepartmentPermission.objects.filter(code__in=permission_codes) + ) + + return self._department_service.create_role( + department=dept, + name=name, + scoped_permissions=scoped_permissions, + ) + + def update_role( + self, role: DepartmentRole, name: Optional[str] = None + ) -> DepartmentRole: + """Обновляет роль (переименование LDAP-группы + БД). + + NOTE: Метод делегирует к DepartmentService. + + Args: + role: Роль. + name: Новое название (опционально). + + Returns: + DepartmentRole: Обновлённая роль. + """ + changes = {} + if name: + changes["name"] = name + + return self._department_service.update_role(role, changes) + + def delete_role(self, role: DepartmentRole) -> None: + """Удаляет роль: LDAP-группу + БД. + + NOTE: Метод делегирует к DepartmentService. + + Args: + role: Роль для удаления. + """ + return self._department_service.delete_role(role) + + def move_user_to_base(self, employee: Employee, base_dn: str) -> str: + """Перемещает пользователя в указанный базовый OU. + + NOTE: Метод делегирует к UserService. + + Args: + employee: Сотрудник для перемещения. + base_dn: DN базового OU. + + Returns: + str: Новый DN пользователя. + + Raises: + DirectoryLdapError: Ошибка LDAP. + DirectoryServiceError: Пользователь не имеет DN. + """ + from .infrastructure.connections import _ldap + + sync_state = LdapSyncState.objects.filter( + model="employee", object_pk=str(employee.pk) + ).first() + + if not sync_state or not sync_state.ldap_dn: + from .errors import DirectoryServiceError + raise DirectoryServiceError(f"Employee {employee.pk} has no LDAP DN") + + with _ldap() as conn: + new_dn = self._user_service._move_user_to_base( + conn, sync_state.ldap_dn, base_dn + ) + sync_state.touch(ldap_dn=new_dn, sync_dir="ldap") + return new_dn + # =========================== POSITIONS =========================== # def reconcile_position(self, pos: Position) -> str: