fix: resolve parser organizations from directory only
Some checks failed
CI/CD Pipeline / Quality Gate (push) Failing after 32s
CI/CD Pipeline / Build and Push Images (push) Has been skipped
CI/CD Pipeline / Internal Notify (push) Successful in 0s
CI/CD Pipeline / Deploy Dev via Compose (push) Has been skipped

This commit is contained in:
2026-06-07 16:28:57 +02:00
parent 6ba8fa8d88
commit 802257a757
19 changed files with 184 additions and 83 deletions

View File

@@ -0,0 +1,30 @@
"""Organization helpers for parser tests."""
from __future__ import annotations
from django.utils import timezone
from organizations.models import Organization
def create_directory_organization(**kwargs) -> Organization:
"""Create an organization row that belongs to the authoritative directory."""
kwargs.setdefault("directory_imported_at", timezone.now())
return Organization.objects.create(**kwargs)
def get_or_create_directory_organization(
*,
defaults: dict | None = None,
**lookup,
) -> tuple[Organization, bool]:
"""Get or create an authoritative directory organization for ingestion tests."""
directory_defaults = dict(defaults or {})
directory_defaults.setdefault("directory_imported_at", timezone.now())
organization, created = Organization.objects.get_or_create(
defaults=directory_defaults,
**lookup,
)
if not created and organization.directory_imported_at is None:
organization.directory_imported_at = timezone.now()
organization.save(update_fields=["directory_imported_at"])
return organization, created

View File

