From 9eb1487ceec21e128239f9a34acc294ac68175b1 Mon Sep 17 00:00:00 2001 From: lizerk Date: Wed, 18 Mar 2026 17:12:56 +0300 Subject: [PATCH] =?UTF-8?q?refactor(departments):=20=D1=83=D0=BF=D1=80?= =?UTF-8?q?=D0=BE=D1=81=D1=82=D0=B8=D1=82=D1=8C=20DepartmentViewSet,=20?= =?UTF-8?q?=D1=83=D0=B1=D1=80=D0=B0=D1=82=D1=8C=20=D1=8F=D0=B2=D0=BD=D1=83?= =?UTF-8?q?=D1=8E=20LDAP=20=D0=BB=D0=BE=D0=B3=D0=B8=D0=BA=D1=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Удалены импорты DirectoryService, DirectoryDepartmentDTO, LDAP errors - Удален импорт _is_ldap_enabled - Убраны все блоки if ldap_enabled: ... else: ... - ViewSet теперь только работает с БД (сигналы синхронизируют LDAP) Методы упрощены до чистой БД логики: - partial_update: просто обновляет поля - set_head: просто устанавливает head + head_appointed_at - set_member_role: обновляет link.role и RoleAssignment - add_member: создает/активирует EmployeeDepartment - remove_member: деактивирует EmployeeDepartment - create: просто создает Department - destroy: просто удаляет Department DepartmentViewSet теперь чистый CRUD: -189 строк (699 → 510) --- backend/api/v1/employees/views/departments.py | 319 ++++-------------- 1 file changed, 65 insertions(+), 254 deletions(-) diff --git a/backend/api/v1/employees/views/departments.py b/backend/api/v1/employees/views/departments.py index 4d9f9c15..3f1ab0a5 100644 --- a/backend/api/v1/employees/views/departments.py +++ b/backend/api/v1/employees/views/departments.py @@ -12,10 +12,6 @@ from django.db.models import (Case, Count, Exists, F, IntegerField, OuterRef, from django.db.models.functions import Coalesce from django.shortcuts import get_object_or_404 from django.utils import timezone -from employees.ldap.directory_service import (DirectoryDepartmentDTO, - DirectoryService) -from employees.ldap.errors import (DirectoryDbError, DirectoryLdapError, - DirectoryServiceError) from employees.models import (Department, DepartmentRole, DeptPerm, EmployeeDepartment, RoleAssignment) from employees.utils import (_build_links_for_dept, _head_choices_for_dept, @@ -31,7 +27,6 @@ from ..serializers import (AddMemberInput, DepartmentBriefSerializer, DepartmentRoleSerializer, DepartmentSerializer, EmployeeBriefSerializer, RemoveMemberInput, SetHeadInput, SetMemberRoleInput) -from ._helpers import _is_ldap_enabled logger = logging.getLogger(__name__) @@ -190,43 +185,20 @@ class DepartmentViewSet(viewsets.ModelViewSet): if not ok: return Response(errs, status=status.HTTP_400_BAD_REQUEST) - # LDAP → DB - ldap_enabled = _is_ldap_enabled() - if ldap_enabled: - svc = DirectoryService() - try: - instance = svc.set_head(instance, new_head) - except ( - DirectoryLdapError, - DirectoryDbError, - DirectoryServiceError, - ) as e: - code = ( - status.HTTP_502_BAD_GATEWAY - if isinstance(e, DirectoryLdapError) - else ( - status.HTTP_400_BAD_REQUEST - if isinstance(e, DirectoryServiceError) - else status.HTTP_500_INTERNAL_SERVER_ERROR - ) - ) - return Response({"detail": str(e)}, status=code) + # Обновляем руководителя (сигналы синхронизируют с LDAP) + instance.head = new_head + if new_head: + instance.head_appointed_at = timezone.now() else: - instance.head = new_head - if new_head: - instance.head_appointed_at = timezone.now() - else: - instance.head_appointed_at = None - instance.save(update_fields=["head", "head_appointed_at"]) + instance.head_appointed_at = None + instance.save(update_fields=["head", "head_appointed_at"]) return instance def partial_update(self, request, *args, **kwargs) -> Response: - """Частичный апдейт отдела через сервисный слой (LDAP → DB).""" + """Частичный апдейт отдела.""" instance = self.get_object() data: Dict[str, Any] = request.data - ldap_enabled = _is_ldap_enabled() - svc = DirectoryService() if ldap_enabled else None # --- HEAD --- if any(k in data for k in ("head", "head_id")): @@ -264,22 +236,9 @@ class DepartmentViewSet(viewsets.ModelViewSet): changes[k] = data.get(k) if changes: - if ldap_enabled: - try: - instance = svc.update_department(instance, changes) - except DirectoryLdapError as e: - return Response( - {"detail": str(e)}, status=status.HTTP_502_BAD_GATEWAY - ) - except DirectoryDbError as e: - return Response( - {"detail": str(e)}, - status=status.HTTP_500_INTERNAL_SERVER_ERROR, - ) - else: - for k, v in changes.items(): - setattr(instance, k, v) - instance.save(update_fields=list(changes.keys())) + for k, v in changes.items(): + setattr(instance, k, v) + instance.save(update_fields=list(changes.keys())) return Response(self.get_serializer(instance).data, status=status.HTTP_200_OK) @@ -288,7 +247,7 @@ class DepartmentViewSet(viewsets.ModelViewSet): @action(detail=True, methods=["post"]) @transaction.atomic def set_head(self, request, pk: str | None = None) -> Response: - """Назначение/снятие руководителя отдела через сервисный слой (LDAP → DB).""" + """Назначение/снятие руководителя отдела.""" dept = self.get_object() payload = SetHeadInput(data=request.data) @@ -339,29 +298,8 @@ class DepartmentViewSet(viewsets.ModelViewSet): if link: # Сотрудник — член отдела: обновляем роль в линке - svc = DirectoryService() if _is_ldap_enabled() else None - - if svc: - try: - svc.set_member_role(dept, employee, role) - except ( - DirectoryLdapError, - DirectoryDbError, - DirectoryServiceError, - ) as e: - return Response( - {"detail": str(e)}, - status=( - 502 - if isinstance(e, DirectoryLdapError) - else 400 - if isinstance(e, DirectoryServiceError) - else 500 - ), - ) - else: - link.role = role - link.save(update_fields=["role"]) + link.role = role + link.save(update_fields=["role"]) # Также создаём/обновляем RoleAssignment для консистентности if role: @@ -379,49 +317,16 @@ class DepartmentViewSet(viewsets.ModelViewSet): via_assignment = True if role: - from employees.ldap.services.department_service import \ - DepartmentService - from employees.ldap.services.group_service import GroupService - from employees.ldap.services.user_service import UserService - - if _is_ldap_enabled(): - try: - group_service = GroupService() - user_service = UserService(group_service) - dept_service = DepartmentService( - group_service, user_service) - dept_service.assign_role(employee, role, request.user) - except Exception as e: - return Response({"detail": str(e)}, status=400) - else: - RoleAssignment.objects.update_or_create( - employee=employee, - role=role, - defaults={"is_active": True, - "assigned_by": request.user}, - ) + RoleAssignment.objects.update_or_create( + employee=employee, + role=role, + defaults={"is_active": True, "assigned_by": request.user}, + ) else: - from employees.ldap.services.department_service import \ - DepartmentService - from employees.ldap.services.group_service import GroupService - from employees.ldap.services.user_service import UserService - active_assignments = RoleAssignment.objects.filter( employee_id=emp_id, role__department=dept, is_active=True - ).select_related("role") - - if _is_ldap_enabled(): - try: - group_service = GroupService() - user_service = UserService(group_service) - dept_service = DepartmentService( - group_service, user_service) - for assignment in active_assignments: - dept_service.revoke_role(employee, assignment.role) - except Exception as e: - return Response({"detail": str(e)}, status=400) - else: - active_assignments.update(is_active=False) + ) + active_assignments.update(is_active=False) return Response( { @@ -501,7 +406,7 @@ class DepartmentViewSet(viewsets.ModelViewSet): @action(detail=True, methods=["post"], url_path="add_member") @transaction.atomic def add_member(self, request, pk: int | None = None): - """Добавляет сотрудника в отдел: MOVE в OU → активирует линк (LDAP → DB).""" + """Добавляет сотрудника в отдел.""" dept = self.get_object() payload = AddMemberInput(data=request.data) payload.is_valid(raise_exception=True) @@ -511,63 +416,26 @@ class DepartmentViewSet(viewsets.ModelViewSet): employee_model = Department._meta.get_field("head").remote_field.model employee = get_object_or_404(employee_model, id=emp_id) - ldap_enabled = _is_ldap_enabled() + link, created = EmployeeDepartment.objects.get_or_create( + employee_id=emp_id, department_id=dept.id, defaults={"is_active": True} + ) + if not created and not link.is_active: + link.is_active = True + link.save(update_fields=["is_active"]) - if ldap_enabled: - svc = DirectoryService() - try: - svc.add_member(dept, employee) - link = ( - EmployeeDepartment.objects.filter( - employee_id=emp_id, department_id=dept.id - ) - .only("role_id", "is_active") - .first() - ) - - return Response( - { - "employee_id": emp_id, - "is_active": True, - "role_id": getattr(link, "role_id", None) if link else None, - }, - status=status.HTTP_200_OK, - ) - except (DirectoryLdapError, DirectoryDbError, DirectoryServiceError) as e: - return Response( - {"detail": str(e)}, - status=( - status.HTTP_502_BAD_GATEWAY - if isinstance(e, DirectoryLdapError) - else ( - status.HTTP_400_BAD_REQUEST - if isinstance(e, DirectoryServiceError) - else status.HTTP_500_INTERNAL_SERVER_ERROR - ) - ), - ) - else: - link, created = EmployeeDepartment.objects.get_or_create( - employee_id=emp_id, department_id=dept.id, defaults={ - "is_active": True} - ) - if not created and not link.is_active: - link.is_active = True - link.save(update_fields=["is_active"]) - - return Response( - { - "employee_id": emp_id, - "is_active": True, - "role_id": link.role_id, - }, - status=status.HTTP_200_OK, - ) + return Response( + { + "employee_id": emp_id, + "is_active": True, + "role_id": link.role_id, + }, + status=status.HTTP_200_OK, + ) @action(detail=True, methods=["post"], url_path="remove_member") @transaction.atomic def remove_member(self, request, pk: int | None = None): - """Удаляет члена отдела: MOVE в Users OU → удаляет линк (LDAP → DB).""" + """Удаляет члена отдела.""" dept = self.get_object() payload = RemoveMemberInput(data=request.data) @@ -582,43 +450,21 @@ class DepartmentViewSet(viewsets.ModelViewSet): status=status.HTTP_400_BAD_REQUEST, ) - ldap_enabled = _is_ldap_enabled() - - if ldap_enabled: - svc = DirectoryService() - try: - svc.remove_member(dept, employee) - return Response( - {"employee_id": emp_id, "removed": True}, - status=status.HTTP_200_OK, - ) - except (DirectoryLdapError, DirectoryDbError, DirectoryServiceError) as e: - return Response( - {"detail": str(e)}, - status=( - 502 - if isinstance(e, DirectoryLdapError) - else 400 - if isinstance(e, DirectoryServiceError) - else 500 - ), - ) - else: - try: - link = EmployeeDepartment.objects.get( - employee_id=emp_id, department_id=dept.id - ) - link.is_active = False - link.save(update_fields=["is_active"]) - return Response( - {"employee_id": emp_id, "removed": True}, - status=status.HTTP_200_OK, - ) - except EmployeeDepartment.DoesNotExist: - return Response( - {"detail": "Employee is not a member of this department."}, - status=status.HTTP_404_NOT_FOUND, - ) + try: + link = EmployeeDepartment.objects.get( + employee_id=emp_id, department_id=dept.id + ) + link.is_active = False + link.save(update_fields=["is_active"]) + return Response( + {"employee_id": emp_id, "removed": True}, + status=status.HTTP_200_OK, + ) + except EmployeeDepartment.DoesNotExist: + return Response( + {"detail": "Employee is not a member of this department."}, + status=status.HTTP_404_NOT_FOUND, + ) @action(detail=False, methods=["get"], url_path="my-departments") def my_departments(self, request) -> Response: @@ -639,61 +485,26 @@ class DepartmentViewSet(viewsets.ModelViewSet): return Response(data) def create(self, request, *args, **kwargs): - """Создание отдела: сначала LDAP OU → затем запись Department.""" + """Создание отдела.""" ser = self.get_serializer(data=request.data) ser.is_valid(raise_exception=True) head = None - head_id = ser.validated_data.get( - "head") or ser.validated_data.get("head_id") + head_id = ser.validated_data.get("head") or ser.validated_data.get("head_id") if head_id: - employee_model = Department._meta.get_field( - "head").remote_field.model + employee_model = Department._meta.get_field("head").remote_field.model head = get_object_or_404(employee_model, id=head_id) - ldap_enabled = _is_ldap_enabled() - - if ldap_enabled: - dto = DirectoryDepartmentDTO( - name=ser.validated_data["name"], - description=ser.validated_data.get("description", ""), - head=head, - ) - svc = DirectoryService() - try: - dept = svc.create_department(dto) - return Response( - self.get_serializer( - dept).data, status=status.HTTP_201_CREATED - ) - except DirectoryLdapError as e: - return Response({"detail": str(e)}, status=502) - except DirectoryDbError as e: - return Response({"detail": str(e)}, status=500) - else: - dept = Department.objects.create( - name=ser.validated_data["name"], - description=ser.validated_data.get("description", ""), - head=head, - ) - return Response( - self.get_serializer(dept).data, status=status.HTTP_201_CREATED - ) + dept = Department.objects.create( + name=ser.validated_data["name"], + description=ser.validated_data.get("description", ""), + head=head, + ) + return Response( + self.get_serializer(dept).data, status=status.HTTP_201_CREATED + ) def destroy(self, request, *args, **kwargs): - """Удаляет отдел: сначала в LDAP → затем из БД.""" + """Удаляет отдел.""" dept = self.get_object() - - ldap_enabled = _is_ldap_enabled() - - if ldap_enabled: - svc = DirectoryService() - try: - svc.delete_department(dept) - return Response(status=204) - except DirectoryLdapError as e: - return Response({"detail": str(e)}, status=502) - except DirectoryDbError as e: - return Response({"detail": str(e)}, status=500) - else: - dept.delete() - return Response(status=204) + dept.delete() + return Response(status=204)