212 lines
7.3 KiB
Python

"""ViewSets для API модуля закупок."""
from drf_spectacular.utils import extend_schema
from django.db.models import Q
from django.utils import timezone
from django_filters.rest_framework import DjangoFilterBackend
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 Sum, Count
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)