Extend organization analytics contract and fix test warnings
Some checks failed
CI/CD Pipeline / Code Quality Checks (push) Failing after 1m42s
CI/CD Pipeline / Run Tests (push) Successful in 1m45s
CI/CD Pipeline / Build Docker Images (push) Has been skipped
CI/CD Pipeline / Push to Gitea Registry (push) Has been skipped
CI/CD Pipeline / Deploy to Server (push) Has been skipped
Some checks failed
CI/CD Pipeline / Code Quality Checks (push) Failing after 1m42s
CI/CD Pipeline / Run Tests (push) Successful in 1m45s
CI/CD Pipeline / Build Docker Images (push) Has been skipped
CI/CD Pipeline / Push to Gitea Registry (push) Has been skipped
CI/CD Pipeline / Deploy to Server (push) Has been skipped
This commit is contained in:
@@ -7,7 +7,7 @@ from django.core.management.base import CommandError
|
||||
from django.test import TestCase
|
||||
|
||||
|
||||
class TestCommand(BaseAppCommand):
|
||||
class SampleCommand(BaseAppCommand):
|
||||
"""Тестовая команда для проверки BaseAppCommand."""
|
||||
|
||||
help = "Test command"
|
||||
|
||||
@@ -51,6 +51,7 @@ class OrganizationAnalyticsApiTest(APITestCase):
|
||||
organization=self.organization,
|
||||
report_year=2026,
|
||||
report_quarter=1,
|
||||
avg_payroll_employees=995,
|
||||
payroll_fund=Decimal("1000000.00"),
|
||||
military_output_actual=Decimal("11000000.00"),
|
||||
civilian_output_actual=Decimal("7000000.00"),
|
||||
@@ -65,6 +66,7 @@ class OrganizationAnalyticsApiTest(APITestCase):
|
||||
organization=self.organization,
|
||||
report_year=2025,
|
||||
report_quarter=1,
|
||||
avg_payroll_employees=970,
|
||||
payroll_fund=Decimal("900000.00"),
|
||||
military_output_actual=Decimal("9000000.00"),
|
||||
civilian_output_actual=Decimal("6000000.00"),
|
||||
@@ -151,6 +153,9 @@ class OrganizationAnalyticsApiTest(APITestCase):
|
||||
report_year=2026,
|
||||
equipment_category="Станочное оборудование",
|
||||
is_domestic=True,
|
||||
commissioning_date=date(2026, 1, 15),
|
||||
is_operational=False,
|
||||
utilization_rate=Decimal("92.00"),
|
||||
physical_wear_percent=Decimal("28.40"),
|
||||
)
|
||||
FormF6RecordFactory.create(
|
||||
@@ -218,6 +223,12 @@ class OrganizationAnalyticsApiTest(APITestCase):
|
||||
self.assertIn("ros", response.data["ratios"][0])
|
||||
self.assertIn("roa", response.data["ratios"][0])
|
||||
self.assertIn("roe", response.data["ratios"][0])
|
||||
self.assertIn("ebitda_margin", response.data["ratios"][0])
|
||||
self.assertEqual(
|
||||
set(response.data["ratio_normatives"]),
|
||||
{"ros", "roa", "roe", "ebitda_margin"},
|
||||
)
|
||||
self.assertTrue(all(value is not None for value in response.data["ratio_normatives"].values()))
|
||||
|
||||
def test_personnel_contract(self):
|
||||
personnel_response = self.client.get(
|
||||
@@ -233,6 +244,11 @@ class OrganizationAnalyticsApiTest(APITestCase):
|
||||
)
|
||||
self.assertEqual(len(personnel_response.data["history"]), 2)
|
||||
self.assertEqual(len(personnel_response.data["age_distribution"]), 3)
|
||||
self.assertIn("average_age", personnel_response.data)
|
||||
self.assertEqual(
|
||||
personnel_response.data["headcount"]["avg_payroll_employees"], 995
|
||||
)
|
||||
self.assertEqual(personnel_response.data["headcount"]["payroll_fund"], 1000000)
|
||||
self.assertEqual(
|
||||
personnel_response.data["age_distribution"][0]["age_group"],
|
||||
"under_30",
|
||||
@@ -255,27 +271,38 @@ class OrganizationAnalyticsApiTest(APITestCase):
|
||||
"domestic_equipment",
|
||||
"imported_equipment",
|
||||
"physical_wear_percent",
|
||||
"weighted_wear_percent",
|
||||
"utilization_rate",
|
||||
"avg_shift_work",
|
||||
"equipment_needed",
|
||||
"average_age_years",
|
||||
"commissioned_equipment",
|
||||
"decommissioned_equipment",
|
||||
},
|
||||
)
|
||||
self.assertEqual(response.data["summary"]["total_equipment"], 187)
|
||||
self.assertEqual(response.data["summary"]["physical_wear_percent"], 32.0)
|
||||
self.assertEqual(response.data["summary"]["weighted_wear_percent"], 32.0)
|
||||
self.assertEqual(response.data["summary"]["utilization_rate"], 0.92)
|
||||
self.assertEqual(response.data["summary"]["commissioned_equipment"], 1)
|
||||
self.assertEqual(response.data["summary"]["decommissioned_equipment"], 1)
|
||||
self.assertEqual(
|
||||
response.data["age_distribution"][0]["bucket"], "under_5_years"
|
||||
)
|
||||
self.assertEqual(len(response.data["age_distribution"]), 5)
|
||||
self.assertGreaterEqual(len(response.data["categories"]), 1)
|
||||
self.assertEqual(len(response.data["dynamics_series"]), 3)
|
||||
self.assertEqual(
|
||||
response.data["categories"][0],
|
||||
{
|
||||
"category": "Станочное оборудование",
|
||||
"total_equipment": 1,
|
||||
"domestic_equipment": 1,
|
||||
"imported_equipment": 0,
|
||||
"total_equipment": 54,
|
||||
"domestic_equipment": 31,
|
||||
"imported_equipment": 23,
|
||||
"physical_wear_percent": 28.4,
|
||||
"weighted_wear_percent": 28.4,
|
||||
"utilization_rate": 0.92,
|
||||
"lease_share_itn_percent": None,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -293,8 +320,12 @@ class OrganizationAnalyticsApiTest(APITestCase):
|
||||
self.assertEqual(products_response.data["summary"]["civilian_output_amount"], 7000000)
|
||||
self.assertEqual(products_response.data["summary"]["hightech_output_amount"], 1500000)
|
||||
self.assertEqual(products_response.data["summary"]["rd_volume_amount"], 900000)
|
||||
self.assertEqual(products_response.data["summary"]["shipped_goods_amount"], 18000000)
|
||||
self.assertEqual(len(products_response.data["production_series"]), 1)
|
||||
self.assertEqual(len(products_response.data["sales_series"]), 1)
|
||||
self.assertEqual(len(products_response.data["rd_volume_series"]), 1)
|
||||
self.assertEqual(len(products_response.data["shipped_goods_series"]), 1)
|
||||
self.assertEqual(len(products_response.data["table"]), 1)
|
||||
|
||||
first_production = products_response.data["production_series"][0]
|
||||
first_sales = products_response.data["sales_series"][0]
|
||||
@@ -302,6 +333,59 @@ class OrganizationAnalyticsApiTest(APITestCase):
|
||||
self.assertEqual(first_production["military_output_amount"], 11000000)
|
||||
self.assertEqual(first_sales["military_domestic_amount"], 9000000)
|
||||
self.assertEqual(first_sales["civilian_export_amount"], 2000000)
|
||||
self.assertEqual(
|
||||
products_response.data["rd_volume_series"][0]["rd_volume_amount"], 900000
|
||||
)
|
||||
self.assertEqual(
|
||||
products_response.data["shipped_goods_series"][0]["shipped_goods_amount"],
|
||||
18000000,
|
||||
)
|
||||
self.assertEqual(
|
||||
products_response.data["table"][0]["shipped_goods_amount"], 18000000
|
||||
)
|
||||
|
||||
def test_products_supports_monthly_and_semiannual_frequency(self):
|
||||
FormF1RecordFactory.create(
|
||||
organization=self.organization,
|
||||
report_year=2026,
|
||||
report_quarter=2,
|
||||
military_output_actual=Decimal("12000000.00"),
|
||||
civilian_output_actual=Decimal("6000000.00"),
|
||||
hightech_output_actual=Decimal("1800000.00"),
|
||||
rd_volume_actual=Decimal("600000.00"),
|
||||
military_domestic_actual=Decimal("9600000.00"),
|
||||
military_export_actual=Decimal("2400000.00"),
|
||||
civilian_domestic_actual=Decimal("4200000.00"),
|
||||
civilian_export_actual=Decimal("1800000.00"),
|
||||
)
|
||||
|
||||
semiannual_response = self.client.get(
|
||||
f"/api/v1/organizations/{self.organization.id}/analytics/products/"
|
||||
"?frequency=semiannual&price_mode=actual&report_year=2026"
|
||||
)
|
||||
self.assertEqual(semiannual_response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(semiannual_response.data["frequency"], "semiannual")
|
||||
self.assertEqual(len(semiannual_response.data["production_series"]), 1)
|
||||
self.assertEqual(
|
||||
semiannual_response.data["production_series"][0]["period"], "2026-H1"
|
||||
)
|
||||
self.assertEqual(
|
||||
semiannual_response.data["production_series"][0]["military_output_amount"],
|
||||
23000000,
|
||||
)
|
||||
|
||||
monthly_response = self.client.get(
|
||||
f"/api/v1/organizations/{self.organization.id}/analytics/products/"
|
||||
"?frequency=monthly&price_mode=actual&report_year=2026"
|
||||
)
|
||||
self.assertEqual(monthly_response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(monthly_response.data["frequency"], "monthly")
|
||||
self.assertEqual(len(monthly_response.data["production_series"]), 6)
|
||||
self.assertEqual(monthly_response.data["production_series"][0]["period"], "2026-01")
|
||||
self.assertEqual(
|
||||
monthly_response.data["production_series"][0]["military_output_amount"],
|
||||
3666666,
|
||||
)
|
||||
|
||||
def test_forecast_contract(self):
|
||||
response = self.client.get(
|
||||
@@ -314,15 +398,24 @@ class OrganizationAnalyticsApiTest(APITestCase):
|
||||
self.assertEqual(response.data["scenario"], "base")
|
||||
self.assertEqual(response.data["horizon_years"], 3)
|
||||
self.assertEqual(response.data["base_year"], 2026)
|
||||
self.assertIn("summary", response.data)
|
||||
self.assertEqual(len(response.data["forecast"]), 3)
|
||||
self.assertEqual(response.data["forecast"][0]["year"], 2027)
|
||||
self.assertIn("revenue_amount", response.data["forecast"][0])
|
||||
self.assertIn("net_profit_amount", response.data["forecast"][0])
|
||||
self.assertIn("margin_percent", response.data["forecast"][0])
|
||||
self.assertEqual(len(response.data["table"]), 3)
|
||||
self.assertEqual(len(response.data["productivity_series"]), 3)
|
||||
self.assertEqual(len(response.data["headcount_series"]), 3)
|
||||
self.assertEqual(len(response.data["investment_series"]), 3)
|
||||
self.assertEqual(len(response.data["rd_series"]), 3)
|
||||
self.assertGreaterEqual(len(response.data["risk_factors"]), 1)
|
||||
self.assertGreaterEqual(len(response.data["opportunity_factors"]), 1)
|
||||
self.assertIn("code", response.data["risk_factors"][0])
|
||||
self.assertIn("name", response.data["risk_factors"][0])
|
||||
self.assertIn("impact_level", response.data["risk_factors"][0])
|
||||
self.assertIn("revenue_cagr_percent", response.data["summary"])
|
||||
self.assertIn("horizon_revenue_amount", response.data["summary"])
|
||||
|
||||
def test_risk_profile_contract(self):
|
||||
response = self.client.get(
|
||||
|
||||
@@ -70,6 +70,8 @@ class OrganizationApiTest(APITestCase):
|
||||
response.data["results"][0]["corporation_scope_label"],
|
||||
"Организации ОПК",
|
||||
)
|
||||
self.assertEqual(response.data["results"][0]["registry_category"], "opk")
|
||||
self.assertEqual(response.data["results"][0]["registry_category_label"], "ОПК")
|
||||
self.assertEqual(response.data["results"][0]["short_name"], "АО «Альфа»")
|
||||
|
||||
def test_detail_includes_active_registries(self):
|
||||
@@ -111,6 +113,8 @@ class OrganizationApiTest(APITestCase):
|
||||
response.data["corporation_scope_label"],
|
||||
"Госкорпорация «Роскосмос»",
|
||||
)
|
||||
self.assertEqual(response.data["registry_category"], "other")
|
||||
self.assertEqual(response.data["registry_category_label"], "Прочие")
|
||||
self.assertEqual(
|
||||
response.data["general_director"]["full_name"],
|
||||
"Иванов Иван Иванович",
|
||||
@@ -189,6 +193,53 @@ class OrganizationApiTest(APITestCase):
|
||||
self.assertEqual(len(response.data["results"]), 1)
|
||||
self.assertEqual(response.data["results"][0]["id"], str(rosatom_org.id))
|
||||
|
||||
def test_registry_category_filters_support_snake_and_camel_case(self):
|
||||
opk_org = OrganizationFactory.create(name="АО ОПК")
|
||||
goz_org = OrganizationFactory.create(name="АО ГОЗ", in_275_fz_registry=True)
|
||||
other_org = OrganizationFactory.create(name="АО Прочие")
|
||||
|
||||
opk_register = Register.objects.create(name="Реестр предприятий ОПК")
|
||||
goz_register = Register.objects.create(name="Реестр госкорпорации Росатом ГОЗ")
|
||||
upload = RegisterUpload.objects.create(
|
||||
registry=opk_register,
|
||||
actual_date=date(2026, 4, 1),
|
||||
file_name="registry-category.xlsx",
|
||||
file_hash="registry-category-hash",
|
||||
rows_count=2,
|
||||
)
|
||||
goz_upload = RegisterUpload.objects.create(
|
||||
registry=goz_register,
|
||||
actual_date=date(2026, 4, 1),
|
||||
file_name="registry-category-goz.xlsx",
|
||||
file_hash="registry-category-goz-hash",
|
||||
rows_count=1,
|
||||
)
|
||||
|
||||
RegistryMembershipPeriod.objects.create(
|
||||
registry=opk_register,
|
||||
organization=opk_org,
|
||||
started_at=date(2026, 4, 1),
|
||||
started_by_upload=upload,
|
||||
)
|
||||
RegistryMembershipPeriod.objects.create(
|
||||
registry=goz_register,
|
||||
organization=goz_org,
|
||||
started_at=date(2026, 4, 1),
|
||||
started_by_upload=goz_upload,
|
||||
)
|
||||
|
||||
response = self.client.get("/api/v1/organizations/?registry_category=goz")
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual([item["id"] for item in response.data["results"]], [str(goz_org.id)])
|
||||
|
||||
response = self.client.get("/api/v1/organizations/?registryCategory=opk")
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual([item["id"] for item in response.data["results"]], [str(opk_org.id)])
|
||||
|
||||
response = self.client.get("/api/v1/organizations/?registry_category=other")
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertIn(str(other_org.id), [item["id"] for item in response.data["results"]])
|
||||
|
||||
|
||||
class OrganizationDictionaryApiTest(APITestCase):
|
||||
"""Tests for organization dictionaries endpoints."""
|
||||
|
||||
@@ -17,6 +17,7 @@ from apps.user.serializers import (
|
||||
)
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.test import TestCase
|
||||
from django.utils import timezone
|
||||
from faker import Faker
|
||||
|
||||
from .factories import ProfileFactory, UserFactory
|
||||
@@ -179,7 +180,7 @@ class UserManagementSerializerTest(TestCase):
|
||||
self.assertEqual(serializer.data["last_name"], "")
|
||||
|
||||
def test_metric_fields_are_derived_from_latest_job(self):
|
||||
now = datetime(2026, 4, 14, 10, 0, 0)
|
||||
now = timezone.make_aware(datetime(2026, 4, 14, 10, 0, 0))
|
||||
latest_job = BackgroundJob.objects.create(
|
||||
task_id="admin-management-latest",
|
||||
task_name="apps.forms.process",
|
||||
|
||||
@@ -7,6 +7,7 @@ from apps.user.models import Profile
|
||||
from apps.user.services import UserService
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
from faker import Faker
|
||||
from rest_framework import status
|
||||
from rest_framework.test import APITestCase
|
||||
@@ -179,7 +180,7 @@ class AdminUsersManagementViewTest(APITestCase):
|
||||
last_name="Петров",
|
||||
)
|
||||
|
||||
now = datetime(2026, 4, 14, 10, 0, 0)
|
||||
now = timezone.make_aware(datetime(2026, 4, 14, 10, 0, 0))
|
||||
completed_job = now + timedelta(minutes=3)
|
||||
failed_job = now - timedelta(minutes=10)
|
||||
BackgroundJob.objects.create(
|
||||
|
||||
Reference in New Issue
Block a user