"""ViewSets для API модуля закупок.""" from django.db.models import Q from django.utils import timezone from django_filters.rest_framework import DjangoFilterBackend from drf_spectacular.utils import extend_schema from rest_framework import filters, permissions, status, viewsets from rest_framework.decorators import action from rest_framework.response import Response from employees.models import Department from procurement.constants import ( ProcurementStatus, ) from procurement.models import ( Budget, ProcurementRequest, ) from ..serializers import ( BudgetSerializer, ProcurementDepartmentStatsSerializer, ProcurementOverviewStatsSerializer, ) class ProcurementStatsViewSet(viewsets.ViewSet): """ViewSet для статистики закупок.""" permission_classes = [permissions.IsAuthenticated] @extend_schema( tags=["Procurement"], summary="Получить общую статистику закупок", responses=ProcurementOverviewStatsSerializer, ) @action(detail=False, methods=["get"]) def overview(self, request): """Общая статистика закупок.""" from django.db.models import Count, Sum from django.utils import timezone now = timezone.now() month_start = now.replace(day=1, hour=0, minute=0, second=0) year_start = now.replace(month=1, day=1, hour=0, minute=0, second=0) user = request.user # Базовый queryset зависит от прав пользователя if user.is_superuser or user.is_staff: base_qs = ProcurementRequest.objects.all() else: base_qs = ProcurementRequest.objects.filter( Q(requestor=user) | Q(department__in=user.departments.all()) ) # Подсчёт по статусам by_status = dict( base_qs.values("status") .annotate(count=Count("id")) .values_list("status", "count") ) # Подсчёт по срочности by_urgency = dict( base_qs.values("urgency") .annotate(count=Count("id")) .values_list("urgency", "count") ) # Общие метрики total = base_qs.count() pending = base_qs.filter(status=ProcurementStatus.PENDING).count() approved_month = base_qs.filter( status=ProcurementStatus.APPROVED, updated_at__gte=month_start ).count() completed_month = base_qs.filter( status=ProcurementStatus.COMPLETED, completed_at__gte=month_start ).count() # Сумма потраченного за год spent_year = ( base_qs.filter( status=ProcurementStatus.COMPLETED, completed_at__gte=year_start ).aggregate(total=Sum("actual_cost"))["total"] or 0 ) return Response( { "total_requests": total, "pending_requests": pending, "approved_this_month": approved_month, "completed_this_month": completed_month, "total_spent_this_year": str(spent_year), "by_status": by_status, "by_urgency": by_urgency, } ) @extend_schema( tags=["Procurement"], summary="Получить статистику закупок по отделам", responses=ProcurementDepartmentStatsSerializer(many=True), ) @action(detail=False, methods=["get"], url_path="by-department") def by_department(self, request): """Статистика по отделам.""" from django.db.models import Sum user = request.user # Только для staff/superuser или руководителей if not (user.is_superuser or user.is_staff): if not user.headed_departments.exists(): return Response( {"detail": "Нет доступа к статистике по отделам."}, status=status.HTTP_403_FORBIDDEN, ) departments = user.headed_departments.all() else: departments = Department.objects.all() result = [] for dept in departments: requests = ProcurementRequest.objects.filter(department=dept) total = requests.count() spent = ( requests.filter(status=ProcurementStatus.COMPLETED).aggregate( total=Sum("actual_cost") )["total"] or 0 ) result.append( { "department": {"id": dept.id, "name": dept.name}, "total_requests": total, "total_spent": str(spent), } ) return Response(result) class BudgetViewSet(viewsets.ReadOnlyModelViewSet): """ViewSet для просмотра бюджетов отдела.""" queryset = Budget.objects.select_related("department") serializer_class = BudgetSerializer filter_backends = [DjangoFilterBackend, filters.OrderingFilter] filterset_fields = ["department", "year", "quarter"] ordering = ["-year", "-quarter", "department__name"] def get_permissions(self): return [permissions.IsAuthenticated()] def get_queryset(self): queryset = super().get_queryset() user = self.request.user if user.is_superuser or user.is_staff: return queryset headed_departments = user.headed_departments.all() if headed_departments.exists(): return queryset.filter(department__in=headed_departments) if self.action == "my_department": return queryset return queryset.none() def list(self, request, *args, **kwargs): user = request.user if not (user.is_superuser or user.is_staff or user.headed_departments.exists()): return Response( {"detail": "Нет доступа к бюджетам отделов."}, status=status.HTTP_403_FORBIDDEN, ) return super().list(request, *args, **kwargs) @action(detail=False, methods=["get"], url_path="my-department") def my_department(self, request): """Бюджет текущего отдела пользователя на текущий квартал.""" user_departments = request.user.departments.all() department = user_departments.first() if department is None: return Response( {"detail": "Пользователь не состоит ни в одном отделе."}, status=status.HTTP_404_NOT_FOUND, ) now = timezone.now() quarter = (now.month - 1) // 3 + 1 budget = ( self.get_queryset() .filter( department=department, year=now.year, quarter=quarter, ) .first() ) if budget is None: return Response( {"detail": "Бюджет на текущий квартал не найден."}, status=status.HTTP_404_NOT_FOUND, ) serializer = self.get_serializer(budget) return Response(serializer.data)