feat: import additional exchange sections
All checks were successful
CI/CD Pipeline / Code Quality Checks (push) Successful in 2m41s
CI/CD Pipeline / Run Tests (push) Successful in 2m47s
CI/CD Pipeline / Build Docker Images (push) Successful in 2m24s
CI/CD Pipeline / Push to Gitea Registry (push) Successful in 0s
CI/CD Pipeline / Deploy to Server (push) Successful in 0s
All checks were successful
CI/CD Pipeline / Code Quality Checks (push) Successful in 2m41s
CI/CD Pipeline / Run Tests (push) Successful in 2m47s
CI/CD Pipeline / Build Docker Images (push) Successful in 2m24s
CI/CD Pipeline / Push to Gitea Registry (push) Successful in 0s
CI/CD Pipeline / Deploy to Server (push) Successful in 0s
This commit is contained in:
@@ -16,7 +16,11 @@ from apps.exchange.models import ExchangeDeliveryChannel, ExchangePackageImport
|
||||
from apps.exchange.services import ExchangePackageImportService
|
||||
from apps.external_data.models import (
|
||||
ArbitrationCase,
|
||||
BankruptcyProcedure,
|
||||
DefenseUnreliableSupplier,
|
||||
IndustrialProduct,
|
||||
InformationSecurityRegistryEntry,
|
||||
LaborVacancy,
|
||||
ProsecutorCheck,
|
||||
PublicProcurement,
|
||||
)
|
||||
@@ -194,6 +198,53 @@ def build_exchange_payload() -> dict[str, list[dict[str, object]]]:
|
||||
"decision_date": "2026-03-25",
|
||||
}
|
||||
],
|
||||
"bankruptcy_procedures": [
|
||||
{
|
||||
"organization_inn": "7707083893",
|
||||
"external_id": "fedresurs:001",
|
||||
"message_type": "Сообщение о намерении",
|
||||
"message_date": "2026-03-26",
|
||||
"case_number": "А40-555/2026",
|
||||
"status": "published",
|
||||
"source_url": "https://fedresurs.ru/message/001",
|
||||
}
|
||||
],
|
||||
"defense_unreliable_suppliers": [
|
||||
{
|
||||
"organization_inn": "7707083893",
|
||||
"external_id": "fas-goz:001",
|
||||
"registry_source": "fas_goz",
|
||||
"registry_number": "ГОЗ-001",
|
||||
"supplier_name": "АО Альфа Обновленная",
|
||||
"reason": "Уклонение от заключения контракта",
|
||||
"included_at": "2026-02-20",
|
||||
"status": "active",
|
||||
"source_url": "https://fas.gov.ru/register/001",
|
||||
}
|
||||
],
|
||||
"information_security_registries": [
|
||||
{
|
||||
"organization_inn": "7707083893",
|
||||
"external_id": "fstec:001",
|
||||
"registry_name": "Реестр лицензий ФСТЭК",
|
||||
"presence_status": "present",
|
||||
"entry_number": "77-001234",
|
||||
"issued_at": "2026-01-10",
|
||||
"expires_at": "2027-01-10",
|
||||
}
|
||||
],
|
||||
"labor_vacancies": [
|
||||
{
|
||||
"organization_inn": "7707083893",
|
||||
"external_id": "trudvsem:001",
|
||||
"vacancy_source": "trudvsem",
|
||||
"title": "Инженер-испытатель",
|
||||
"status": "open",
|
||||
"published_at": "2026-04-01",
|
||||
"salary_amount": "175000.00",
|
||||
"source_url": "https://trudvsem.ru/vacancy/001",
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
@@ -234,6 +285,23 @@ class ExchangePackageApiTest(APITestCase):
|
||||
self.assertEqual(ProsecutorCheck.objects.count(), 1)
|
||||
self.assertEqual(PublicProcurement.objects.count(), 1)
|
||||
self.assertEqual(ArbitrationCase.objects.count(), 1)
|
||||
self.assertEqual(BankruptcyProcedure.objects.count(), 1)
|
||||
self.assertEqual(DefenseUnreliableSupplier.objects.count(), 1)
|
||||
self.assertEqual(InformationSecurityRegistryEntry.objects.count(), 1)
|
||||
self.assertEqual(LaborVacancy.objects.count(), 1)
|
||||
self.assertEqual(
|
||||
response.data["result"]["bankruptcy_procedures"]["created"],
|
||||
1,
|
||||
)
|
||||
self.assertEqual(
|
||||
response.data["result"]["defense_unreliable_suppliers"]["created"],
|
||||
1,
|
||||
)
|
||||
self.assertEqual(
|
||||
response.data["result"]["information_security_registries"]["created"],
|
||||
1,
|
||||
)
|
||||
self.assertEqual(response.data["result"]["labor_vacancies"]["created"], 1)
|
||||
|
||||
organization = Organization.objects.get(inn="7707083893")
|
||||
self.assertEqual(organization.name, "АО Альфа Обновленная")
|
||||
|
||||
@@ -3,10 +3,13 @@
|
||||
import factory
|
||||
from apps.external_data.models import (
|
||||
ArbitrationCase,
|
||||
BankruptcyProcedure,
|
||||
DefenseUnreliableSupplier,
|
||||
IndustrialProduct,
|
||||
InformationSecurityRegistryEntry,
|
||||
LaborVacancy,
|
||||
ProsecutorCheck,
|
||||
PublicProcurement,
|
||||
InformationSecurityRegistryEntry,
|
||||
)
|
||||
from faker import Faker
|
||||
|
||||
@@ -71,9 +74,7 @@ class ArbitrationCaseFactory(factory.django.DjangoModelFactory):
|
||||
decision_date = factory.LazyAttribute(lambda _: fake.date_this_year())
|
||||
|
||||
|
||||
class InformationSecurityRegistryEntryFactory(
|
||||
factory.django.DjangoModelFactory
|
||||
):
|
||||
class InformationSecurityRegistryEntryFactory(factory.django.DjangoModelFactory):
|
||||
class Meta:
|
||||
model = InformationSecurityRegistryEntry
|
||||
|
||||
@@ -83,3 +84,45 @@ class InformationSecurityRegistryEntryFactory(
|
||||
entry_number = "77-001234"
|
||||
issued_at = factory.LazyAttribute(lambda _: fake.date_this_year())
|
||||
expires_at = factory.LazyAttribute(lambda _: fake.date_this_year())
|
||||
|
||||
|
||||
class BankruptcyProcedureFactory(factory.django.DjangoModelFactory):
|
||||
class Meta:
|
||||
model = BankruptcyProcedure
|
||||
|
||||
organization = factory.SubFactory(OrganizationFactory)
|
||||
external_id = factory.Sequence(lambda n: f"fedresurs:{n}")
|
||||
message_type = "Сообщение о намерении"
|
||||
message_date = factory.LazyAttribute(lambda _: fake.date_this_year())
|
||||
case_number = factory.Sequence(lambda n: f"А40-{20_000 + n}/2026")
|
||||
status = "published"
|
||||
source_url = factory.Sequence(lambda n: f"https://fedresurs.ru/message/{n}")
|
||||
|
||||
|
||||
class DefenseUnreliableSupplierFactory(factory.django.DjangoModelFactory):
|
||||
class Meta:
|
||||
model = DefenseUnreliableSupplier
|
||||
|
||||
organization = factory.SubFactory(OrganizationFactory)
|
||||
external_id = factory.Sequence(lambda n: f"fas-goz:{n}")
|
||||
registry_source = "fas_goz"
|
||||
registry_number = factory.Sequence(lambda n: f"ГОЗ-{n:04d}")
|
||||
supplier_name = factory.LazyAttribute(lambda obj: obj.organization.name)
|
||||
reason = "Уклонение от заключения контракта"
|
||||
included_at = factory.LazyAttribute(lambda _: fake.date_this_year())
|
||||
status = "active"
|
||||
source_url = factory.Sequence(lambda n: f"https://fas.gov.ru/register/{n}")
|
||||
|
||||
|
||||
class LaborVacancyFactory(factory.django.DjangoModelFactory):
|
||||
class Meta:
|
||||
model = LaborVacancy
|
||||
|
||||
organization = factory.SubFactory(OrganizationFactory)
|
||||
external_id = factory.Sequence(lambda n: f"trudvsem:{n}")
|
||||
vacancy_source = "trudvsem"
|
||||
title = "Инженер-испытатель"
|
||||
status = "open"
|
||||
published_at = factory.LazyAttribute(lambda _: fake.date_this_year())
|
||||
salary_amount = "175000.00"
|
||||
source_url = factory.Sequence(lambda n: f"https://trudvsem.ru/vacancy/{n}")
|
||||
|
||||
@@ -10,10 +10,13 @@ from rest_framework.test import APITestCase
|
||||
|
||||
from tests.apps.external_data.factories import (
|
||||
ArbitrationCaseFactory,
|
||||
BankruptcyProcedureFactory,
|
||||
DefenseUnreliableSupplierFactory,
|
||||
IndustrialProductFactory,
|
||||
InformationSecurityRegistryEntryFactory,
|
||||
LaborVacancyFactory,
|
||||
ProsecutorCheckFactory,
|
||||
PublicProcurementFactory,
|
||||
InformationSecurityRegistryEntryFactory,
|
||||
)
|
||||
from tests.apps.organization.factories import OrganizationFactory
|
||||
from tests.apps.user.factories import UserFactory
|
||||
@@ -116,3 +119,47 @@ class ExternalDataApiTest(APITestCase):
|
||||
self.assertEqual(result["presence_status"], "present")
|
||||
self.assertIn("registry_name", result)
|
||||
self.assertIn("entry_number", result)
|
||||
|
||||
def test_additional_exchange_sections_are_exposed(self):
|
||||
BankruptcyProcedureFactory.create(
|
||||
organization=self.organization,
|
||||
message_type="Сообщение о намерении",
|
||||
message_date=date(2026, 3, 26),
|
||||
)
|
||||
DefenseUnreliableSupplierFactory.create(
|
||||
organization=self.organization,
|
||||
registry_source="fas_goz",
|
||||
included_at=date(2026, 2, 20),
|
||||
)
|
||||
LaborVacancyFactory.create(
|
||||
organization=self.organization,
|
||||
vacancy_source="trudvsem",
|
||||
published_at=date(2026, 4, 1),
|
||||
)
|
||||
BankruptcyProcedureFactory.create(organization=self.other_organization)
|
||||
DefenseUnreliableSupplierFactory.create(organization=self.other_organization)
|
||||
LaborVacancyFactory.create(organization=self.other_organization)
|
||||
|
||||
bankruptcy_response = self.client.get(
|
||||
f"/api/v1/bankruptcy-procedures/?organization={self.organization.id}"
|
||||
"&message_date_from=2026-01-01&message_date_to=2026-12-31"
|
||||
)
|
||||
defense_response = self.client.get(
|
||||
f"/api/v1/defense-unreliable-suppliers/?organization={self.organization.id}"
|
||||
"®istry_source=fas_goz"
|
||||
)
|
||||
vacancies_response = self.client.get(
|
||||
f"/api/v1/labor-vacancies/?organization={self.organization.id}"
|
||||
"&vacancy_source=trudvsem"
|
||||
)
|
||||
|
||||
self.assertEqual(bankruptcy_response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(defense_response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(vacancies_response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(bankruptcy_response.data["count"], 1)
|
||||
self.assertEqual(defense_response.data["count"], 1)
|
||||
self.assertEqual(vacancies_response.data["count"], 1)
|
||||
self.assertEqual(
|
||||
vacancies_response.data["results"][0]["title"],
|
||||
"Инженер-испытатель",
|
||||
)
|
||||
|
||||
@@ -193,19 +193,18 @@ class FormUploadContractsApiTest(APITestCase):
|
||||
|
||||
def test_upload_processing_error_contract(self):
|
||||
for _, case in self.CASES.items():
|
||||
with self.subTest(form=case["form"]):
|
||||
with patch(
|
||||
case["parse_target"],
|
||||
side_effect=RuntimeError("parse failed"),
|
||||
) as parse_mock:
|
||||
response = self.client.post(
|
||||
case["url"],
|
||||
self._build_payload(case["payload"], file_size=256),
|
||||
format="multipart",
|
||||
)
|
||||
with self.subTest(form=case["form"]), patch(
|
||||
case["parse_target"],
|
||||
side_effect=RuntimeError("parse failed"),
|
||||
) as parse_mock:
|
||||
response = self.client.post(
|
||||
case["url"],
|
||||
self._build_payload(case["payload"], file_size=256),
|
||||
format="multipart",
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
self.assertEqual(response.data["error_code"], "processing_error")
|
||||
self.assertEqual(response.data["error_message"], "parse failed")
|
||||
self.assertEqual(response.data["details"], [])
|
||||
parse_mock.assert_called_once()
|
||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
self.assertEqual(response.data["error_code"], "processing_error")
|
||||
self.assertEqual(response.data["error_message"], "parse failed")
|
||||
self.assertEqual(response.data["details"], [])
|
||||
parse_mock.assert_called_once()
|
||||
|
||||
@@ -188,9 +188,18 @@ class OrganizationAnalyticsApiTest(APITestCase):
|
||||
self.assertEqual(response.data["insurance_contributions"]["amount"], 302000)
|
||||
self.assertEqual(response.data["organization_id"], str(self.organization.id))
|
||||
self.assertEqual(response.data["report_period"], {"year": 2026, "quarter": 1})
|
||||
self.assertEqual(set(response.data["revenue"]), {"amount", "previous_amount", "delta_percent"})
|
||||
self.assertEqual(set(response.data["net_profit"]), {"amount", "previous_amount", "delta_percent"})
|
||||
self.assertEqual(set(response.data["taxes_paid"]), {"amount", "previous_amount", "delta_percent"})
|
||||
self.assertEqual(
|
||||
set(response.data["revenue"]),
|
||||
{"amount", "previous_amount", "delta_percent"},
|
||||
)
|
||||
self.assertEqual(
|
||||
set(response.data["net_profit"]),
|
||||
{"amount", "previous_amount", "delta_percent"},
|
||||
)
|
||||
self.assertEqual(
|
||||
set(response.data["taxes_paid"]),
|
||||
{"amount", "previous_amount", "delta_percent"},
|
||||
)
|
||||
self.assertEqual(
|
||||
set(response.data["insurance_contributions"]),
|
||||
{"amount", "previous_amount", "delta_percent"},
|
||||
@@ -228,7 +237,12 @@ class OrganizationAnalyticsApiTest(APITestCase):
|
||||
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()))
|
||||
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(
|
||||
@@ -236,7 +250,9 @@ class OrganizationAnalyticsApiTest(APITestCase):
|
||||
"?report_year=2026&history_years=2"
|
||||
)
|
||||
self.assertEqual(personnel_response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(personnel_response.data["organization_id"], str(self.organization.id))
|
||||
self.assertEqual(
|
||||
personnel_response.data["organization_id"], str(self.organization.id)
|
||||
)
|
||||
self.assertEqual(personnel_response.data["report_year"], 2026)
|
||||
self.assertEqual(
|
||||
personnel_response.data["headcount"]["average_employees"],
|
||||
@@ -312,15 +328,25 @@ class OrganizationAnalyticsApiTest(APITestCase):
|
||||
"?frequency=quarterly&price_mode=actual&report_year=2026"
|
||||
)
|
||||
self.assertEqual(products_response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(products_response.data["organization_id"], str(self.organization.id))
|
||||
self.assertEqual(
|
||||
products_response.data["organization_id"], str(self.organization.id)
|
||||
)
|
||||
self.assertEqual(products_response.data["report_year"], 2026)
|
||||
self.assertEqual(products_response.data["frequency"], "quarterly")
|
||||
self.assertEqual(products_response.data["price_mode"], "actual")
|
||||
self.assertEqual(products_response.data["summary"]["military_output_amount"], 11000000)
|
||||
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"]["military_output_amount"], 11000000
|
||||
)
|
||||
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(
|
||||
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)
|
||||
@@ -381,7 +407,9 @@ class OrganizationAnalyticsApiTest(APITestCase):
|
||||
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]["period"], "2026-01"
|
||||
)
|
||||
self.assertEqual(
|
||||
monthly_response.data["production_series"][0]["military_output_amount"],
|
||||
3666666,
|
||||
|
||||
@@ -230,15 +230,21 @@ class OrganizationApiTest(APITestCase):
|
||||
|
||||
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)])
|
||||
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)])
|
||||
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"]])
|
||||
self.assertIn(
|
||||
str(other_org.id), [item["id"] for item in response.data["results"]]
|
||||
)
|
||||
|
||||
|
||||
class OrganizationDictionaryApiTest(APITestCase):
|
||||
|
||||
Reference in New Issue
Block a user