feat: export organizations to state corp v3
All checks were successful
CI/CD Pipeline / Quality Gate (push) Successful in 30s
CI/CD Pipeline / Build and Push Images (push) Successful in 41s
CI/CD Pipeline / Internal Notify (push) Successful in 0s
CI/CD Pipeline / Deploy Dev via Compose (push) Successful in 19s

This commit is contained in:
2026-06-13 14:07:40 +02:00
parent 90d6bb90bd
commit f95ba3f56a
11 changed files with 513 additions and 218 deletions

View File

@@ -7,6 +7,8 @@ import hashlib
import json
import struct
import zlib
from datetime import date
from decimal import Decimal
from io import BytesIO
from unittest.mock import Mock, patch
from zipfile import ZipFile
@@ -18,9 +20,9 @@ from apps.parsers.models import (
GenericParserRecord,
ParserLoadLog,
)
from apps.registers.models import Register
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from django.test import TestCase, override_settings
from organizations.models import Organization
from tests.apps.parsers.factories import (
IndustrialCertificateRecordFactory,
@@ -29,11 +31,6 @@ from tests.apps.parsers.factories import (
ManufacturerRecordFactory,
ProcurementRecordFactory,
)
from tests.apps.registers.factories import (
OrganizationFactory,
RegisterFactory,
RegistryMembershipPeriodFactory,
)
def _b64url_decode(value: str) -> bytes:
@@ -71,24 +68,59 @@ class StateCorpExchangeServiceTest(TestCase):
"""Verify package compatibility with state-corp receiver contract."""
def test_build_package_contains_expected_payload(self):
registry = Register.objects.get(name="Реестр госкорпорации Росатом")
organization = OrganizationFactory.create(
mn_inn=7707083893,
mn_ogrn=1027700132195,
organization = Organization.objects.create(
rn=1001,
name="АО Альфа",
full_name="Акционерное общество Альфа",
short_name="АО Альфа",
pn_name="АО Альфа",
in_kpp=770701001,
mn_okpo="12345678",
)
RegistryMembershipPeriodFactory.create(
registry=registry,
organization=organization,
started_at="2026-01-01",
ended_at=None,
pn_name_en="Alpha JSC",
inn="7707083893",
ogrn="1027700132195",
kpp="770701001",
okpo="12345678",
gk_code="1",
gk_name="Росатом",
in_korp_code="head",
in_korp_name="Головная организация",
filial=".F.",
is_branch=False,
registration_date=date(2024, 2, 15),
create_date="2024",
organizational_legal_form="12267",
organizational_legal_form1="Акционерное общество",
ownership_form="61",
ownership_form1="Федеральная собственность",
authorized_capital=Decimal("1500000.50"),
legal_address="г. Москва, ул. Тверская, д. 1",
business_act_cod="26.11",
business_activity="Производство электронных компонентов",
general_director="Иванов Иван Иванович",
general_director_tax_id="123456789012",
uk="АО Управляющая компания",
inn_uk="7707000000",
appointment_date=date(2025, 1, 10),
cf_fl_rn="director-001",
akc_fs="100",
akc_sf="0",
re_za=True,
re_zasf=False,
goz_participation=True,
opk_registry_membership=True,
ropk_num="РОПК-001",
ropk_razdel_num="opk-1",
ropk_razdel_name="Росатом ОПК",
min="Минпромторг",
dep="Департамент радиоэлектроники",
otr="Радиоэлектроника",
integrated_structure="Интегрированная структура Альфа",
state_sector_code="10",
state_sector_name="Государственный сектор",
)
IndustrialCertificateRecordFactory.create(
inn=str(organization.mn_inn),
ogrn=str(organization.mn_ogrn),
organisation_name=organization.pn_name,
inn=organization.inn,
ogrn=organization.ogrn,
organisation_name=organization.name,
certificate_number="cert-001",
issue_date="2026-01-10",
issue_date_normalized="2026-01-10",
@@ -97,23 +129,23 @@ class StateCorpExchangeServiceTest(TestCase):
certificate_file_url="https://minpromtorg.gov.ru/cert/001",
)
ManufacturerRecordFactory.create(
inn=str(organization.mn_inn),
ogrn=str(organization.mn_ogrn),
full_legal_name=organization.pn_name,
inn=organization.inn,
ogrn=organization.ogrn,
full_legal_name=organization.name,
address="г. Москва, ул. Тверская, д. 1",
)
IndustrialProductRecordFactory.create(
inn=str(organization.mn_inn),
ogrn=str(organization.mn_ogrn),
full_organisation_name=organization.pn_name,
inn=organization.inn,
ogrn=organization.ogrn,
full_organisation_name=organization.name,
registry_number="prod-001",
product_name="Система связи М-1",
product_model="Связь",
)
InspectionRecordFactory.create(
inn=str(organization.mn_inn),
ogrn=str(organization.mn_ogrn),
organisation_name=organization.pn_name,
inn=organization.inn,
ogrn=organization.ogrn,
organisation_name=organization.name,
registration_number="insp-001",
control_authority="Минпромторг",
legal_basis="294-ФЗ",
@@ -122,9 +154,9 @@ class StateCorpExchangeServiceTest(TestCase):
status="active",
)
ProcurementRecordFactory.create(
customer_inn=str(organization.mn_inn),
customer_ogrn=str(organization.mn_ogrn),
customer_name=organization.pn_name,
customer_inn=organization.inn,
customer_ogrn=organization.ogrn,
customer_name=organization.name,
purchase_number="purchase-001",
purchase_name="Поставка оборудования",
law_type="223-ФЗ",
@@ -137,7 +169,7 @@ class StateCorpExchangeServiceTest(TestCase):
)
financial_report = FinancialReport.objects.create(
external_id="fin-001",
ogrn=str(organization.mn_ogrn),
ogrn=organization.ogrn,
file_name="fin_001_1027700132195.xlsx",
file_hash="f" * 64,
load_batch=1,
@@ -157,8 +189,8 @@ class StateCorpExchangeServiceTest(TestCase):
source=ParserLoadLog.Source.PROCUREMENTS_44FZ,
load_batch=1,
external_id="purchase-generic-001",
inn=str(organization.mn_inn),
ogrn=str(organization.mn_ogrn),
inn=organization.inn,
ogrn=organization.ogrn,
title="Поставка generic-оборудования",
record_date="15.02.2026",
amount="2500000.00",
@@ -173,8 +205,8 @@ class StateCorpExchangeServiceTest(TestCase):
source=ParserLoadLog.Source.ARBITRATION,
load_batch=1,
external_id="case-001",
inn=str(organization.mn_inn),
ogrn=str(organization.mn_ogrn),
inn=organization.inn,
ogrn=organization.ogrn,
title="А40-1/2026",
record_date="2026-03-25",
status="in_progress",
@@ -188,8 +220,8 @@ class StateCorpExchangeServiceTest(TestCase):
source=ParserLoadLog.Source.FEDRESURS_BANKRUPTCY,
load_batch=1,
external_id="fedresurs-001",
inn=str(organization.mn_inn),
ogrn=str(organization.mn_ogrn),
inn=organization.inn,
ogrn=organization.ogrn,
title="Сообщение о намерении",
record_date="2026-03-26",
status="published",
@@ -204,15 +236,15 @@ class StateCorpExchangeServiceTest(TestCase):
source=ParserLoadLog.Source.FAS_GOZ,
load_batch=1,
external_id="fas-goz-001",
inn=str(organization.mn_inn),
ogrn=str(organization.mn_ogrn),
inn=organization.inn,
ogrn=organization.ogrn,
title="Уклонение от заключения контракта",
record_date="2026-02-20",
status="active",
url="https://fas.gov.ru/register/001",
payload={
"Номер реестровой записи": "ГОЗ-001",
"Полное наименование лица": organization.pn_name,
"Полное наименование лица": organization.name,
"Дата вступления постановления": "2026-02-20",
},
)
@@ -220,8 +252,8 @@ class StateCorpExchangeServiceTest(TestCase):
source=ParserLoadLog.Source.FSTEC,
load_batch=1,
external_id="fstec-001",
inn=str(organization.mn_inn),
ogrn=str(organization.mn_ogrn),
inn=organization.inn,
ogrn=organization.ogrn,
title="Реестр лицензий ФСТЭК",
record_date="2026-01-10",
status="present",
@@ -236,8 +268,8 @@ class StateCorpExchangeServiceTest(TestCase):
source=ParserLoadLog.Source.TRUDVSEM,
load_batch=1,
external_id="trudvsem-001",
inn=str(organization.mn_inn),
ogrn=str(organization.mn_ogrn),
inn=organization.inn,
ogrn=organization.ogrn,
title="Инженер-испытатель",
record_date="2026-04-01",
amount="175000.00",
@@ -248,7 +280,7 @@ class StateCorpExchangeServiceTest(TestCase):
package = StateCorpExchangeService.build_package(actual_date="2026-03-15")
self.assertEqual(package.payload_counts["organizations"], 1)
self.assertEqual(package.payload_counts["registry_memberships"], 1)
self.assertNotIn("registry_memberships", package.payload_counts)
self.assertEqual(package.payload_counts["industrial_certificates"], 1)
self.assertEqual(package.payload_counts["manufacturers"], 1)
self.assertEqual(package.payload_counts["industrial_products"], 1)
@@ -264,15 +296,65 @@ class StateCorpExchangeServiceTest(TestCase):
payload = _decode_package_payload(package)
self.assertEqual(payload["format"], StateCorpExchangeService.PAYLOAD_FORMAT)
self.assertEqual(payload["schema_version"], 3)
self.assertEqual(payload["manifest"]["schema_version"], 3)
self.assertEqual(payload["manifest"]["source_system"], "mostovik")
self.assertEqual(payload["data"]["organizations"][0]["inn"], "7707083893")
self.assertNotIn("registry_memberships", payload["data"])
self.assertNotIn("registry_memberships", payload["manifest"]["sections"])
self.assertEqual(
payload["data"]["registry_memberships"][0],
payload["data"]["organizations"][0],
{
"organization_inn": "7707083893",
"registry_name": "Реестр госкорпорации Росатом",
"started_at": "2026-01-01",
"ended_at": None,
"mostovik_uid": str(organization.uid),
"rn": 1001,
"name": "АО Альфа",
"full_name": "Акционерное общество Альфа",
"short_name": "АО Альфа",
"pn_name": "АО Альфа",
"pn_name_en": "Alpha JSC",
"inn": "7707083893",
"kpp": "770701001",
"ogrn": "1027700132195",
"ogrip": "",
"okpo": "12345678",
"identity_status": "complete",
"primary_identity": "inn:7707083893:kpp:770701001",
"gk_code": "1",
"gk_name": "Росатом",
"in_korp_code": "head",
"in_korp_name": "Головная организация",
"filial": ".F.",
"is_branch": False,
"re_za": True,
"re_zasf": False,
"goz_participation": True,
"opk_registry_membership": True,
"ropk_num": "РОПК-001",
"ropk_razdel_num": "opk-1",
"ropk_razdel_name": "Росатом ОПК",
"registration_date": "2024-02-15",
"create_date": "2024",
"organizational_legal_form": "12267",
"organizational_legal_form1": "Акционерное общество",
"ownership_form": "61",
"ownership_form1": "Федеральная собственность",
"authorized_capital": "1500000.50",
"legal_address": "г. Москва, ул. Тверская, д. 1",
"business_act_cod": "26.11",
"business_activity": "Производство электронных компонентов",
"general_director": "Иванов Иван Иванович",
"general_director_tax_id": "123456789012",
"uk": "АО Управляющая компания",
"inn_uk": "7707000000",
"appointment_date": "2025-01-10",
"cf_fl_rn": "director-001",
"akc_fs": "100",
"akc_sf": "0",
"min": "Минпромторг",
"dep": "Департамент радиоэлектроники",
"otr": "Радиоэлектроника",
"integrated_structure": "Интегрированная структура Альфа",
"state_sector_code": "10",
"state_sector_name": "Государственный сектор",
},
)
self.assertEqual(
@@ -320,56 +402,38 @@ class StateCorpExchangeServiceTest(TestCase):
"Инженер-испытатель",
)
def test_build_package_exports_only_active_rosatom_roscosmos_registry_members(self):
target_registry = Register.objects.get(name="Реестр госкорпорации Роскосмос")
non_target_registry = RegisterFactory.create()
target = OrganizationFactory.create(
mn_inn=7707000001,
mn_ogrn=1027700000001,
pn_name="АО Целевая",
def test_build_package_exports_only_rosatom_roscosmos_organizations(self):
target = Organization.objects.create(
inn="7707000001",
ogrn="1027700000001",
name="АО Целевая",
gk_name="Роскосмос",
)
non_target = OrganizationFactory.create(
mn_inn=7707000002,
mn_ogrn=1027700000002,
pn_name="АО Не экспортируется",
non_target = Organization.objects.create(
inn="7707000002",
ogrn="1027700000002",
name="АО Не экспортируется",
gk_name="Иная корпорация",
)
inactive_target = OrganizationFactory.create(
mn_inn=7707000003,
mn_ogrn=1027700000003,
pn_name="АО Бывшая",
)
RegistryMembershipPeriodFactory.create(
registry=target_registry,
organization=target,
started_at="2026-01-01",
ended_at=None,
)
RegistryMembershipPeriodFactory.create(
registry=non_target_registry,
organization=non_target,
started_at="2026-01-01",
ended_at=None,
)
RegistryMembershipPeriodFactory.create(
registry=target_registry,
organization=inactive_target,
started_at="2026-01-01",
ended_at="2026-03-01",
rosatom = Organization.objects.create(
inn="7707000003",
ogrn="1027700000003",
name="АО Росатом",
gk_name="ГК Росатом",
)
IndustrialProductRecordFactory.create(
inn=str(target.mn_inn),
inn=target.inn,
registry_number="target-product",
)
IndustrialProductRecordFactory.create(
inn=str(non_target.mn_inn),
inn=non_target.inn,
registry_number="non-target-product",
)
GenericParserRecord.objects.create(
source=ParserLoadLog.Source.TRUDVSEM,
load_batch=1,
external_id="target-vacancy",
inn=str(target.mn_inn),
inn=target.inn,
title="Целевая вакансия",
record_date="2026-03-10",
)
@@ -377,7 +441,7 @@ class StateCorpExchangeServiceTest(TestCase):
source=ParserLoadLog.Source.TRUDVSEM,
load_batch=1,
external_id="non-target-vacancy",
inn=str(non_target.mn_inn),
inn=non_target.inn,
title="Лишняя вакансия",
record_date="2026-03-10",
)
@@ -386,16 +450,10 @@ class StateCorpExchangeServiceTest(TestCase):
payload = _decode_package_payload(package)
self.assertEqual(
[item["inn"] for item in payload["data"]["organizations"]],
[str(target.mn_inn)],
)
self.assertEqual(
[
(item["organization_inn"], item["registry_name"])
for item in payload["data"]["registry_memberships"]
],
[(str(target.mn_inn), "Реестр госкорпорации Роскосмос")],
sorted(item["inn"] for item in payload["data"]["organizations"]),
sorted([target.inn, rosatom.inn]),
)
self.assertNotIn("registry_memberships", payload["data"])
self.assertEqual(
[
item["registry_number"]
@@ -408,6 +466,16 @@ class StateCorpExchangeServiceTest(TestCase):
["target-vacancy"],
)
package = StateCorpExchangeService.build_package(
organization_inns=[target.inn, non_target.inn],
actual_date="2026-03-15",
)
payload = _decode_package_payload(package)
self.assertEqual(
[item["inn"] for item in payload["data"]["organizations"]],
[target.inn],
)
@patch("apps.exchange.state_corp_services.requests.post")
def test_send_package_posts_multipart_archive(self, post_mock):
response_mock = Mock(status_code=201)