"""ViewSet composition for procurement requests.""" from django.contrib.contenttypes.models import ContentType from django_filters.rest_framework import DjangoFilterBackend from rest_framework import filters, permissions, viewsets from rest_framework.decorators import action from rest_framework.response import Response from procurement.activity import create_procurement_activity from procurement.models import ( ProcurementActivityAction, ProcurementActivityObjectKind, ProcurementRequest, ) from ..permissions import ( CanApproveProcurementRequest, CanManageProcurementRequest, ) from ..serializers import ( ProcurementRequestActivitySerializer, ProcurementRequestCreateSerializer, ProcurementRequestDetailSerializer, ProcurementRequestListSerializer, ) from .mixins import ( ProcurementRequestQueryMixin, RequestCommentsMixin, RequestExecutionMixin, RequestWorkflowMixin, ) class ProcurementRequestViewSet( ProcurementRequestQueryMixin, RequestWorkflowMixin, RequestCommentsMixin, RequestExecutionMixin, viewsets.ModelViewSet, ): """ViewSet для заявок на закупку.""" queryset = ProcurementRequest.objects.select_related( "department", "processing_department", "requestor", "executor", ).prefetch_related( "items__attachments__uploaded_by", "approvals__approver", ) filter_backends = [ DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter, ] filterset_fields = { "status": ["exact", "in"], "urgency": ["exact"], "department": ["exact"], "processing_department": ["exact"], "requestor": ["exact"], "executor": ["exact"], "fulfillment_status": ["exact"], } search_fields = ["title", "description"] ordering_fields = ["created_at", "status"] ordering = None def get_serializer_class(self): """Выбрать сериализатор в зависимости от действия.""" if self.action == "create": return ProcurementRequestCreateSerializer if self.action in { "retrieve", "submit", "approve", "reject", "start_work", "complete", "cancel", "mark_all_received", "notify_arrival", "set_viewed", "approval_options", "activity", }: return ProcurementRequestDetailSerializer return ProcurementRequestListSerializer @action(detail=True, methods=["get"], url_path="activity") def activity(self, request, pk=None): procurement_request = self.get_object() queryset = procurement_request.activities.select_related("actor").order_by( "-created_at", "-id", ) serializer = ProcurementRequestActivitySerializer(queryset, many=True) return Response(serializer.data) def perform_update(self, serializer): tracked_fields = [ "title", "description", "department_id", "processing_department_id", "urgency", "actual_cost", ] before = { field: getattr(serializer.instance, field) for field in tracked_fields } procurement_request = serializer.save() changed_fields = [ field for field in tracked_fields if before[field] != getattr(procurement_request, field) ] if changed_fields: create_procurement_activity( procurement_request, self.request.user, ProcurementActivityAction.UPDATED, object_kind=ProcurementActivityObjectKind.REQUEST, object_id=procurement_request.pk, metadata={"fields": changed_fields}, ) def get_permissions(self): """Выбрать права доступа в зависимости от действия.""" if self.action in {"approve", "reject"}: permission_classes = [CanApproveProcurementRequest] elif self.action in { "submit", "set_viewed", "create_options", "approval_options", }: permission_classes = [permissions.IsAuthenticated] else: permission_classes = [CanManageProcurementRequest] return [permission() for permission in permission_classes] def _attach_linked_task_payloads( self, requests: list[ProcurementRequest], ) -> None: """Предзагрузить компактные бейджи задач для списка закупок.""" if not requests: return user = getattr(self.request, "user", None) request_ids = [request_obj.id for request_obj in requests] mapping = {request_id: [] for request_id in request_ids} if user and user.is_authenticated: from tasks.access import task_board_access_q from tasks.models import ( TaskBoard, TaskLinkedObject, TaskLinkedObjectKind, ) request_content_type = ContentType.objects.get_for_model(ProcurementRequest) accessible_boards = TaskBoard.objects.filter( is_archived=False, ).filter(task_board_access_q(user)) links = ( TaskLinkedObject.objects.filter( kind=TaskLinkedObjectKind.PROCUREMENT_REQUEST, content_type=request_content_type, object_id__in=request_ids, task__board__in=accessible_boards, ) .select_related("task", "task__board", "task__column") .order_by("object_id", "task__title", "task_id") ) for link in links: mapping.setdefault(link.object_id, []).append( { "link_id": link.id, "id": link.task_id, "title": link.task.title, "board_id": link.task.board_id, "board_name": link.task.board.name, "column_id": link.task.column_id, "column_name": link.task.column.name, "column_color": link.task.column.color, "priority": link.task.priority, "priority_display": link.task.get_priority_display(), } ) for request_obj in requests: request_obj._linked_task_payloads = mapping.get(request_obj.id, []) def list(self, request, *args, **kwargs): queryset = self.filter_queryset(self.get_queryset()) page = self.paginate_queryset(queryset) if page is not None: self._attach_linked_task_payloads(list(page)) serializer = self.get_serializer(page, many=True) return self.get_paginated_response(serializer.data) requests = list(queryset) self._attach_linked_task_payloads(requests) serializer = self.get_serializer(requests, many=True) return Response(serializer.data)