315 lines
11 KiB
Python
315 lines
11 KiB
Python
"""
|
||
Тесты для Budget - reserved_amount, my_department, stats endpoints.
|
||
"""
|
||
|
||
from decimal import Decimal
|
||
|
||
import pytest
|
||
from django.contrib.auth.models import Permission
|
||
from django.utils import timezone
|
||
from rest_framework import status
|
||
|
||
from procurement.constants import ProcurementStatus, UrgencyLevel
|
||
from procurement.models import (
|
||
Budget,
|
||
ProcurementItem,
|
||
ProcurementRequest,
|
||
)
|
||
|
||
|
||
@pytest.mark.django_db
|
||
class TestBudgetReservedAmount:
|
||
"""Тесты для reserved_amount и available_amount."""
|
||
|
||
@pytest.fixture(autouse=True)
|
||
def setup(self, department_factory, user_factory, link_factory):
|
||
"""Настройка тестовых данных."""
|
||
self.department = department_factory(name="IT Reserved Test")
|
||
now = timezone.now()
|
||
self.quarter = (now.month - 1) // 3 + 1
|
||
|
||
self.budget = Budget.objects.create(
|
||
department=self.department,
|
||
year=now.year,
|
||
quarter=self.quarter,
|
||
allocated_amount=Decimal("100000.00"),
|
||
spent_amount=Decimal("20000.00"),
|
||
)
|
||
|
||
self.user = user_factory(
|
||
email="budgetuser@test.com",
|
||
first_name="Test",
|
||
last_name="User",
|
||
)
|
||
link_factory(self.user, self.department, is_active=True)
|
||
|
||
def test_reserved_amount_no_pending(self):
|
||
"""Тест: reserved_amount = 0 если нет pending заявок."""
|
||
assert self.budget.reserved_amount == Decimal("0.00")
|
||
|
||
def test_reserved_amount_with_pending(self):
|
||
"""Тест: reserved_amount считает сумму pending заявок."""
|
||
# Создаём pending заявки с позициями
|
||
request1 = ProcurementRequest.objects.create(
|
||
title="Заявка 1",
|
||
description="Описание",
|
||
department=self.department,
|
||
requestor=self.user,
|
||
status=ProcurementStatus.PENDING,
|
||
urgency=UrgencyLevel.LOW,
|
||
)
|
||
ProcurementItem.objects.create(
|
||
request=request1,
|
||
name="Товар 1",
|
||
quantity=1,
|
||
unit="шт",
|
||
estimated_unit_price=Decimal("5000.00"),
|
||
)
|
||
|
||
request2 = ProcurementRequest.objects.create(
|
||
title="Заявка 2",
|
||
description="Описание",
|
||
department=self.department,
|
||
requestor=self.user,
|
||
status=ProcurementStatus.PENDING,
|
||
urgency=UrgencyLevel.MEDIUM,
|
||
)
|
||
ProcurementItem.objects.create(
|
||
request=request2,
|
||
name="Товар 2",
|
||
quantity=1,
|
||
unit="шт",
|
||
estimated_unit_price=Decimal("3000.00"),
|
||
)
|
||
|
||
# reserved должен быть 8000
|
||
assert self.budget.reserved_amount == Decimal("8000.00")
|
||
|
||
def test_available_amount(self):
|
||
"""Тест: available_amount = remaining - reserved."""
|
||
request = ProcurementRequest.objects.create(
|
||
title="Pending заявка",
|
||
description="Описание",
|
||
department=self.department,
|
||
requestor=self.user,
|
||
status=ProcurementStatus.PENDING,
|
||
urgency=UrgencyLevel.LOW,
|
||
)
|
||
ProcurementItem.objects.create(
|
||
request=request,
|
||
name="Товар",
|
||
quantity=1,
|
||
unit="шт",
|
||
estimated_unit_price=Decimal("10000.00"),
|
||
)
|
||
|
||
# remaining = 100000 - 20000 = 80000
|
||
# reserved = 10000
|
||
# available = 80000 - 10000 = 70000
|
||
assert self.budget.remaining_amount == Decimal("80000.00")
|
||
assert self.budget.reserved_amount == Decimal("10000.00")
|
||
assert self.budget.available_amount == Decimal("70000.00")
|
||
|
||
|
||
@pytest.mark.django_db
|
||
class TestBudgetMyDepartmentEndpoint:
|
||
"""Тесты для /budgets/my-department/ endpoint."""
|
||
|
||
@pytest.fixture(autouse=True)
|
||
def setup(self, api_client, department_factory, user_factory, link_factory):
|
||
"""Настройка тестовых данных."""
|
||
self.client = api_client
|
||
|
||
self.department = department_factory(name="IT My Dept Test")
|
||
|
||
now = timezone.now()
|
||
self.quarter = (now.month - 1) // 3 + 1
|
||
|
||
self.budget = Budget.objects.create(
|
||
department=self.department,
|
||
year=now.year,
|
||
quarter=self.quarter,
|
||
allocated_amount=Decimal("100000.00"),
|
||
spent_amount=Decimal("30000.00"),
|
||
)
|
||
|
||
self.user = user_factory(
|
||
email="mydeptuser@test.com",
|
||
first_name="Test",
|
||
last_name="User",
|
||
)
|
||
link_factory(self.user, self.department, is_active=True)
|
||
self.user_factory = user_factory
|
||
|
||
def test_my_department_success(self):
|
||
"""Тест: получение бюджета своего отдела."""
|
||
self.client.force_authenticate(user=self.user)
|
||
|
||
response = self.client.get("/api/v1/procurement/budgets/my-department/")
|
||
|
||
assert response.status_code == status.HTTP_200_OK
|
||
assert response.data["department"] == self.department.id
|
||
allocated = Decimal(response.data["allocated_amount"])
|
||
assert allocated == Decimal("100000.00")
|
||
spent = Decimal(response.data["spent_amount"])
|
||
assert spent == Decimal("30000.00")
|
||
remaining = Decimal(response.data["remaining_amount"])
|
||
assert remaining == Decimal("70000.00")
|
||
assert "reserved_amount" in response.data
|
||
assert "available_amount" in response.data
|
||
|
||
def test_my_department_no_department(self):
|
||
"""Тест: 404 если пользователь не в отделе."""
|
||
user_no_dept = self.user_factory(
|
||
email="nodept@test.com",
|
||
first_name="No",
|
||
last_name="Dept",
|
||
)
|
||
self.client.force_authenticate(user=user_no_dept)
|
||
|
||
response = self.client.get("/api/v1/procurement/budgets/my-department/")
|
||
|
||
assert response.status_code == status.HTTP_404_NOT_FOUND
|
||
|
||
def test_my_department_no_budget(self):
|
||
"""Тест: 404 если нет бюджета на текущий квартал."""
|
||
# Удаляем бюджет
|
||
self.budget.delete()
|
||
|
||
self.client.force_authenticate(user=self.user)
|
||
|
||
response = self.client.get("/api/v1/procurement/budgets/my-department/")
|
||
|
||
assert response.status_code == status.HTTP_404_NOT_FOUND
|
||
|
||
|
||
@pytest.mark.django_db
|
||
class TestProcurementStatsEndpoints:
|
||
"""Тесты для /stats/ endpoints."""
|
||
|
||
@pytest.fixture(autouse=True)
|
||
def setup(self, api_client, department_factory, user_factory, link_factory):
|
||
"""Настройка тестовых данных."""
|
||
self.client = api_client
|
||
|
||
self.department = department_factory(name="IT Stats Test")
|
||
|
||
self.user = user_factory(
|
||
email="statsuser@test.com",
|
||
first_name="Test",
|
||
last_name="User",
|
||
)
|
||
link_factory(self.user, self.department, is_active=True)
|
||
|
||
self.staff_user = user_factory(
|
||
email="statsstaff@test.com",
|
||
first_name="Staff",
|
||
last_name="User",
|
||
)
|
||
self.staff_user.is_staff = True
|
||
self.staff_user.save()
|
||
|
||
# Создаём несколько заявок
|
||
for i in range(3):
|
||
ProcurementRequest.objects.create(
|
||
title=f"Заявка {i + 1}",
|
||
description="Описание",
|
||
department=self.department,
|
||
requestor=self.user,
|
||
status=ProcurementStatus.PENDING,
|
||
urgency=UrgencyLevel.MEDIUM,
|
||
)
|
||
|
||
def test_stats_overview(self):
|
||
"""Тест: получение общей статистики."""
|
||
self.client.force_authenticate(user=self.user)
|
||
|
||
response = self.client.get("/api/v1/procurement/stats/overview/")
|
||
|
||
assert response.status_code == status.HTTP_200_OK
|
||
assert "total_requests" in response.data
|
||
assert "pending_requests" in response.data
|
||
assert "by_status" in response.data
|
||
assert "by_urgency" in response.data
|
||
|
||
def test_stats_by_department_staff(self):
|
||
"""Тест: статистика по отделам для staff."""
|
||
self.client.force_authenticate(user=self.staff_user)
|
||
|
||
response = self.client.get("/api/v1/procurement/stats/by-department/")
|
||
|
||
assert response.status_code == status.HTTP_200_OK
|
||
assert isinstance(response.data, list)
|
||
|
||
def test_stats_by_department_regular_user(self):
|
||
"""Тест: 403 для обычного пользователя."""
|
||
self.client.force_authenticate(user=self.user)
|
||
|
||
response = self.client.get("/api/v1/procurement/stats/by-department/")
|
||
|
||
assert response.status_code == status.HTTP_403_FORBIDDEN
|
||
|
||
def test_stats_by_department_head(self):
|
||
"""Тест: руководитель отдела видит статистику своих отделов."""
|
||
# Делаем пользователя руководителем
|
||
self.department.head = self.user
|
||
self.department.save()
|
||
|
||
self.client.force_authenticate(user=self.user)
|
||
|
||
response = self.client.get("/api/v1/procurement/stats/by-department/")
|
||
|
||
assert response.status_code == status.HTTP_200_OK
|
||
assert len(response.data) == 1
|
||
assert response.data[0]["department"]["id"] == self.department.id
|
||
|
||
|
||
@pytest.mark.django_db
|
||
class TestBudgetAlerts:
|
||
"""Тесты для алертов бюджета."""
|
||
|
||
@pytest.fixture(autouse=True)
|
||
def setup(self, api_client, department_factory, user_factory, link_factory):
|
||
"""Настройка тестовых данных."""
|
||
self.client = api_client
|
||
|
||
self.department = department_factory(name="IT Alerts Test")
|
||
|
||
now = timezone.now()
|
||
self.quarter = (now.month - 1) // 3 + 1
|
||
|
||
# Бюджет почти исчерпан (95% использовано)
|
||
self.budget = Budget.objects.create(
|
||
department=self.department,
|
||
year=now.year,
|
||
quarter=self.quarter,
|
||
allocated_amount=Decimal("100000.00"),
|
||
spent_amount=Decimal("95000.00"), # 95% использовано
|
||
)
|
||
|
||
self.requestor = user_factory(
|
||
email="alertsrequestor@test.com",
|
||
first_name="Requestor",
|
||
last_name="User",
|
||
)
|
||
link_factory(self.requestor, self.department, is_active=True)
|
||
|
||
# Руководитель отдела
|
||
self.dept_head = user_factory(
|
||
email="alertshead@test.com",
|
||
first_name="Department",
|
||
last_name="Head",
|
||
)
|
||
link_factory(self.dept_head, self.department, is_active=True)
|
||
self.department.head = self.dept_head
|
||
self.department.save()
|
||
|
||
# Даём права на approve
|
||
perm = Permission.objects.get(codename="approve_procurementrequest")
|
||
self.dept_head.user_permissions.add(perm)
|
||
|
||
def test_budget_utilization_above_90(self):
|
||
"""Тест: бюджет используется более чем на 90%."""
|
||
assert self.budget.utilization_percentage == Decimal("95.00")
|
||
assert self.budget.remaining_amount == Decimal("5000.00")
|