@@ -45,6 +45,7 @@ from tests.apps.parsers.factories import (
ParserLoadLogFactory,
ProxyFactory,
)
from tests.apps.parsers.organization_helpers import create_directory_organization
from tests.apps.user.factories import UserFactory
from tests.utils.fixtures import fake
@@ -80,7 +81,7 @@ def _build_fns_zip_upload() -> SimpleUploadedFile:
buffer = io.BytesIO()
with zipfile.ZipFile(buffer, "w", compression=zipfile.ZIP_DEFLATED) as archive:
ogrn = _digits(13)
Organization.objects.create(
create_directory_organization(
name=f"ФНС {ogrn}",
ogrn=ogrn,
)
@@ -98,7 +99,7 @@ def _build_fns_zip_upload() -> SimpleUploadedFile:
def _build_fns_excel_upload() -> SimpleUploadedFile:
ogrn = _digits(13)
Organization.objects.create(
create_directory_organization(
name=f"ФНС {ogrn}",
ogrn=ogrn,
)

View File

@@ -37,6 +37,8 @@ from organizations.models import (
OrganizationSourceRecord,
)
from tests.apps.parsers.organization_helpers import create_directory_organization
class DirectIngestionParserServicesTest(TestCase):
"""Parser save services should not write legacy parser record rows."""
@@ -52,7 +54,7 @@ class DirectIngestionParserServicesTest(TestCase):
("7707083809", "1027700132009"),
("7707083810", "1027700132010"),
):
Organization.objects.create(
create_directory_organization(
name=f"Справочник {inn or ogrn}",
inn=inn,
ogrn=ogrn,

View File

@@ -14,6 +14,7 @@ from apps.parsers.services import ParserLoadLogService, ProcurementService
from django.test import TestCase, override_settings
from organizations.models import Organization, OrganizationSourceRecord
from tests.apps.parsers.organization_helpers import create_directory_organization
from tests.utils import TestHTTPServer
from tests.utils.fixtures import build_zakupki_xml, build_zip, fake
@@ -44,7 +45,7 @@ def _add_zakupki_zip(
) -> int:
xml_bytes, rows = build_zakupki_xml(count=count)
for row in rows:
Organization.objects.create(
create_directory_organization(
name=row.customer_name,
inn=row.customer_inn,
kpp=row.customer_kpp,

View File

@@ -12,6 +12,7 @@ from apps.parsers.services import (
from django.test import TestCase
from organizations.models import Organization
from tests.apps.parsers.organization_helpers import create_directory_organization
from tests.utils.fixtures import fake
@@ -32,7 +33,7 @@ def _form_code() -> str:
def _create_registry_organization(*, inn: str, ogrn: str) -> Organization:
return Organization.objects.create(
return create_directory_organization(
name=fake.company(),
ogrn=ogrn,
inn=inn,

View File

@@ -24,6 +24,7 @@ from rest_framework import status
from rest_framework.test import APITestCase
from tests.apps.user.factories import UserFactory
from tests.apps.parsers.organization_helpers import create_directory_organization
from tests.utils.fixtures import fake
@@ -55,7 +56,7 @@ def _build_fns_zip_bytes(file_map: dict[str, bytes]) -> bytes:
def _ensure_directory_organization(*, ogrn: str) -> Organization:
return Organization.objects.create(
return create_directory_organization(
name=f"ФНС {ogrn}",
ogrn=ogrn,
)

View File

@@ -11,6 +11,7 @@ from django.test import TestCase
from organizations.models import Organization, OrganizationSourceRecord
from tests.apps.parsers.factories import ProcurementRecordFactory, fake
from tests.apps.parsers.organization_helpers import create_directory_organization
def _digits(length: int) -> str:
@@ -35,7 +36,7 @@ def _other_law(law_type: str) -> str:
def _create_registry_organization(*, inn: str, ogrn: str) -> Organization:
return Organization.objects.create(
return create_directory_organization(
name=fake.company(),
ogrn=ogrn,
inn=inn,
@@ -47,7 +48,7 @@ def _create_registry_organization(*, inn: str, ogrn: str) -> Organization:
def _ensure_directory_organization_for_procurement(
procurement: Procurement,
) -> Organization:
return Organization.objects.create(
return create_directory_organization(
name=procurement.customer_name,
ogrn=procurement.customer_ogrn,
inn=procurement.customer_inn,

View File

@@ -25,6 +25,7 @@ from django.test import TestCase
from organizations.models import Organization
from tests.apps.parsers.factories import ParserLoadLogFactory
from tests.apps.parsers.organization_helpers import create_directory_organization
class NormalizeHelpersTest(TestCase):
@@ -67,12 +68,12 @@ class RegistryOrganizationResolverTest(TestCase):
self.assertEqual(lookup.by_ogrn, {})
def test_resolve_organization_id_by_unique_inn_and_ogrn(self):
org_by_inn = Organization.objects.create(
org_by_inn = create_directory_organization(
name="By INN",
inn="7701001001",
ogrn="1027700100001",
)
org_by_ogrn = Organization.objects.create(
org_by_ogrn = create_directory_organization(
name="By OGRN",
inn="7701001002",
ogrn="1027700100002",
@@ -146,12 +147,12 @@ class ParserLoadLogServiceRetryTest(TestCase):
class SmallParserServiceQueryTest(TestCase):
def test_industrial_product_service_query_helpers(self):
Organization.objects.create(
create_directory_organization(
name='ООО "Продукт 1"',
inn="7701001001",
ogrn="1027700100001",
)
Organization.objects.create(
create_directory_organization(
name='ООО "Продукт 2"',
inn="7701001001",
ogrn="1027700100002",
@@ -201,12 +202,12 @@ class SmallParserServiceQueryTest(TestCase):
)
def test_inspection_service_has_data_for_period(self):
Organization.objects.create(
create_directory_organization(
name='ООО "Проверка 1"',
inn="7701002001",
ogrn="1027700200001",
)
Organization.objects.create(
create_directory_organization(
name='ООО "Проверка 2"',
inn="7701002002",
ogrn="1027700200002",
@@ -263,13 +264,13 @@ class SmallParserServiceQueryTest(TestCase):
self.assertTrue(InspectionService.has_data_for_period(2026, 4, True))
def test_procurement_service_find_by_customer_name_with_batch(self):
Organization.objects.create(
create_directory_organization(
name="АО Тестовый заказчик 1",
inn="7701003001",
kpp="770101001",
ogrn="1027700300001",
)
Organization.objects.create(
create_directory_organization(
name="АО Тестовый заказчик 2",
inn="7701003002",
kpp="770101002",
@@ -332,7 +333,7 @@ class SmallParserServiceQueryTest(TestCase):
class FNSReportServiceHelpersTest(TestCase):
def test_exists_find_and_status_helpers(self):
Organization.objects.create(
create_directory_organization(
name='ООО "ФНС"',
ogrn="1027700111111",
)

View File

@@ -50,6 +50,7 @@ from .factories import (
ParserLoadLogFactory,
ProxyFactory,
)
from .organization_helpers import create_directory_organization
def _digits(length: int) -> str:
@@ -61,7 +62,7 @@ def _proxy_address() -> str:
def _create_registry_organization(*, inn: str, ogrn: str) -> Organization:
return Organization.objects.create(
return create_directory_organization(
name=fake.company(),
ogrn=ogrn,
inn=inn,

View File

@@ -25,6 +25,11 @@ from organizations.source_ingestion import (
)
from rest_framework.exceptions import ValidationError
from tests.apps.parsers.organization_helpers import (
create_directory_organization,
get_or_create_directory_organization,
)
def _save_source_record(
*,
@@ -38,7 +43,7 @@ def _save_source_record(
payload = dict(payload or {})
if inn:
if not Organization.objects.filter(inn=inn).exists():
Organization.objects.create(
create_directory_organization(
name=organization_name or title or external_id,
inn=inn,
)
@@ -46,7 +51,7 @@ def _save_source_record(
rn_key = organization_name or title or external_id
rn = str(int(hashlib.sha256(rn_key.encode()).hexdigest()[:12], 16) % 10**12)
payload.setdefault("rn", rn)
Organization.objects.get_or_create(
get_or_create_directory_organization(
rn=rn,
defaults={"name": organization_name or title or external_id},
)

View File

@@ -20,6 +20,10 @@ from rest_framework import status
from rest_framework.test import APITestCase
from tests.apps.parsers.factories import ParserLoadLogFactory
from tests.apps.parsers.organization_helpers import (
create_directory_organization,
get_or_create_directory_organization,
)
from tests.apps.user.factories import UserFactory
from tests.utils.fixtures import fake
@@ -45,7 +49,7 @@ def _save_source_record(
if ogrn:
query["ogrn"] = ogrn
if not Organization.objects.filter(**query).exists():
Organization.objects.create(
create_directory_organization(
name=organization_name or title or external_id,
inn=inn,
ogrn=ogrn,
@@ -54,7 +58,7 @@ def _save_source_record(
rn_key = organization_name or title or external_id
rn = str(int(hashlib.sha256(rn_key.encode()).hexdigest()[:12], 16) % 10**12)
payload["rn"] = rn
Organization.objects.get_or_create(
get_or_create_directory_organization(
rn=rn,
defaults={"name": organization_name or title or external_id},
)

View File

@@ -19,6 +19,7 @@ from tests.apps.parsers.factories import (
ParserLoadLogFactory,
ProcurementRecordFactory,
)
from tests.apps.parsers.organization_helpers import create_directory_organization
from tests.apps.user.factories import UserFactory
from tests.utils.fixtures import fake
@@ -36,12 +37,12 @@ class SourcesApiE2ETest(APITestCase):
shared_inn = _digits(10)
shared_ogrn = _digits(13)
report_ogrn = _digits(13)
Organization.objects.create(
create_directory_organization(
name="Shared Organization",
inn=shared_inn,
ogrn=shared_ogrn,
)
Organization.objects.create(
create_directory_organization(
name="Financial Organization",
ogrn=report_ogrn,
)

View File

@@ -70,6 +70,7 @@ from tests.apps.parsers.factories import (
ProcurementRecordFactory,
ProxyFactory,
)
from tests.apps.parsers.organization_helpers import create_directory_organization
from tests.utils import TestHTTPServer
from tests.utils.fixtures import (
build_minpromtorg_certificates_excel,
@@ -96,7 +97,7 @@ def _digits(length: int) -> str:
def OrganizationFactory(**kwargs) -> Organization:
kwargs.setdefault("name", kwargs.get("pn_name") or fake.company())
kwargs.setdefault("okpo", kwargs.get("mn_okpo") or _digits(8))
return Organization.objects.create(**kwargs)
return create_directory_organization(**kwargs)
def RegistryMembershipPeriodFactory(
@@ -135,7 +136,7 @@ def _ensure_directory_organization(
kpp: str = "",
name: str = "",
) -> Organization:
return Organization.objects.create(
return create_directory_organization(
name=name or f"Справочник {inn or ogrn}",
inn=str(inn or ""),
ogrn=str(ogrn or ""),
@@ -312,7 +313,7 @@ class GenericSourceFetchTestCase(TestCase):
@override_settings(CHECKO_API_KEY="")
def test_fstec_enriches_identity_from_local_registry_name_match(self):
Organization.objects.create(
create_directory_organization(
pn_name='ОБЩЕСТВО С ОГРАНИЧЕННОЙ ОТВЕТСТВЕННОСТЬЮ "ВЕБЛОК"',
mn_ogrn=1237700253306,
mn_inn=7713497980,
@@ -348,7 +349,7 @@ class GenericSourceFetchTestCase(TestCase):
@override_settings(CHECKO_API_KEY="")
def test_fstec_restores_leading_zero_for_local_registry_inn(self):
Organization.objects.create(
create_directory_organization(
pn_name='ОБЩЕСТВО С ОГРАНИЧЕННОЙ ОТВЕТСТВЕННОСТЬЮ "БАШНЕФТЬ-ДОБЫЧА"',
mn_ogrn=1090280032699,
mn_inn=277106840,
@@ -487,21 +488,21 @@ class GenericSourceFetchTestCase(TestCase):
@override_settings(CHECKO_API_KEY="test-key", FEDRESURS_CHECKO_FALLBACK_LIMIT=10)
def test_fedresurs_falls_back_to_checko_for_active_registry_organizations(self):
organization = Organization.objects.create(
organization = create_directory_organization(
pn_name='ООО "Тест"',
mn_ogrn=1027700000000,
mn_inn=7701000001,
in_kpp=770101001,
mn_okpo="12345678",
)
inactive = Organization.objects.create(
inactive = create_directory_organization(
pn_name='ООО "Бывший реестр"',
mn_ogrn=1027700000098,
mn_inn=7701000098,
in_kpp=770101001,
mn_okpo="12345678",
)
no_membership = Organization.objects.create(
no_membership = create_directory_organization(
pn_name='ООО "Без реестра"',
mn_ogrn=1027700000099,
mn_inn=7701000099,
@@ -610,14 +611,14 @@ class GenericSourceFetchTestCase(TestCase):
def test_arbitration_fetches_checko_legal_cases_for_active_registry_organizations(
self,
):
organization = Organization.objects.create(
organization = create_directory_organization(
pn_name='ООО "Арбитраж"',
mn_ogrn=1027700000001,
mn_inn=7701000002,
in_kpp=770101001,
mn_okpo="12345678",
)
inactive = Organization.objects.create(
inactive = create_directory_organization(
pn_name='ООО "Бывший арбитраж"',
mn_ogrn=1027700000088,
mn_inn=7701000088,
@@ -743,14 +744,14 @@ class GenericSourceFetchTestCase(TestCase):
def test_registry_inspections_fetches_checko_for_active_registry_organizations(
self,
):
organization = Organization.objects.create(
organization = create_directory_organization(
pn_name='ООО "Проверки"',
mn_ogrn=1027700000004,
mn_inn=7701000004,
in_kpp=770101001,
mn_okpo="12345678",
)
inactive = Organization.objects.create(
inactive = create_directory_organization(
pn_name='ООО "Старые проверки"',
mn_ogrn=1027700000005,
mn_inn=7701000005,
@@ -822,14 +823,14 @@ class GenericSourceFetchTestCase(TestCase):
def test_registry_contracts_fetches_checko_for_active_registry_organizations(
self,
):
organization = Organization.objects.create(
organization = create_directory_organization(
pn_name='ООО "Контракты"',
mn_ogrn=1027700000006,
mn_inn=7701000006,
in_kpp=770101001,
mn_okpo="12345678",
)
inactive = Organization.objects.create(
inactive = create_directory_organization(
pn_name='ООО "Старые контракты"',
mn_ogrn=1027700000007,
mn_inn=7701000007,
@@ -2109,7 +2110,7 @@ class FNSFileTasksTestCase(TestCase):
content = _build_fns_excel_bytes()
external_id = _digits(5)
ogrn = _digits(13)
Organization.objects.create(name=f"ФНС {ogrn}", ogrn=ogrn)
create_directory_organization(name=f"ФНС {ogrn}", ogrn=ogrn)
filename = f"fin_{external_id}_{ogrn}.xlsx"
file_path = os.path.join(watch_dir, filename)
with open(file_path, "wb") as handle:
@@ -2344,7 +2345,7 @@ class FNSFileTasksTestCase(TestCase):
os.makedirs(watch_dir, exist_ok=True)
file_path = self._write_fns_file(watch_dir)
ogrn = self._ogrn_from_fns_file_path(file_path)
Organization.objects.create(name=f"ФНС филиал {ogrn}", ogrn=ogrn)
create_directory_organization(name=f"ФНС филиал {ogrn}", ogrn=ogrn)
with override_settings(
FNS_WATCH_DIRECTORY=watch_dir,

View File

@@ -35,6 +35,10 @@ from tests.apps.parsers.factories import (
ManufacturerRecordFactory,
ParserLoadLogFactory,
)
from tests.apps.parsers.organization_helpers import (
create_directory_organization,
get_or_create_directory_organization,
)
from tests.apps.user.factories import UserFactory
from tests.utils.fixtures import fake
@@ -46,7 +50,7 @@ def _digits(length: int) -> str:
def RegisterOrganizationFactory(**kwargs) -> Organization:
kwargs.setdefault("name", kwargs.get("pn_name") or fake.company())
kwargs.setdefault("okpo", kwargs.get("mn_okpo") or _digits(8))
return Organization.objects.create(**kwargs)
return create_directory_organization(**kwargs)
def RegistryMembershipPeriodFactory(
@@ -113,7 +117,7 @@ def _save_source_record(
if ogrn:
query["ogrn"] = ogrn
if not Organization.objects.filter(**query).exists():
Organization.objects.create(
create_directory_organization(
name=organization_name,
inn=inn,
ogrn=ogrn,
@@ -122,7 +126,7 @@ def _save_source_record(
rn_key = organization_name or title or external_id
rn = str(int(hashlib.sha256(rn_key.encode()).hexdigest()[:12], 16) % 10**12)
payload.setdefault("rn", rn)
Organization.objects.get_or_create(
get_or_create_directory_organization(
rn=rn,
defaults={"name": organization_name},
)
@@ -281,7 +285,7 @@ class ParsersViewSetTest(APITestCase):
def test_procurements_v1_enriches_missing_customer_fields_from_canonical_organization(
self,
):
organization = Organization.objects.create(
organization = create_directory_organization(
name='ООО "КАНОНИЧЕСКИЙ ЗАКАЗЧИК"',
inn="7701000101",
kpp="770101001",
@@ -323,13 +327,13 @@ class ParsersViewSetTest(APITestCase):
self.assertEqual(detail_row["customer_ogrn"], organization.ogrn)
def test_procurements_v1_uses_customer_kpp_for_branch_enrichment(self):
head = Organization.objects.create(
head = create_directory_organization(
name='ООО "КАНОНИЧЕСКИЙ ЗАКАЗЧИК"',
inn="7701000101",
kpp="770101001",
ogrn="1027700000001",
)
branch = Organization.objects.create(
branch = create_directory_organization(
name='ООО "КАНОНИЧЕСКИЙ ЗАКАЗЧИК" ФИЛИАЛ',
inn=head.inn,
kpp="780101001",
@@ -361,13 +365,13 @@ class ParsersViewSetTest(APITestCase):
self.assertEqual(response.data["customer_kpp"], branch.kpp)
def test_procurements_v1_does_not_guess_branch_when_customer_kpp_is_missing(self):
head = Organization.objects.create(
head = create_directory_organization(
name='ООО "КАНОНИЧЕСКИЙ ЗАКАЗЧИК"',
inn="7701000101",
kpp="770101001",
ogrn="1027700000001",
)
Organization.objects.create(
create_directory_organization(
name='ООО "КАНОНИЧЕСКИЙ ЗАКАЗЧИК" ФИЛИАЛ',
inn=head.inn,
kpp="780101001",
@@ -479,7 +483,7 @@ class ParsersViewSetTest(APITestCase):
def test_parser_results_v1_enrich_missing_organization_fields_without_contract_change(
self,
):
organization = Organization.objects.create(
organization = create_directory_organization(
name='ООО "КАНОНИЧЕСКИЙ ПОСТАВЩИК"',
inn="7701000102",
kpp="770102001",
@@ -1199,7 +1203,7 @@ class ParsersViewSetTest(APITestCase):
failed_dir = os.path.join(tmpdir, "failed")
content = _build_fns_excel_bytes()
ogrn = _digits(13)
Organization.objects.create(name=f"ФНС {ogrn}", ogrn=ogrn)
create_directory_organization(name=f"ФНС {ogrn}", ogrn=ogrn)
upload = SimpleUploadedFile(
f"fin_{_digits(5)}_{ogrn}.xlsx",
content,
@@ -1226,7 +1230,7 @@ class ParsersViewSetTest(APITestCase):
failed_dir = os.path.join(tmpdir, "failed")
content = _build_fns_excel_bytes()
ogrn = _digits(13)
Organization.objects.create(name=f"ФНС {ogrn}", ogrn=ogrn)
create_directory_organization(name=f"ФНС {ogrn}", ogrn=ogrn)
upload = SimpleUploadedFile(
f"fin_{_digits(5)}_{ogrn}.xlsx",
content,
@@ -1250,7 +1254,7 @@ class ParsersViewSetTest(APITestCase):
self.client.force_authenticate(self.admin)
external_id = _digits(5)
ogrn = _digits(13)
Organization.objects.create(name=f"ФНС {ogrn}", ogrn=ogrn)
create_directory_organization(name=f"ФНС {ogrn}", ogrn=ogrn)
filename = f"fin_{external_id}_{ogrn}.xlsx"
upload = SimpleUploadedFile(
"fns_reports.zip",