fix: align reporting forms and demo analytics
All checks were successful
CI/CD Pipeline / Code Quality Checks (push) Successful in 2m21s
CI/CD Pipeline / Run Tests (push) Successful in 4m23s
CI/CD Pipeline / Build and Push Dev Images (push) Successful in 32s
CI/CD Pipeline / Deploy Dev via Compose (push) Successful in 52s

This commit is contained in:
2026-07-28 11:07:47 +02:00
parent ff0b2bdfca
commit df0f41b9c0
31 changed files with 1627 additions and 328 deletions

View File

@@ -41,6 +41,8 @@ class OrganizationAnalyticsApiTest(APITestCase):
organization=self.organization,
report_year=2026,
report_quarter=1,
report_month=6,
avg_employees=1050,
avg_payroll_employees=995,
payroll_fund=Decimal("1000000.00"),
military_output_actual=Decimal("11000000.00"),
@@ -56,6 +58,8 @@ class OrganizationAnalyticsApiTest(APITestCase):
organization=self.organization,
report_year=2025,
report_quarter=1,
report_month=12,
avg_employees=1020,
avg_payroll_employees=970,
payroll_fund=Decimal("900000.00"),
military_output_actual=Decimal("9000000.00"),
@@ -88,11 +92,19 @@ class OrganizationAnalyticsApiTest(APITestCase):
FormF3RecordFactory.create(
organization=self.organization,
report_year=2026,
avg_employees=1050,
production_workers=620,
engineering_workers=210,
administrative_workers=220,
workers_needed=35,
avg_employees=None,
production_workers=None,
engineering_workers=None,
administrative_workers=None,
workers_needed=None,
average_employee_age=Decimal("42.00"),
employees_under_20=4,
employees_20_29=18,
employees_30_39=28,
employees_40_49=26,
employees_50_59=17,
employees_over_60=7,
machine_tools_and_equipment=46,
total_equipment=187,
domestic_equipment=91,
imported_equipment=96,
@@ -109,14 +121,16 @@ class OrganizationAnalyticsApiTest(APITestCase):
FormF3RecordFactory.create(
organization=self.organization,
report_year=2025,
avg_employees=1020,
avg_employees=None,
average_employee_age=Decimal("41.00"),
)
FormF4RecordFactory.create(
organization=self.organization,
report_year=2026,
revenue_rsbu=Decimal("1100000000.00"),
net_profit_rsbu=Decimal("320000000.00"),
ebitda_rsbu=Decimal("480000000.00"),
report_half_year=2,
revenue_rsbu=Decimal("477807.00"),
net_profit_rsbu=Decimal("442414.00"),
ebitda_rsbu=Decimal("138564.00"),
gross_profit_rsbu=Decimal("520000000.00"),
operating_profit_rsbu=Decimal("300000000.00"),
net_debt_rsbu=Decimal("200000000.00"),
@@ -131,9 +145,10 @@ class OrganizationAnalyticsApiTest(APITestCase):
FormF4RecordFactory.create(
organization=self.organization,
report_year=2025,
revenue_rsbu=Decimal("980000000.00"),
net_profit_rsbu=Decimal("250000000.00"),
ebitda_rsbu=Decimal("410000000.00"),
report_half_year=2,
revenue_rsbu=Decimal("450000.00"),
net_profit_rsbu=Decimal("400000.00"),
ebitda_rsbu=Decimal("120000.00"),
ros=Decimal("21.00"),
roa=Decimal("10.50"),
roe=Decimal("14.10"),
@@ -152,14 +167,31 @@ class OrganizationAnalyticsApiTest(APITestCase):
organization=self.organization,
report_year=2026,
category="Станочное оборудование",
total_equipment=54,
domestic_equipment=31,
imported_equipment=23,
total_equipment=187,
domestic_equipment=None,
imported_equipment=96,
commissioned_equipment=12,
decommissioned_equipment=7,
age_under_5=70,
age_under_5_imported=35,
age_under_5_utilization_rate=Decimal("92.00"),
age_under_5_lease_share_itn_percent=Decimal("20.00"),
age_5_10=41,
age_5_10_imported=21,
age_5_10_utilization_rate=Decimal("92.00"),
age_5_10_lease_share_itn_percent=Decimal("20.00"),
age_10_15=33,
age_10_15_imported=17,
age_10_15_utilization_rate=Decimal("92.00"),
age_10_15_lease_share_itn_percent=Decimal("20.00"),
age_15_20=22,
age_15_20_imported=12,
age_15_20_utilization_rate=Decimal("92.00"),
age_15_20_lease_share_itn_percent=Decimal("20.00"),
age_over_20=21,
age_over_20_imported=11,
age_over_20_utilization_rate=Decimal("92.00"),
age_over_20_lease_share_itn_percent=Decimal("20.00"),
physical_wear_percent=Decimal("28.40"),
utilization_rate=Decimal("92.00"),
avg_shift_work=Decimal("1.80"),
@@ -231,14 +263,24 @@ class OrganizationAnalyticsApiTest(APITestCase):
self.assertEqual(response.data["organization_id"], str(self.organization.id))
self.assertEqual(response.data["group"], "efficiency")
self.assertEqual(response.data["periods"], [2025, 2026])
self.assertEqual(
response.data["report_periods"],
[
{"year": 2025, "report_half_year": 2},
{"year": 2026, "report_half_year": 2},
],
)
self.assertEqual(
response.data["kpis"].keys(),
{"revenue", "ebitda", "net_profit", "revenue_per_employee"},
)
self.assertGreater(response.data["kpis"]["revenue_per_employee"]["value"], 0)
self.assertEqual(response.data["kpis"]["revenue"]["value"], 477807)
self.assertEqual(response.data["kpis"]["net_profit"]["value"], 442414)
self.assertEqual(response.data["kpis"]["ebitda"]["value"], 138564)
self.assertEqual(response.data["kpis"]["revenue_per_employee"]["value"], 455)
self.assertEqual(
response.data["kpis"]["revenue_per_employee"]["unit"],
"rub_per_employee",
"rub_thousands_per_employee",
)
for kpi in response.data["kpis"].values():
self.assertIn("previous_value", kpi)
@@ -251,7 +293,7 @@ class OrganizationAnalyticsApiTest(APITestCase):
self.assertIn("unit", series)
self.assertIn("points", series)
expected_unit = (
"rub_per_employee"
"rub_thousands_per_employee"
if series["metric"] == "revenue_per_employee"
else "rub_thousands"
)
@@ -283,10 +325,43 @@ class OrganizationAnalyticsApiTest(APITestCase):
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data["periods"], [])
self.assertEqual(response.data["report_periods"], [])
self.assertEqual(response.data["kpis"], {})
self.assertFalse(response.data["data_available"])
self.assertEqual(response.data["message"], "Данные отсутствуют")
def test_economics_normalizes_f2_rubles_to_thousands_without_f4(self):
organization = OrganizationFactory.create()
FormF1RecordFactory.create(
organization=organization,
report_year=2026,
report_month=6,
avg_employees=100,
)
FormF2RecordFactory.create(
organization=organization,
report_year=2026,
revenue=Decimal("1200000.00"),
net_profit=Decimal("300000.00"),
ebitda=Decimal("480000.00"),
)
response = self.client.get(
f"/api/v1/organizations/{organization.id}/analytics/economics/"
"?group=efficiency&from_year=2026&to_year=2026"
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data["kpis"]["revenue"]["value"], 1200)
self.assertEqual(
response.data["kpis"]["revenue_per_employee"]["value"],
12,
)
self.assertEqual(
response.data["kpis"]["revenue"]["unit"],
"rub_thousands",
)
def test_personnel_contract(self):
personnel_response = self.client.get(
f"/api/v1/organizations/{self.organization.id}/analytics/personnel/"
@@ -297,6 +372,7 @@ class OrganizationAnalyticsApiTest(APITestCase):
personnel_response.data["organization_id"], str(self.organization.id)
)
self.assertEqual(personnel_response.data["report_year"], 2026)
self.assertEqual(personnel_response.data["report_month"], 6)
self.assertEqual(
personnel_response.data["headcount"]["average_employees"],
1050,
@@ -306,6 +382,7 @@ class OrganizationAnalyticsApiTest(APITestCase):
set(personnel_response.data["history"][0]),
{
"year",
"report_month",
"average_employees",
"avg_payroll_employees",
"average_age",
@@ -313,15 +390,22 @@ class OrganizationAnalyticsApiTest(APITestCase):
},
)
self.assertEqual(len(personnel_response.data["age_distribution"]), 3)
self.assertIn("average_age", personnel_response.data)
self.assertEqual(personnel_response.data["average_age"], 42.0)
self.assertEqual(
personnel_response.data["headcount"]["avg_payroll_employees"], 995
)
self.assertEqual(personnel_response.data["headcount"]["payroll_fund"], 1000000)
self.assertEqual(
personnel_response.data["headcount"]["payroll_fund"], 1000000000
)
self.assertIsNone(personnel_response.data["headcount"]["production_workers"])
self.assertEqual(
personnel_response.data["age_distribution"][0]["age_group"],
"under_30",
)
self.assertEqual(
personnel_response.data["age_distribution"][0]["employees_count"],
22,
)
self.assertIn("employees_count", personnel_response.data["age_distribution"][0])
def test_yearly_analytics_use_latest_available_year_when_requested_year_is_empty(
@@ -374,15 +458,19 @@ class OrganizationAnalyticsApiTest(APITestCase):
},
)
self.assertEqual(response.data["summary"]["total_equipment"], 187)
self.assertEqual(response.data["summary"]["machine_tools_and_equipment"], 46)
self.assertIsNone(response.data["summary"]["domestic_equipment"])
self.assertEqual(response.data["summary"]["imported_equipment"], 96)
self.assertEqual(response.data["summary"]["physical_wear_percent"], 32.0)
self.assertEqual(response.data["summary"]["weighted_wear_percent"], 32.0)
self.assertIsNone(response.data["summary"]["weighted_wear_percent"])
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["summary"]["commissioned_equipment"], 12)
self.assertEqual(response.data["summary"]["decommissioned_equipment"], 7)
self.assertEqual(
response.data["age_distribution"][0]["bucket"], "under_5_years"
)
self.assertEqual(len(response.data["age_distribution"]), 5)
self.assertEqual(response.data["age_distribution"][0]["imported_equipment"], 35)
self.assertEqual(
set(response.data["age_distribution"][0]),
{
@@ -400,13 +488,13 @@ class OrganizationAnalyticsApiTest(APITestCase):
response.data["categories"][0],
{
"category": "Станочное оборудование",
"total_equipment": 54,
"domestic_equipment": 31,
"imported_equipment": 23,
"physical_wear_percent": 28.4,
"weighted_wear_percent": 28.4,
"total_equipment": 187,
"domestic_equipment": None,
"imported_equipment": 96,
"physical_wear_percent": None,
"weighted_wear_percent": None,
"utilization_rate": 0.92,
"lease_share_itn_percent": None,
"lease_share_itn_percent": 20.0,
},
)