feat: import mostovik exchange sections
All checks were successful
CI/CD Pipeline / Run Tests (push) Successful in 6m12s
CI/CD Pipeline / Code Quality Checks (push) Successful in 6m19s
CI/CD Pipeline / Build Docker Images (push) Successful in 2m21s
CI/CD Pipeline / Push to Gitea Registry (push) Successful in 1s
CI/CD Pipeline / Deploy to Server (push) Successful in 1s

This commit is contained in:
2026-05-27 23:13:40 +02:00
parent bd8e1a8400
commit d1b0cd7945
49 changed files with 1831 additions and 319 deletions

View File

@@ -18,9 +18,13 @@ from apps.external_data.models import (
ArbitrationCase,
BankruptcyProcedure,
DefenseUnreliableSupplier,
FinancialReport,
FinancialReportLine,
IndustrialCertificate,
IndustrialProduct,
InformationSecurityRegistryEntry,
LaborVacancy,
ManufacturerRegistryEntry,
ProsecutorCheck,
PublicProcurement,
)
@@ -164,6 +168,26 @@ def build_exchange_payload() -> dict[str, list[dict[str, object]]]:
"registry_number": "prod-001",
}
],
"industrial_certificates": [
{
"organization_inn": "7707083893",
"certificate_number": "CERT-001",
"issue_date": "2026-01-10",
"expiry_date": "2027-01-10",
"certificate_file_url": "https://minpromtorg.gov.ru/cert/001",
"organisation_name": "АО Альфа Обновленная",
"ogrn": "1027700132195",
}
],
"manufacturers": [
{
"organization_inn": "7707083893",
"full_legal_name": "АО Альфа Обновленная",
"inn": "7707083893",
"ogrn": "1027700132195",
"address": "г. Москва, ул. Тверская, д. 1",
}
],
"prosecutor_checks": [
{
"organization_inn": "7707083893",
@@ -188,6 +212,28 @@ def build_exchange_payload() -> dict[str, list[dict[str, object]]]:
"purchase_name": "Поставка специализированного оборудования",
}
],
"financial_reports": [
{
"organization_inn": "7707083893",
"external_id": "fin-001",
"ogrn": "1027700132195",
"file_name": "fin_001_1027700132195.xlsx",
"file_hash": "f" * 64,
"load_batch": 7,
"status": "success",
"source": "api",
"lines": [
{
"form_code": "1",
"line_code": "1600",
"line_name": "Баланс",
"year": 2025,
"period_start": 1000,
"period_end": 1500,
}
],
}
],
"arbitration_cases": [
{
"organization_inn": "7707083893",
@@ -281,9 +327,13 @@ class ExchangePackageApiTest(APITestCase):
self.assertEqual(response.data["result"]["organizations"]["created"], 1)
self.assertEqual(response.data["result"]["organizations"]["updated"], 1)
self.assertEqual(Organization.objects.count(), 2)
self.assertEqual(IndustrialCertificate.objects.count(), 1)
self.assertEqual(ManufacturerRegistryEntry.objects.count(), 1)
self.assertEqual(IndustrialProduct.objects.count(), 1)
self.assertEqual(ProsecutorCheck.objects.count(), 1)
self.assertEqual(PublicProcurement.objects.count(), 1)
self.assertEqual(FinancialReport.objects.count(), 1)
self.assertEqual(FinancialReportLine.objects.count(), 1)
self.assertEqual(ArbitrationCase.objects.count(), 1)
self.assertEqual(BankruptcyProcedure.objects.count(), 1)
self.assertEqual(DefenseUnreliableSupplier.objects.count(), 1)
@@ -293,6 +343,10 @@ class ExchangePackageApiTest(APITestCase):
response.data["result"]["bankruptcy_procedures"]["created"],
1,
)
self.assertEqual(
response.data["result"]["financial_reports"]["created_lines"],
1,
)
self.assertEqual(
response.data["result"]["defense_unreliable_suppliers"]["created"],
1,
@@ -332,6 +386,43 @@ class ExchangePackageApiTest(APITestCase):
self.assertEqual(ExchangePackageImport.objects.count(), 0)
self.assertEqual(Organization.objects.count(), 0)
def test_upload_rejects_external_rows_for_organization_absent_from_package(self):
Organization.objects.create(
inn="7707083893",
name="АО Альфа",
ogrn="1027700132195",
kpp="770701001",
)
archive = build_exchange_archive(
package_id="pkg-missing-package-organization",
data={
"organizations": [],
"industrial_products": [
{
"organization_inn": "7707083893",
"product_name": "Система связи М-1",
"registry_number": "prod-001",
}
],
},
)
response = self.client.post(
self.url,
{"file": archive},
format="multipart",
HTTP_X_EXCHANGE_TOKEN=TEST_TOKEN,
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertIn("отсутствует в разделе organizations", response.data["file"][0])
self.assertEqual(Organization.objects.count(), 1)
self.assertEqual(IndustrialProduct.objects.count(), 0)
package_import = ExchangePackageImport.objects.get(
package_id="pkg-missing-package-organization"
)
self.assertEqual(package_import.status, "failed")
def test_upload_is_idempotent_for_duplicate_package(self):
archive = build_exchange_archive(
package_id="pkg-duplicate-001",

View File

@@ -5,9 +5,13 @@ from apps.external_data.models import (
ArbitrationCase,
BankruptcyProcedure,
DefenseUnreliableSupplier,
FinancialReport,
FinancialReportLine,
IndustrialCertificate,
IndustrialProduct,
InformationSecurityRegistryEntry,
LaborVacancy,
ManufacturerRegistryEntry,
ProsecutorCheck,
PublicProcurement,
)
@@ -30,6 +34,32 @@ class IndustrialProductFactory(factory.django.DjangoModelFactory):
registry_number = factory.Sequence(lambda n: f"{1000 + n}/2026")
class IndustrialCertificateFactory(factory.django.DjangoModelFactory):
class Meta:
model = IndustrialCertificate
organization = factory.SubFactory(OrganizationFactory)
certificate_number = factory.Sequence(lambda n: f"CERT-{n:04d}")
issue_date = factory.LazyAttribute(lambda _: fake.date_this_year())
expiry_date = factory.LazyAttribute(lambda _: fake.date_this_decade())
certificate_file_url = factory.Sequence(
lambda n: f"https://minpromtorg.gov.ru/cert/{n}"
)
organisation_name = factory.LazyAttribute(lambda obj: obj.organization.name)
ogrn = factory.LazyAttribute(lambda obj: obj.organization.ogrn)
class ManufacturerRegistryEntryFactory(factory.django.DjangoModelFactory):
class Meta:
model = ManufacturerRegistryEntry
organization = factory.SubFactory(OrganizationFactory)
full_legal_name = factory.LazyAttribute(lambda obj: obj.organization.name)
inn = factory.LazyAttribute(lambda obj: obj.organization.inn)
ogrn = factory.LazyAttribute(lambda obj: obj.organization.ogrn)
address = factory.LazyAttribute(lambda _: fake.address())
class ProsecutorCheckFactory(factory.django.DjangoModelFactory):
class Meta:
model = ProsecutorCheck
@@ -126,3 +156,30 @@ class LaborVacancyFactory(factory.django.DjangoModelFactory):
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}")
class FinancialReportFactory(factory.django.DjangoModelFactory):
class Meta:
model = FinancialReport
organization = factory.SubFactory(OrganizationFactory)
external_id = factory.Sequence(lambda n: f"fin-{n:04d}")
ogrn = factory.LazyAttribute(lambda obj: obj.organization.ogrn)
file_name = factory.Sequence(lambda n: f"fin_{n:04d}.xlsx")
file_hash = factory.Sequence(lambda n: f"{n:064x}"[-64:])
load_batch = factory.Sequence(lambda n: n + 1)
status = "success"
source = "api"
class FinancialReportLineFactory(factory.django.DjangoModelFactory):
class Meta:
model = FinancialReportLine
report = factory.SubFactory(FinancialReportFactory)
form_code = "1"
line_code = "1600"
line_name = "Баланс"
year = 2025
period_start = 1000
period_end = 1500

View File

@@ -12,9 +12,13 @@ from tests.apps.external_data.factories import (
ArbitrationCaseFactory,
BankruptcyProcedureFactory,
DefenseUnreliableSupplierFactory,
FinancialReportFactory,
FinancialReportLineFactory,
IndustrialCertificateFactory,
IndustrialProductFactory,
InformationSecurityRegistryEntryFactory,
LaborVacancyFactory,
ManufacturerRegistryEntryFactory,
ProsecutorCheckFactory,
PublicProcurementFactory,
)
@@ -48,6 +52,33 @@ class ExternalDataApiTest(APITestCase):
response.data["results"][0]["organization"], str(self.organization.id)
)
def test_manufacturing_source_endpoints_are_exposed(self):
IndustrialCertificateFactory.create(
organization=self.organization,
certificate_number="CERT-001",
issue_date=date(2026, 1, 10),
)
ManufacturerRegistryEntryFactory.create(
organization=self.organization,
full_legal_name="АО Альфа",
)
IndustrialCertificateFactory.create(organization=self.other_organization)
ManufacturerRegistryEntryFactory.create(organization=self.other_organization)
certificates_response = self.client.get(
f"/api/v1/industrial-certificates/?organization={self.organization.id}"
"&issue_date_from=2026-01-01&issue_date_to=2026-12-31"
)
manufacturers_response = self.client.get(
f"/api/v1/manufacturers/?organization={self.organization.id}"
"&search=Альфа"
)
self.assertEqual(certificates_response.status_code, status.HTTP_200_OK)
self.assertEqual(manufacturers_response.status_code, status.HTTP_200_OK)
self.assertEqual(certificates_response.data["count"], 1)
self.assertEqual(manufacturers_response.data["count"], 1)
def test_prosecutor_checks_support_date_range_filters(self):
ProsecutorCheckFactory.create(
organization=self.organization,
@@ -163,3 +194,20 @@ class ExternalDataApiTest(APITestCase):
vacancies_response.data["results"][0]["title"],
"Инженер-испытатель",
)
def test_financial_reports_endpoint_returns_lines(self):
report = FinancialReportFactory.create(
organization=self.organization,
status="success",
)
FinancialReportLineFactory.create(report=report, line_code="1600")
FinancialReportFactory.create(organization=self.other_organization)
response = self.client.get(
f"/api/v1/financial-reports/?organization={self.organization.id}"
"&status=success"
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data["count"], 1)
self.assertEqual(response.data["results"][0]["lines"][0]["line_code"], "1600")

View File

@@ -18,7 +18,8 @@ class FormF1RecordFactory(factory.django.DjangoModelFactory):
organization = factory.SubFactory(OrganizationFactory)
load_batch = factory.Sequence(lambda n: n + 1)
report_year = 2026
report_quarter = 1
report_month = 1
report_quarter = None
# Выпуск военной продукции (факт. цены)
military_output_actual = factory.LazyAttribute(

View File

@@ -53,14 +53,16 @@ class FormF1ServiceTest(TestCase):
organization=org,
load_batch=501,
report_year=2026,
report_quarter=1,
report_month=9,
report_quarter=None,
)
current = FormF1Service.create_versioned_record(
organization=org,
load_batch=502,
report_year=2026,
report_quarter=1,
report_quarter=None,
extra_period_fields={"report_month": 9},
military_output_actual=150,
)
@@ -69,6 +71,8 @@ class FormF1ServiceTest(TestCase):
self.assertEqual(previous.superseded_by_batch, 502)
self.assertIsNotNone(previous.superseded_at)
self.assertTrue(current.is_active_version)
self.assertEqual(current.report_month, 9)
self.assertIsNone(current.report_quarter)
class FormF1ParserTest(TestCase):
@@ -76,7 +80,7 @@ class FormF1ParserTest(TestCase):
def test_get_column_mappings_returns_mappings(self):
"""Test get_column_mappings returns correct mappings."""
parser = FormF1Parser(report_year=2026, report_quarter=1)
parser = FormF1Parser(report_year=2026, report_month=9)
mappings = parser.get_column_mappings()
self.assertIsInstance(mappings, list)
@@ -87,10 +91,11 @@ class FormF1ParserTest(TestCase):
self.assertIn("military_output_actual", field_names)
self.assertIn("civilian_output_actual", field_names)
def test_create_record_creates_organization(self):
"""Test create_record creates organization if not exists."""
parser = FormF1Parser(report_year=2026, report_quarter=1)
def test_create_record_uses_existing_organization(self):
"""Report imports must attach rows only to preloaded Mostovik organizations."""
parser = FormF1Parser(report_year=2026, report_month=9)
parser.load_batch = 505
organization = OrganizationFactory.create(inn="1234567890")
row_data = {
"inn": "1234567890",
@@ -103,7 +108,9 @@ class FormF1ParserTest(TestCase):
record = parser.create_record(row_data)
self.assertIsNotNone(record)
self.assertEqual(record.organization.inn, "1234567890")
self.assertEqual(record.organization_id, organization.id)
self.assertEqual(record.military_output_actual, 100.0)
self.assertEqual(record.report_year, 2026)
self.assertEqual(record.report_quarter, 1)
self.assertEqual(record.report_month, 9)
self.assertIsNone(record.report_quarter)
self.assertEqual(record.report_period_display, "Сентябрь 2026")

View File

@@ -60,10 +60,11 @@ class FormF2ParserTest(TestCase):
self.assertIn("total_assets", field_names)
self.assertIn("revenue", field_names)
def test_create_record_creates_organization(self):
"""Test create_record creates organization if not exists."""
def test_create_record_uses_existing_organization(self):
"""Report imports must attach rows only to preloaded Mostovik organizations."""
parser = FormF2Parser(report_year=2026, report_quarter=1)
parser.load_batch = 502
organization = OrganizationFactory.create(inn="2234567890")
row_data = {
"inn": "2234567890",
@@ -75,4 +76,4 @@ class FormF2ParserTest(TestCase):
record = parser.create_record(row_data)
self.assertIsNotNone(record)
self.assertEqual(record.organization.inn, "2234567890")
self.assertEqual(record.organization_id, organization.id)

View File

@@ -60,10 +60,11 @@ class FormF3ParserTest(TestCase):
self.assertIn("avg_employees", field_names)
self.assertIn("total_equipment", field_names)
def test_create_record_creates_organization(self):
"""Test create_record creates organization if not exists."""
def test_create_record_uses_existing_organization(self):
"""Report imports must attach rows only to preloaded Mostovik organizations."""
parser = FormF3Parser(report_year=2026, report_quarter=1)
parser.load_batch = 503
organization = OrganizationFactory.create(inn="3234567890")
row_data = {
"inn": "3234567890",
@@ -75,4 +76,4 @@ class FormF3ParserTest(TestCase):
record = parser.create_record(row_data)
self.assertIsNotNone(record)
self.assertEqual(record.organization.inn, "3234567890")
self.assertEqual(record.organization_id, organization.id)

View File

@@ -18,7 +18,8 @@ class FormF4RecordFactory(factory.django.DjangoModelFactory):
organization = factory.SubFactory(OrganizationFactory)
load_batch = factory.Sequence(lambda n: n + 1)
report_year = 2026
report_quarter = 1
report_half_year = 1
report_quarter = None
# Выручка
revenue_rsbu = factory.LazyAttribute(

View File

@@ -50,7 +50,7 @@ class FormF4ParserTest(TestCase):
def test_get_column_mappings_returns_mappings(self):
"""Test get_column_mappings returns correct mappings."""
parser = FormF4Parser(report_year=2026, report_quarter=1)
parser = FormF4Parser(report_year=2026, report_half_year=1)
mappings = parser.get_column_mappings()
self.assertIsInstance(mappings, list)
@@ -60,10 +60,11 @@ class FormF4ParserTest(TestCase):
self.assertIn("revenue_rsbu", field_names)
self.assertIn("net_profit_rsbu", field_names)
def test_create_record_creates_organization(self):
"""Test create_record creates organization if not exists."""
parser = FormF4Parser(report_year=2026, report_quarter=1)
def test_create_record_uses_existing_organization(self):
"""Report imports must attach rows only to preloaded Mostovik organizations."""
parser = FormF4Parser(report_year=2026, report_half_year=1)
parser.load_batch = 504
organization = OrganizationFactory.create(inn="4234567890")
row_data = {
"inn": "4234567890",
@@ -75,4 +76,7 @@ class FormF4ParserTest(TestCase):
record = parser.create_record(row_data)
self.assertIsNotNone(record)
self.assertEqual(record.organization.inn, "4234567890")
self.assertEqual(record.organization_id, organization.id)
self.assertEqual(record.report_half_year, 1)
self.assertIsNone(record.report_quarter)
self.assertEqual(record.report_period_display, "I полугодие 2026")

View File

@@ -86,10 +86,11 @@ class FormF5ParserTest(TestCase):
self.assertIn("equipment_id", field_names)
self.assertIn("name", field_names)
def test_create_record_creates_organization(self):
"""Test create_record creates organization if not exists."""
def test_create_record_uses_existing_organization(self):
"""Report imports must attach rows only to preloaded Mostovik organizations."""
parser = FormF5Parser(report_year=2026, report_quarter=1)
parser.load_batch = 505
organization = OrganizationFactory.create(inn="5234567890")
row_data = {
"inn": "5234567890",
@@ -101,6 +102,6 @@ class FormF5ParserTest(TestCase):
record = parser.create_record(row_data)
self.assertIsNotNone(record)
self.assertEqual(record.organization.inn, "5234567890")
self.assertEqual(record.organization_id, organization.id)
self.assertEqual(record.report_year, 2026)
self.assertEqual(record.report_quarter, 1)

View File

@@ -60,10 +60,11 @@ class FormF6ParserTest(TestCase):
self.assertIn("row_code", field_names)
self.assertIn("total_equipment", field_names)
def test_create_record_creates_organization(self):
"""Test create_record creates organization if not exists."""
def test_create_record_uses_existing_organization(self):
"""Report imports must attach rows only to preloaded Mostovik organizations."""
parser = FormF6Parser(report_year=2026, report_quarter=1)
parser.load_batch = 506
organization = OrganizationFactory.create(inn="6234567890")
row_data = {
"inn": "6234567890",
@@ -76,4 +77,4 @@ class FormF6ParserTest(TestCase):
record = parser.create_record(row_data)
self.assertIsNotNone(record)
self.assertEqual(record.organization.inn, "6234567890")
self.assertEqual(record.organization_id, organization.id)

View File

@@ -1,4 +1,4 @@
"""Contract tests for F-2…F-6 upload endpoints."""
"""Contract tests for F-1…F-6 upload endpoints."""
from __future__ import annotations
@@ -16,7 +16,7 @@ from tests.apps.user.factories import UserFactory
@override_settings(ROOT_URLCONF="core.urls")
class FormUploadContractsApiTest(APITestCase):
"""Contract tests for multipart upload endpoints Ф-2 ... Ф-6."""
"""Contract tests for multipart upload endpoints Ф-1 ... Ф-6."""
BACKGROUND_THRESHOLD_PLUS = 1024 * 1024 + 1
RESULT_PAYLOAD = {
@@ -26,6 +26,16 @@ class FormUploadContractsApiTest(APITestCase):
"errors": [],
}
CASES = {
"f1": {
"url": "/api/v1/forms/f1/upload/",
"form": "f1",
"payload": {"report_year": 2026, "report_month": 9},
"period_field": "report_month",
"period_value": 9,
"report_period_display": "Сентябрь 2026",
"parse_target": "apps.form_1.api.parse_form_f1_file",
"task_target": "apps.form_1.api.process_form_f1_file",
},
"f2": {
"url": "/api/v1/forms/f2/upload/",
"form": "f2",
@@ -109,6 +119,7 @@ class FormUploadContractsApiTest(APITestCase):
if case["period_field"]:
self.assertEqual(response_data[case["period_field"]], case["period_value"])
else:
self.assertNotIn("report_month", response_data)
self.assertNotIn("report_quarter", response_data)
self.assertNotIn("report_half_year", response_data)

View File

@@ -1,6 +1,7 @@
"""Tests for Organization services."""
from apps.organization.services import OrganizationService
from apps.organization.models import Organization
from apps.organization.services import OrganizationNotFoundError, OrganizationService
from django.test import TestCase
from .factories import OrganizationFactory
@@ -9,15 +10,15 @@ from .factories import OrganizationFactory
class OrganizationServiceTest(TestCase):
"""Tests for OrganizationService."""
def test_get_or_create_by_inn_creates_new(self):
"""Test get_or_create_by_inn creates new organization."""
org, created = OrganizationService.get_or_create_by_inn(
inn="1234567890",
defaults={"name": "Test Org", "ogrn": "1234567890123"},
)
self.assertTrue(created)
self.assertEqual(org.inn, "1234567890")
self.assertEqual(org.name, "Test Org")
def test_get_or_create_by_inn_rejects_missing_organization(self):
"""OrganizationService must not create organizations outside exchange import."""
with self.assertRaises(OrganizationNotFoundError):
OrganizationService.get_or_create_by_inn(
inn="1234567890",
defaults={"name": "Test Org", "ogrn": "1234567890123"},
)
self.assertEqual(Organization.objects.count(), 0)
def test_get_or_create_by_inn_returns_existing(self):
"""Test get_or_create_by_inn returns existing organization."""

View File

@@ -108,6 +108,20 @@ class RegisterBackupImportServiceTest(TestCase):
)
def test_import_backup_archive_creates_registers_uploads_and_periods(self):
Organization.objects.create(
name="АО Альфа",
inn="7707083893",
ogrn="1027700132195",
kpp="770701001",
okpo="12345678",
)
Organization.objects.create(
name="АО Бета",
inn="7707083894",
ogrn="1027700132196",
kpp="770701002",
okpo="12345679",
)
uploaded_file = build_backup_archive(
actual_date=date(2026, 3, 15),
data={
@@ -174,8 +188,9 @@ class RegisterBackupImportServiceTest(TestCase):
)
self.assertEqual(result["registers_created"], 1)
self.assertEqual(result["organizations_created"], 2)
self.assertEqual(result["organizations_created"], 0)
self.assertEqual(result["organizations_updated"], 0)
self.assertEqual(result["organizations_skipped"], 0)
self.assertEqual(result["uploads_created"], 1)
self.assertEqual(result["periods_imported"], 2)
self.assertEqual(result["closed_periods"], 0)
@@ -201,7 +216,7 @@ class RegisterBackupImportServiceTest(TestCase):
2,
)
def test_import_backup_archive_closes_missing_periods_and_updates_orgs(self):
def test_import_backup_archive_closes_missing_periods_and_skips_unknown_orgs(self):
register = Register.objects.create(name="Реестр предприятий ОПК")
existing_upload = RegisterUpload.objects.create(
registry=register,
@@ -312,28 +327,22 @@ class RegisterBackupImportServiceTest(TestCase):
)
self.assertEqual(result["registers_created"], 0)
self.assertEqual(result["organizations_created"], 1)
self.assertEqual(result["organizations_updated"], 1)
self.assertEqual(result["organizations_created"], 0)
self.assertEqual(result["organizations_updated"], 0)
self.assertEqual(result["organizations_skipped"], 1)
self.assertEqual(result["uploads_created"], 1)
self.assertEqual(result["periods_imported"], 2)
self.assertEqual(result["periods_imported"], 1)
self.assertEqual(result["closed_periods"], 1)
self.assertEqual(result["active_periods"], 2)
self.assertEqual(result["active_periods"], 1)
alpha_period = RegistryMembershipPeriod.objects.get(
registry=register,
organization=alpha,
)
beta.refresh_from_db()
gamma = Organization.objects.get(inn="7707083895")
gamma_period = RegistryMembershipPeriod.objects.get(
registry=register,
organization=gamma,
ended_at__isnull=True,
)
self.assertEqual(alpha_period.ended_at, date(2026, 3, 15))
self.assertIsNone(alpha_period.ended_by_upload)
self.assertEqual(beta.name, "АО Бета обновленная")
self.assertEqual(beta.kpp, "770701099")
self.assertEqual(gamma.name, "АО Гамма")
self.assertEqual(gamma_period.started_at, date(2026, 3, 15))
self.assertEqual(beta.name, "АО Бета")
self.assertEqual(beta.kpp, "770701002")
self.assertFalse(Organization.objects.filter(inn="7707083895").exists())

View File

@@ -51,7 +51,21 @@ class RegisterImportServiceTest(TestCase):
self.registry = Register.objects.create(name="Реестр предприятий ОПК")
def test_sync_registry_memberships_creates_snapshot(self):
"""First upload creates organizations, upload fact and active periods."""
"""First upload links only organizations already loaded from Mostovik."""
Organization.objects.create(
name="АО Альфа",
inn="7707083893",
ogrn="1027700132195",
okpo="12345678",
kpp="770701001",
)
Organization.objects.create(
name="АО Бета",
inn="7707083894",
ogrn="1027700132196",
okpo="12345679",
kpp="770701002",
)
uploaded_file = build_registry_upload(
"registry.xlsx",
[
@@ -79,7 +93,9 @@ class RegisterImportServiceTest(TestCase):
)
self.assertEqual(result["rows_in_file"], 2)
self.assertEqual(result["organizations_created"], 2)
self.assertEqual(result["organizations_created"], 0)
self.assertEqual(result["organizations_updated"], 0)
self.assertEqual(result["organizations_skipped"], 0)
self.assertEqual(result["opened_periods"], 2)
self.assertEqual(Organization.objects.count(), 2)
self.assertEqual(RegisterUpload.objects.count(), 1)
@@ -88,8 +104,22 @@ class RegisterImportServiceTest(TestCase):
2,
)
def test_sync_registry_memberships_closes_missing_and_opens_new(self):
"""Next snapshot closes missing orgs and opens periods for new ones."""
def test_sync_registry_memberships_closes_missing_and_skips_unknown_orgs(self):
"""Next snapshot closes missing orgs but does not create unknown ones."""
Organization.objects.create(
name="АО Альфа",
inn="7707083893",
ogrn="1027700132195",
okpo="12345678",
kpp="770701001",
)
Organization.objects.create(
name="АО Бета",
inn="7707083894",
ogrn="1027700132196",
okpo="12345679",
kpp="770701002",
)
first_upload = build_registry_upload(
"registry-1.xlsx",
[
@@ -140,14 +170,14 @@ class RegisterImportServiceTest(TestCase):
actual_date=date(2026, 4, 1),
)
self.assertEqual(result["organizations_created"], 1)
self.assertEqual(result["organizations_updated"], 1)
self.assertEqual(result["opened_periods"], 1)
self.assertEqual(result["organizations_created"], 0)
self.assertEqual(result["organizations_updated"], 0)
self.assertEqual(result["organizations_skipped"], 1)
self.assertEqual(result["opened_periods"], 0)
self.assertEqual(result["closed_periods"], 1)
alpha = Organization.objects.get(inn="7707083893")
beta = Organization.objects.get(inn="7707083894")
gamma = Organization.objects.get(inn="7707083895")
alpha_period = RegistryMembershipPeriod.objects.get(
registry=self.registry,
@@ -158,15 +188,10 @@ class RegisterImportServiceTest(TestCase):
organization=beta,
ended_at__isnull=True,
)
gamma_period = RegistryMembershipPeriod.objects.get(
registry=self.registry,
organization=gamma,
ended_at__isnull=True,
)
self.assertEqual(alpha_period.ended_at, date(2026, 4, 1))
self.assertEqual(beta_period.started_at, date(2026, 3, 1))
self.assertEqual(gamma_period.started_at, date(2026, 4, 1))
self.assertFalse(Organization.objects.filter(inn="7707083895").exists())
beta.refresh_from_db()
self.assertEqual(beta.name, "АО Бета обновлённое")
self.assertEqual(beta.kpp, "770701099")
self.assertEqual(beta.name, "АО Бета")
self.assertEqual(beta.kpp, "770701002")