feat(organizations): migrate source storage to polymorphic records

This commit is contained in:
2026-05-19 10:23:53 +02:00
parent 19a7d5a91c
commit 4ca2fa25d5
44 changed files with 7129 additions and 1551 deletions

View File

@@ -0,0 +1,389 @@
"""Tests for organization API v2 backed by source extensions."""
from django.core.cache import cache
from django.urls import reverse
from organizations.models import (
Organization,
OrganizationSourceRecord,
PlannedInspectionExtension,
)
from rest_framework import status
from rest_framework.test import APITestCase
from tests.apps.registers.factories import (
OrganizationFactory as RegistryOrganizationFactory,
)
from tests.apps.registers.factories import (
RegisterFactory,
RegistryMembershipPeriodFactory,
)
from tests.apps.user.factories import UserFactory
class OrganizationSourceExtensionsApiV2Test(APITestCase):
"""Checks organization-centric source extension API contract."""
def setUp(self):
cache.clear()
self.user = UserFactory.create_user()
self.client.force_authenticate(self.user)
def test_list_returns_compact_source_summaries_instead_of_embedded_data(self):
organization = Organization.objects.create(
name='ООО "API"',
inn="7707083810",
ogrn="1027700132010",
)
extension = PlannedInspectionExtension.objects.create(
organization=organization,
title="Плановые проверки Генпрокуратуры России",
records_count=1,
)
OrganizationSourceRecord.objects.create(
extension=extension,
record_type="inspection",
source="inspections",
external_id="INSP-API",
title="Проверка API",
payload={"registration_number": "INSP-API"},
)
response = self.client.get(
reverse("api_v2:organizations:organizations-list"),
{"has_registry": "false"},
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
item = response.data["data"][0]
self.assertNotIn("data", item)
self.assertNotIn("data_sources", item)
self.assertEqual(item["identity_status"], Organization.IdentityStatus.COMPLETE)
self.assertEqual(item["sources"][0]["uid"], str(extension.uid))
self.assertEqual(item["sources"][0]["source_group"], "planned_inspections")
self.assertEqual(item["sources"][0]["records_count"], 1)
def test_organization_sources_action_returns_extensions(self):
organization = Organization.objects.create(
name='ООО "Sources"',
inn="7707083811",
ogrn="1027700132011",
)
extension = PlannedInspectionExtension.objects.create(
organization=organization,
title="Плановые проверки Генпрокуратуры России",
)
response = self.client.get(
reverse(
"api_v2:organizations:organizations-sources",
args=[organization.uid],
)
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data[0]["uid"], str(extension.uid))
def test_source_records_endpoint_returns_extension_records(self):
organization = Organization.objects.create(
name='ООО "Records"',
inn="7707083812",
ogrn="1027700132012",
)
extension = PlannedInspectionExtension.objects.create(
organization=organization,
title="Плановые проверки Генпрокуратуры России",
)
OrganizationSourceRecord.objects.create(
extension=extension,
record_type="inspection",
source="inspections",
external_id="INSP-RECORD",
title="Проверка records",
payload={"registration_number": "INSP-RECORD"},
)
response = self.client.get(
reverse(
"api_v2:organizations:organization-sources-records",
args=[extension.uid],
)
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data["data"][0]["external_id"], "INSP-RECORD")
self.assertEqual(
response.data["data"][0]["payload"]["registration_number"],
"INSP-RECORD",
)
def test_flat_source_records_endpoint_filters_by_source_group(self):
target = Organization.objects.create(
name='ООО "Flat Records"',
inn="7707083813",
ogrn="1027700132013",
)
other = Organization.objects.create(
name='ООО "Other Records"',
inn="7707083814",
ogrn="1027700132014",
)
extension = PlannedInspectionExtension.objects.create(
organization=target,
title="Плановые проверки Генпрокуратуры России",
)
other_extension = PlannedInspectionExtension.objects.create(
organization=other,
title="Плановые проверки Генпрокуратуры России",
)
OrganizationSourceRecord.objects.create(
extension=extension,
record_type="inspection",
source="inspections",
external_id="INSP-FLAT",
title="Проверка flat",
payload={"registration_number": "INSP-FLAT"},
)
OrganizationSourceRecord.objects.create(
extension=other_extension,
record_type="inspection",
source="inspections",
external_id="INSP-OTHER",
title="Другая проверка",
payload={"registration_number": "INSP-OTHER"},
)
response = self.client.get(
reverse("api_v2:organizations:organization-source-records-list"),
{
"source_group": "planned_inspections",
"organization": str(target.uid),
},
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data["meta"]["pagination"]["total_count"], 1)
record = response.data["data"][0]
self.assertEqual(record["external_id"], "INSP-FLAT")
self.assertEqual(record["source_group"], "planned_inspections")
self.assertEqual(record["organization"]["uid"], str(target.uid))
def test_flat_source_records_endpoint_filters_by_has_registry(self):
with_registry = Organization.objects.create(
name='ООО "With Registry Source"',
inn="7707083815",
ogrn="1027700132015",
)
without_registry = Organization.objects.create(
name='ООО "Without Registry Source"',
inn="7707083816",
ogrn="1027700132016",
)
with_registry_extension = PlannedInspectionExtension.objects.create(
organization=with_registry,
title="Плановые проверки Генпрокуратуры России",
)
without_registry_extension = PlannedInspectionExtension.objects.create(
organization=without_registry,
title="Плановые проверки Генпрокуратуры России",
)
OrganizationSourceRecord.objects.create(
extension=with_registry_extension,
record_type="inspection",
source="inspections",
external_id="INSP-WITH-REGISTRY",
title="Проверка организации из реестра",
)
OrganizationSourceRecord.objects.create(
extension=without_registry_extension,
record_type="inspection",
source="inspections",
external_id="INSP-WITHOUT-REGISTRY",
title="Проверка организации без реестра",
)
registry = RegisterFactory(name="Реестр source records")
registry_organization = RegistryOrganizationFactory(
mn_inn=int(with_registry.inn),
mn_ogrn=int(with_registry.ogrn),
)
RegistryMembershipPeriodFactory(
registry=registry,
organization=registry_organization,
)
only_registry = self.client.get(
reverse("api_v2:organizations:organization-source-records-list"),
{
"has_registry": "true",
"source_group": "planned_inspections",
},
)
without_registry_response = self.client.get(
reverse("api_v2:organizations:organization-source-records-list"),
{
"has_registry": "false",
"source_group": "planned_inspections",
"ordering": "extension__organization__inn",
},
)
self.assertEqual(only_registry.status_code, status.HTTP_200_OK)
self.assertEqual(only_registry.data["meta"]["pagination"]["total_count"], 1)
self.assertEqual(
only_registry.data["data"][0]["external_id"],
"INSP-WITH-REGISTRY",
)
self.assertEqual(without_registry_response.status_code, status.HTTP_200_OK)
self.assertEqual(
without_registry_response.data["meta"]["pagination"]["total_count"],
1,
)
self.assertEqual(
without_registry_response.data["data"][0]["external_id"],
"INSP-WITHOUT-REGISTRY",
)
def test_source_record_organization_uses_active_registry_identity(self):
organization = Organization.objects.create(
name="1241800009703",
ogrn="1241800009703",
)
extension = PlannedInspectionExtension.objects.create(
organization=organization,
title="Плановые проверки Генпрокуратуры России",
)
OrganizationSourceRecord.objects.create(
extension=extension,
record_type="inspection",
source="inspections",
external_id="INSP-REGISTRY-IDENTITY",
title="Проверка организации из реестра",
)
registry_organization = RegistryOrganizationFactory(
pn_name='ООО "Реестровое имя"',
mn_inn=1800020960,
mn_ogrn=int(organization.ogrn),
in_kpp=180001001,
)
RegistryMembershipPeriodFactory(organization=registry_organization)
response = self.client.get(
reverse("api_v2:organizations:organization-source-records-list"),
{
"has_registry": "true",
"source_group": "planned_inspections",
},
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data["meta"]["pagination"]["total_count"], 1)
response_organization = response.data["data"][0]["organization"]
self.assertEqual(response_organization["uid"], str(organization.uid))
self.assertEqual(response_organization["name"], 'ООО "Реестровое имя"')
self.assertEqual(response_organization["inn"], "1800020960")
self.assertEqual(response_organization["kpp"], "180001001")
self.assertEqual(response_organization["ogrn"], organization.ogrn)
def test_flat_source_records_searches_payload_values_displayed_in_tables(self):
target = Organization.objects.create(
name='ООО "Поиск по payload"',
inn="7707083817",
ogrn="1027700132017",
)
other = Organization.objects.create(
name='ООО "Другая payload"',
inn="7707083818",
ogrn="1027700132018",
)
extension = PlannedInspectionExtension.objects.create(
organization=target,
title="Плановые проверки Генпрокуратуры России",
)
other_extension = PlannedInspectionExtension.objects.create(
organization=other,
title="Плановые проверки Генпрокуратуры России",
)
OrganizationSourceRecord.objects.create(
extension=extension,
record_type="inspection",
source="inspections",
external_id="INSP-PAYLOAD",
title="Проверка payload",
payload={
"registration_number": "INSPECTION-PAYLOAD-42",
"control_authority": "Северный Ростехнадзор",
"start_date_normalized": "2026-06-15",
},
)
OrganizationSourceRecord.objects.create(
extension=other_extension,
record_type="inspection",
source="inspections",
external_id="INSP-OTHER-PAYLOAD",
title="Другая проверка payload",
payload={
"registration_number": "INSPECTION-OTHER-42",
"control_authority": "Другой контрольный орган",
},
)
for search_value in (
"INSPECTION-PAYLOAD-42",
"Северный Ростехнадзор",
"2026-06-15",
):
with self.subTest(search_value=search_value):
response = self.client.get(
reverse("api_v2:organizations:organization-source-records-list"),
{
"has_registry": "false",
"source_group": "planned_inspections",
"search": search_value,
},
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data["meta"]["pagination"]["total_count"], 1)
self.assertEqual(
response.data["data"][0]["external_id"],
"INSP-PAYLOAD",
)
def test_flat_source_records_searches_registry_identity_displayed_in_tables(self):
organization = Organization.objects.create(
name="1241800009704",
ogrn="1241800009704",
)
extension = PlannedInspectionExtension.objects.create(
organization=organization,
title="Плановые проверки Генпрокуратуры России",
)
OrganizationSourceRecord.objects.create(
extension=extension,
record_type="inspection",
source="inspections",
external_id="INSP-REGISTRY-SEARCH",
title="Проверка поиска по реестру",
)
registry_organization = RegistryOrganizationFactory(
pn_name='АО "Реестровый поиск"',
mn_inn=1800020961,
mn_ogrn=int(organization.ogrn),
in_kpp=180001002,
)
RegistryMembershipPeriodFactory(organization=registry_organization)
response = self.client.get(
reverse("api_v2:organizations:organization-source-records-list"),
{
"has_registry": "true",
"source_group": "planned_inspections",
"search": "Реестровый поиск",
},
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data["meta"]["pagination"]["total_count"], 1)
record = response.data["data"][0]
self.assertEqual(record["external_id"], "INSP-REGISTRY-SEARCH")
self.assertEqual(record["organization"]["name"], 'АО "Реестровый поиск"')