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
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:
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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."""
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user