Add v2 registry uploads and source CSV exports
This commit is contained in:
@@ -151,3 +151,25 @@ class ParserDashboardPageTest(TestCase):
|
||||
self.assertIn("На конец</th>", content)
|
||||
self.assertIn("На начало</th>", content)
|
||||
self.assertIn("Покрытие доп. данными", content)
|
||||
|
||||
def test_dashboard_uses_v2_registry_upload_routes(self):
|
||||
response = self.client.get("/dashboard")
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
content = response.content.decode()
|
||||
self.assertIn("REGISTRY_UPLOAD_SLUGS_BY_NAME", content)
|
||||
self.assertIn("/api/v2/registers/${registrySlug}/upload/", content)
|
||||
self.assertIn("/api/v2/registers/opk/upload/", content)
|
||||
self.assertIn("registryUploadUrlForSelectedRegistry", content)
|
||||
|
||||
def test_dashboard_exposes_v2_source_csv_downloads(self):
|
||||
response = self.client.get("/dashboard")
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
content = response.content.decode()
|
||||
self.assertIn("function sourceCsvDownloadUrl", content)
|
||||
self.assertIn("/api/v2/sources/${source.api_route}/download/", content)
|
||||
self.assertIn("data-source-download", content)
|
||||
self.assertIn("downloadSourceCsv", content)
|
||||
self.assertIn("CSV", content)
|
||||
self.assertNotIn("/api/v2/sources/fns/reports/download/", content)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import csv
|
||||
import io
|
||||
import os
|
||||
import tempfile
|
||||
@@ -99,6 +100,41 @@ class ParsersViewSetTest(APITestCase):
|
||||
)
|
||||
self.assertEqual(detail.status_code, status.HTTP_200_OK)
|
||||
|
||||
def test_v2_source_csv_download_exports_source_rows(self):
|
||||
record = IndustrialCertificateRecordFactory(
|
||||
certificate_number="CERT-CSV-1",
|
||||
organisation_name='АО "CSV"',
|
||||
inn="7701000101",
|
||||
ogrn="1027700000001",
|
||||
)
|
||||
self.client.force_authenticate(self.user)
|
||||
|
||||
response = self.client.get(
|
||||
reverse("api_v2:parser_sources:minpromtorg-certificates-download")
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(response["Content-Type"], "text/csv; charset=utf-8")
|
||||
self.assertIn(
|
||||
'attachment; filename="minpromtorg-certificates.csv"',
|
||||
response["Content-Disposition"],
|
||||
)
|
||||
rows = list(csv.DictReader(io.StringIO(response.content.decode("utf-8"))))
|
||||
self.assertEqual(len(rows), 1)
|
||||
self.assertEqual(rows[0]["id"], str(record.id))
|
||||
self.assertEqual(rows[0]["source"], ParserLoadLog.Source.INDUSTRIAL)
|
||||
self.assertEqual(rows[0]["external_id"], "CERT-CSV-1")
|
||||
self.assertEqual(rows[0]["organisation_name"], 'АО "CSV"')
|
||||
self.assertEqual(rows[0]["inn"], "7701000101")
|
||||
self.assertIn("CERT-CSV-1", rows[0]["payload"])
|
||||
|
||||
def test_v2_source_csv_download_is_not_registered_for_financial_reports(self):
|
||||
self.client.force_authenticate(self.user)
|
||||
|
||||
response = self.client.get("/api/v2/sources/fns/reports/download/")
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
|
||||
|
||||
def test_manufacturers_list_and_retrieve(self):
|
||||
record = ManufacturerRecordFactory()
|
||||
second_record = ManufacturerRecordFactory()
|
||||
@@ -404,6 +440,10 @@ class ParsersViewSetTest(APITestCase):
|
||||
sources["procurements_44fz"]["result_list_url"],
|
||||
"/api/v1/parsers/results/procurements_44fz/",
|
||||
)
|
||||
self.assertEqual(
|
||||
sources["procurements_44fz"]["api_route"],
|
||||
"eis/procurements-44fz",
|
||||
)
|
||||
self.assertEqual(
|
||||
sources["procurements_223fz"]["result_list_url"],
|
||||
"/api/v1/parsers/results/procurements_223fz/",
|
||||
|
||||
@@ -160,6 +160,100 @@ class RegisterImportServiceTest(TestCase):
|
||||
self.assertEqual(existing.in_kpp, 444)
|
||||
self.assertEqual(existing.mn_okpo, "87654321")
|
||||
|
||||
def test_parse_xlsx_accepts_opk_source_header_aliases(self):
|
||||
upload = _upload(
|
||||
"opk.xlsx",
|
||||
[
|
||||
[
|
||||
"rn",
|
||||
"okpo",
|
||||
"ogrn",
|
||||
"inn",
|
||||
"filial",
|
||||
"ropk_num",
|
||||
"ropk_razdel_num",
|
||||
"ropk_razdel_name",
|
||||
"short_name",
|
||||
"full_name",
|
||||
],
|
||||
[
|
||||
1,
|
||||
"07506197",
|
||||
"1027600980990",
|
||||
"7601000086",
|
||||
"",
|
||||
"1",
|
||||
"1",
|
||||
"Раздел",
|
||||
'АО "ЯРЗ"',
|
||||
'АКЦИОНЕРНОЕ ОБЩЕСТВО "ЯРОСЛАВСКИЙ РАДИОЗАВОД"',
|
||||
],
|
||||
],
|
||||
)
|
||||
|
||||
rows = RegisterImportService.parse_xlsx(upload)
|
||||
|
||||
self.assertEqual(len(rows), 1)
|
||||
self.assertEqual(
|
||||
rows[0].pn_name,
|
||||
'АКЦИОНЕРНОЕ ОБЩЕСТВО "ЯРОСЛАВСКИЙ РАДИОЗАВОД"',
|
||||
)
|
||||
self.assertEqual(rows[0].mn_ogrn, 1027600980990)
|
||||
self.assertEqual(rows[0].mn_inn, 7601000086)
|
||||
self.assertIsNone(rows[0].in_kpp)
|
||||
self.assertEqual(rows[0].mn_okpo, "07506197")
|
||||
|
||||
def test_parse_xlsx_skips_opk_branch_rows_without_identity(self):
|
||||
upload = _upload(
|
||||
"opk.xlsx",
|
||||
[
|
||||
[
|
||||
"rn",
|
||||
"okpo",
|
||||
"ogrn",
|
||||
"inn",
|
||||
"filial",
|
||||
"ropk_num",
|
||||
"ropk_razdel_num",
|
||||
"ropk_razdel_name",
|
||||
"short_name",
|
||||
"full_name",
|
||||
],
|
||||
[
|
||||
100,
|
||||
"52511425",
|
||||
None,
|
||||
None,
|
||||
True,
|
||||
48,
|
||||
1,
|
||||
"Минпромторг России",
|
||||
'Филиал ПАО "Ил" - ВАСО',
|
||||
(
|
||||
'Филиал публичного акционерного общества "Авиационный '
|
||||
'комплекс им. С.В. Ильюшина" - ВАСО'
|
||||
),
|
||||
],
|
||||
[
|
||||
1,
|
||||
"07506197",
|
||||
"1027600980990",
|
||||
"7601000086",
|
||||
False,
|
||||
1,
|
||||
1,
|
||||
"Минпромторг России",
|
||||
'АО "ЯРЗ"',
|
||||
'АКЦИОНЕРНОЕ ОБЩЕСТВО "ЯРОСЛАВСКИЙ РАДИОЗАВОД"',
|
||||
],
|
||||
],
|
||||
)
|
||||
|
||||
rows = RegisterImportService.parse_xlsx(upload)
|
||||
|
||||
self.assertEqual(len(rows), 1)
|
||||
self.assertEqual(rows[0].mn_ogrn, 1027600980990)
|
||||
|
||||
def test_get_active_periods_by_org_returns_mapping(self):
|
||||
registry = RegisterFactory()
|
||||
active_period = RegistryMembershipPeriodFactory(
|
||||
|
||||
@@ -4,6 +4,7 @@ from __future__ import annotations
|
||||
|
||||
import io
|
||||
from datetime import date
|
||||
from unittest.mock import patch
|
||||
|
||||
from apps.registers.models import Organization, RegistryMembershipPeriod
|
||||
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||
@@ -94,6 +95,35 @@ class RegistersViewsTest(APITestCase):
|
||||
self.client.force_authenticate(self.user)
|
||||
return response
|
||||
|
||||
def _post_v2_slug_upload(
|
||||
self,
|
||||
*,
|
||||
slug: str,
|
||||
rows: list[dict],
|
||||
actual_date_value: date,
|
||||
with_kpp: bool = True,
|
||||
file_name: str = "registry.xlsx",
|
||||
):
|
||||
self.client.force_authenticate(self.admin)
|
||||
content = _build_register_excel_bytes(rows, with_kpp=with_kpp)
|
||||
upload = SimpleUploadedFile(
|
||||
file_name,
|
||||
content,
|
||||
content_type=(
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
||||
),
|
||||
)
|
||||
response = self.client.post(
|
||||
reverse(f"api_v2:registers:register-upload-{slug}"),
|
||||
{
|
||||
"actual_date": actual_date_value.isoformat(),
|
||||
"file": upload,
|
||||
},
|
||||
format="multipart",
|
||||
)
|
||||
self.client.force_authenticate(self.user)
|
||||
return response
|
||||
|
||||
def test_registries_list_and_retrieve(self):
|
||||
registry = RegisterFactory(name="Росатом")
|
||||
RegisterUploadFactory(registry=registry)
|
||||
@@ -130,6 +160,66 @@ class RegistersViewsTest(APITestCase):
|
||||
self.assertIn("Реестр госкорпорации Росатом ГОЗ", names)
|
||||
self.assertIn("Реестр госкорпорации Росатом ОПК", names)
|
||||
|
||||
def test_v2_registry_slug_upload_uses_fixed_registry_and_refreshes_snapshots(self):
|
||||
rows = [
|
||||
{
|
||||
"pn_name": 'АО "Росатом ГОЗ"',
|
||||
"mn_ogrn": "1027600980990",
|
||||
"mn_inn": "7601000086",
|
||||
"in_kpp": "760401001",
|
||||
"mn_okpo": "07506197",
|
||||
}
|
||||
]
|
||||
|
||||
with patch("registers.views._start_snapshot_refresh_task") as refresh_task:
|
||||
response = self._post_v2_slug_upload(
|
||||
slug="rosatom-goz",
|
||||
rows=rows,
|
||||
actual_date_value=date(2026, 5, 7),
|
||||
file_name="rosatom_goz.xlsx",
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||
self.assertTrue(response.data["success"])
|
||||
self.assertEqual(
|
||||
response.data["registry_name"],
|
||||
"Реестр госкорпорации Росатом ГОЗ",
|
||||
)
|
||||
refresh_task.assert_called_once_with()
|
||||
membership = RegistryMembershipPeriod.objects.get(ended_at__isnull=True)
|
||||
self.assertEqual(
|
||||
membership.registry.name,
|
||||
"Реестр госкорпорации Росатом ГОЗ",
|
||||
)
|
||||
|
||||
def test_v2_registry_slug_upload_url_does_not_duplicate_registers_segment(self):
|
||||
self.assertEqual(
|
||||
reverse("api_v2:registers:register-upload-rosatom-goz"),
|
||||
"/api/v2/registers/rosatom-goz/upload/",
|
||||
)
|
||||
|
||||
def test_v2_registry_slug_upload_does_not_refresh_snapshots_after_import_error(self):
|
||||
rows = [
|
||||
{
|
||||
"pn_name": 'АО "Невалидный ОКПО"',
|
||||
"mn_ogrn": "1027600980990",
|
||||
"mn_inn": "7601000086",
|
||||
"in_kpp": "760401001",
|
||||
"mn_okpo": "07A06197",
|
||||
}
|
||||
]
|
||||
|
||||
with patch("registers.views._start_snapshot_refresh_task") as refresh_task:
|
||||
response = self._post_v2_slug_upload(
|
||||
slug="rosatom-goz",
|
||||
rows=rows,
|
||||
actual_date_value=date(2026, 5, 7),
|
||||
file_name="invalid_rosatom_goz.xlsx",
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
refresh_task.assert_not_called()
|
||||
|
||||
def test_organizations_list_and_retrieve(self):
|
||||
organization = OrganizationFactory()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user