feat: export organizations to state corp v3
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user