fix: exclude provider records from source exports
All checks were successful
All checks were successful
This commit is contained in:
@@ -38,6 +38,9 @@ JavaScript. Ticket не попадает в URL и после первого з
|
|||||||
объединяет три таблицы, а поле `record_type` различает тип строки. Все строки
|
объединяет три таблицы, а поле `record_type` различает тип строки. Все строки
|
||||||
содержат реквизиты организации, включая ОКПО.
|
содержат реквизиты организации, включая ОКПО.
|
||||||
|
|
||||||
|
Записи технического внешнего поставщика в публичные файлы не включаются; в БД
|
||||||
|
они сохраняются для работы интеграции и дедупликации.
|
||||||
|
|
||||||
Физических XLSX-файлов может быть больше: по умолчанию один файл содержит не
|
Физических XLSX-файлов может быть больше: по умолчанию один файл содержит не
|
||||||
более 100 000 строк данных и получает суффикс `-part-001`, `-part-002` и далее.
|
более 100 000 строк данных и получает суффикс `-part-001`, `-part-002` и далее.
|
||||||
|
|
||||||
|
|||||||
@@ -54,6 +54,11 @@ DEFAULT_XLSX_DATA_ROWS_PER_FILE = 100_000
|
|||||||
DEFAULT_DOWNLOAD_TICKET_TTL_SECONDS = 5 * 60
|
DEFAULT_DOWNLOAD_TICKET_TTL_SECONDS = 5 * 60
|
||||||
SOURCE_RECORD_EXPORT_TICKET_CACHE_PREFIX = "external-data:source-record-exports:ticket"
|
SOURCE_RECORD_EXPORT_TICKET_CACHE_PREFIX = "external-data:source-record-exports:ticket"
|
||||||
SOURCE_RECORD_EXPORT_TICKET_PATTERN = re.compile(r"[A-Za-z0-9_-]{43}")
|
SOURCE_RECORD_EXPORT_TICKET_PATTERN = re.compile(r"[A-Za-z0-9_-]{43}")
|
||||||
|
EXCLUDED_EXPORT_PROVIDER_PATTERN = re.compile(
|
||||||
|
r"(?<![A-Za-zА-Яа-яЁё0-9])(?:checko(?:\.ru)?|чекало|чекко|чеко)"
|
||||||
|
r"(?![A-Za-zА-Яа-яЁё0-9])",
|
||||||
|
flags=re.IGNORECASE,
|
||||||
|
)
|
||||||
|
|
||||||
ORGANIZATION_EXPORT_FIELDS = [
|
ORGANIZATION_EXPORT_FIELDS = [
|
||||||
"Наименование",
|
"Наименование",
|
||||||
@@ -737,6 +742,8 @@ def _spool_source_group_rows(
|
|||||||
is_first_row = True
|
is_first_row = True
|
||||||
for model_spec in source_spec.models:
|
for model_spec in source_spec.models:
|
||||||
for record in _iter_source_model_records(model_spec):
|
for record in _iter_source_model_records(model_spec):
|
||||||
|
if _is_excluded_export_provider_record(record, model_spec=model_spec):
|
||||||
|
continue
|
||||||
row = _build_record_row(
|
row = _build_record_row(
|
||||||
record,
|
record,
|
||||||
source_spec=source_spec,
|
source_spec=source_spec,
|
||||||
@@ -911,6 +918,32 @@ def _build_record_row(
|
|||||||
return {key: _serialize_json_value(value) for key, value in row.items()}
|
return {key: _serialize_json_value(value) for key, value in row.items()}
|
||||||
|
|
||||||
|
|
||||||
|
def _is_excluded_export_provider_record(
|
||||||
|
record: Any,
|
||||||
|
*,
|
||||||
|
model_spec: SourceModelExportSpec,
|
||||||
|
) -> bool:
|
||||||
|
"""Return whether a provider-backed record must stay out of public files."""
|
||||||
|
return any(
|
||||||
|
_contains_excluded_export_provider(getattr(record, field_name))
|
||||||
|
for field_name in model_spec.fields
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _contains_excluded_export_provider(value: object) -> bool:
|
||||||
|
if isinstance(value, str):
|
||||||
|
return EXCLUDED_EXPORT_PROVIDER_PATTERN.search(value) is not None
|
||||||
|
if isinstance(value, dict):
|
||||||
|
return any(
|
||||||
|
_contains_excluded_export_provider(key)
|
||||||
|
or _contains_excluded_export_provider(item)
|
||||||
|
for key, item in value.items()
|
||||||
|
)
|
||||||
|
if isinstance(value, list | tuple):
|
||||||
|
return any(_contains_excluded_export_provider(item) for item in value)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def _serialize_flat_value(value: Any) -> str | int | float | bool:
|
def _serialize_flat_value(value: Any) -> str | int | float | bool:
|
||||||
if value is None:
|
if value is None:
|
||||||
return ""
|
return ""
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ from rest_framework import status
|
|||||||
from rest_framework.test import APITestCase
|
from rest_framework.test import APITestCase
|
||||||
|
|
||||||
from tests.apps.external_data.factories import (
|
from tests.apps.external_data.factories import (
|
||||||
|
BankruptcyProcedureFactory,
|
||||||
FinancialReportFactory,
|
FinancialReportFactory,
|
||||||
FinancialReportLineFactory,
|
FinancialReportLineFactory,
|
||||||
IndustrialCertificateFactory,
|
IndustrialCertificateFactory,
|
||||||
@@ -106,6 +107,42 @@ class SourceRecordExportApiTest(APITestCase):
|
|||||||
financial_rows = json.loads(financial_path.read_text(encoding="utf-8"))
|
financial_rows = json.loads(financial_path.read_text(encoding="utf-8"))
|
||||||
self.assertEqual(financial_rows[0]["financial_lines"][0]["line_code"], "1600")
|
self.assertEqual(financial_rows[0]["financial_lines"][0]["line_code"], "1600")
|
||||||
|
|
||||||
|
def test_generation_excludes_provider_records_and_keeps_okpo(self):
|
||||||
|
organization = OrganizationFactory.create(okpo="11223344")
|
||||||
|
excluded_record = BankruptcyProcedureFactory.create(
|
||||||
|
organization=organization,
|
||||||
|
external_id="checko-fedresurs:123",
|
||||||
|
source_url="https://api.checko.ru/v2/bankruptcy/123",
|
||||||
|
)
|
||||||
|
included_record = BankruptcyProcedureFactory.create(
|
||||||
|
organization=organization,
|
||||||
|
external_id="checkout-reference:456",
|
||||||
|
source_url="https://fedresurs.ru/message/456",
|
||||||
|
)
|
||||||
|
|
||||||
|
generation = build_source_record_export_artifacts()
|
||||||
|
|
||||||
|
self.assertEqual(generation.records_count, 1)
|
||||||
|
artifacts = [
|
||||||
|
artifact
|
||||||
|
for artifact in generation.artifacts
|
||||||
|
if artifact.source_group == "bankruptcy"
|
||||||
|
]
|
||||||
|
self.assertEqual(
|
||||||
|
{artifact.file_format for artifact in artifacts}, {"csv", "xlsx", "json"}
|
||||||
|
)
|
||||||
|
for artifact in artifacts:
|
||||||
|
if artifact.file_format == "json":
|
||||||
|
exported_values = json.loads(artifact.path.read_text(encoding="utf-8"))
|
||||||
|
elif artifact.file_format == "csv":
|
||||||
|
exported_values = artifact.path.read_text(encoding="utf-8-sig")
|
||||||
|
else:
|
||||||
|
workbook = load_workbook(artifact.path, read_only=True)
|
||||||
|
exported_values = list(workbook["data"].iter_rows(values_only=True))
|
||||||
|
self.assertNotIn(str(excluded_record.id), str(exported_values))
|
||||||
|
self.assertIn(str(included_record.id), str(exported_values))
|
||||||
|
self.assertIn("11223344", str(exported_values))
|
||||||
|
|
||||||
def test_management_command_bootstraps_first_generation(self):
|
def test_management_command_bootstraps_first_generation(self):
|
||||||
command_output = StringIO()
|
command_output = StringIO()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user