Files
mostovik-backend/tests/apps/parsers/test_direct_ingestion_services.py
Aleksandr Meshchriakov 4be9e702c3
All checks were successful
CI/CD Pipeline / Quality Gate (push) Successful in 30s
CI/CD Pipeline / Build and Push Images (push) Successful in 0s
CI/CD Pipeline / Internal Notify (push) Successful in 1s
CI/CD Pipeline / Deploy Dev via Compose (push) Successful in 0s
CI/CD Pipeline / Quality Gate (pull_request) Successful in 31s
CI/CD Pipeline / Build and Push Images (pull_request) Successful in 1s
CI/CD Pipeline / Internal Notify (pull_request) Successful in 1s
CI/CD Pipeline / Deploy Dev via Compose (pull_request) Successful in 1s
fix: align source API and exchange demo data
2026-07-22 17:20:39 +02:00

383 lines
14 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Tests for parser services writing directly to organization source storage."""
from decimal import Decimal
from apps.parsers.clients.common import GenericParserItem
from apps.parsers.clients.fns.schemas import ReportLine
from apps.parsers.clients.minpromtorg.schemas import (
IndustrialCertificate,
IndustrialProduct,
Manufacturer,
)
from apps.parsers.clients.proverki.schemas import Inspection
from apps.parsers.clients.zakupki.schemas import Procurement
from apps.parsers.models import (
FinancialReport,
GenericParserRecord,
IndustrialCertificateRecord,
IndustrialProductRecord,
InspectionRecord,
ManufacturerRecord,
ParserLoadLog,
ProcurementRecord,
)
from apps.parsers.services import (
FNSReportService,
GenericParserRecordService,
IndustrialCertificateService,
IndustrialProductService,
InspectionService,
ManufacturerService,
ProcurementService,
)
from django.test import TestCase
from organizations.models import (
OrganizationSourceFinancialLine,
OrganizationSourceRecord,
)
from tests.apps.parsers.organization_helpers import create_directory_organization
class DirectIngestionParserServicesTest(TestCase):
"""Parser save services should not write legacy parser record rows."""
def setUp(self):
for inn, ogrn in (
("7707083801", "1027700132001"),
("7707083802", "1027700132002"),
("7707083803", ""),
("7707083804", "1027700132004"),
("", "1027700132005"),
("", "1027700132006"),
("7707083809", "1027700132009"),
("7707083810", "1027700132010"),
):
create_directory_organization(
name=f"Справочник {inn or ogrn}",
inn=inn,
ogrn=ogrn,
kpp="770701001" if inn == "7707083810" else "",
okpo="",
)
def test_industrial_certificate_save_records_writes_organization_source_records(
self,
):
saved = IndustrialCertificateService.save_certificates(
[
IndustrialCertificate(
issue_date="01.02.2026",
certificate_number="CERT-DIRECT-1",
expiry_date="2029-02-01",
certificate_file_url="https://example.test/cert.pdf",
organisation_name='ООО "Сертификат"',
inn="7707083801",
ogrn="1027700132001",
)
],
batch_id=47,
)
self.assertEqual(saved, 1)
self.assertEqual(IndustrialCertificateRecord.objects.count(), 0)
record = OrganizationSourceRecord.objects.get(
source=ParserLoadLog.Source.INDUSTRIAL,
external_id="CERT-DIRECT-1",
)
self.assertEqual(record.record_type, "industrial_certificate")
self.assertEqual(record.payload["issue_date_normalized"], "2026-02-01")
self.assertEqual(record.payload["expiry_date_normalized"], "2029-02-01")
self.assertEqual(record.url, "https://example.test/cert.pdf")
def test_industrial_certificate_save_records_skips_records_without_certificate_number(
self,
):
saved = IndustrialCertificateService.save_certificates(
[
IndustrialCertificate(
issue_date="01.02.2026",
certificate_number="",
expiry_date="2029-02-01",
certificate_file_url="-",
organisation_name='ООО "Без номера"',
inn="7707083801",
ogrn="1027700132001",
),
IndustrialCertificate(
issue_date="01.02.2026",
certificate_number="CERT-DIRECT-2",
expiry_date="2029-02-01",
certificate_file_url="https://example.test/cert-2.pdf",
organisation_name='ООО "С номером"',
inn="7707083802",
ogrn="1027700132002",
),
],
batch_id=48,
)
self.assertEqual(saved, 1)
self.assertEqual(
OrganizationSourceRecord.objects.filter(
source=ParserLoadLog.Source.INDUSTRIAL,
).count(),
1,
)
self.assertTrue(
OrganizationSourceRecord.objects.filter(
source=ParserLoadLog.Source.INDUSTRIAL,
external_id="CERT-DIRECT-2",
).exists()
)
self.assertFalse(
OrganizationSourceRecord.objects.filter(
source=ParserLoadLog.Source.INDUSTRIAL,
external_id="",
).exists()
)
def test_manufacturer_save_records_writes_organization_source_records(self):
saved = ManufacturerService.save_manufacturers(
[
Manufacturer(
full_legal_name='ООО "Производитель"',
inn="7707083802",
ogrn="1027700132002",
address="Москва",
)
],
batch_id=48,
)
self.assertEqual(saved, 1)
self.assertEqual(ManufacturerRecord.objects.count(), 0)
record = OrganizationSourceRecord.objects.get(
source=ParserLoadLog.Source.MANUFACTURES,
external_id="7707083802",
)
self.assertEqual(record.record_type, "manufacturer")
self.assertEqual(record.title, 'ООО "Производитель"')
self.assertEqual(record.payload["address"], "Москва")
def test_industrial_product_save_records_writes_organization_source_records(self):
saved = IndustrialProductService.save_products(
[
IndustrialProduct(
full_organisation_name='ООО "Продукция"',
inn="7707083809",
ogrn="1027700132009",
registry_number="PROD-DIRECT-1",
product_name="Станок",
product_model="MODEL-1",
okpd2_code="28.41",
tnved_code="8457109000",
regulatory_document="ГОСТ",
)
],
batch_id=49,
)
self.assertEqual(saved, 1)
self.assertEqual(IndustrialProductRecord.objects.count(), 0)
record = OrganizationSourceRecord.objects.get(
source=ParserLoadLog.Source.INDUSTRIAL_PRODUCTS,
external_id="PROD-DIRECT-1",
)
self.assertEqual(record.record_type, "industrial_product")
self.assertEqual(record.title, "Станок")
self.assertEqual(record.payload["okpd2_code"], "28.41")
def test_procurement_save_records_writes_organization_source_records(self):
saved = ProcurementService.save_procurements(
[
Procurement(
purchase_number="PROC-DIRECT-1",
purchase_name="Поставка оборудования",
customer_inn="7707083810",
customer_kpp="770701001",
customer_ogrn="1027700132010",
customer_name='ООО "Заказчик"',
max_price="1 234 567,89",
currency_code="RUB",
placement_method="Аукцион",
publish_date="01.03.2026",
end_date="2026-03-15",
status="published",
law_type="44-FZ",
purchase_object_info="Оборудование",
href="https://example.test/procurement",
)
],
batch_id=50,
region_code="77",
data_year=2026,
data_month=3,
)
self.assertEqual(saved, 1)
self.assertEqual(ProcurementRecord.objects.count(), 0)
record = OrganizationSourceRecord.objects.get(
source=ParserLoadLog.Source.PROCUREMENTS,
external_id="PROC-DIRECT-1",
)
self.assertEqual(record.record_type, "procurement")
self.assertEqual(record.amount, Decimal("1234567.89"))
self.assertEqual(record.payload["publish_date_normalized"], "2026-03-01")
self.assertEqual(record.payload["region_code"], "77")
self.assertEqual(record.payload["data_month"], 3)
def test_generic_save_records_writes_organization_source_records(self):
saved = GenericParserRecordService.save_records(
[
GenericParserItem(
source=ParserLoadLog.Source.FAS_GOZ,
external_id="fas-goz-1",
inn="7707083803",
ogrn="",
organisation_name='ООО "ГОЗ"',
title="Уклонение от ГОЗ",
record_date="2026-05-18",
amount=Decimal("12.30"),
status="active",
url="https://example.test/fas-goz-1",
payload={"registry": "fas"},
)
],
batch_id=51,
source=ParserLoadLog.Source.FAS_GOZ,
)
self.assertEqual(saved, 1)
self.assertEqual(GenericParserRecord.objects.count(), 0)
record = OrganizationSourceRecord.objects.get(
source=ParserLoadLog.Source.FAS_GOZ,
external_id="fas-goz-1",
)
self.assertEqual(record.title, "Уклонение от ГОЗ")
self.assertEqual(record.payload["registry"], "fas")
self.assertEqual(record.load_batch, 51)
def test_vacancy_and_arbitration_dates_are_normalized_for_source_records(self):
for index, (source, raw_date, expected_date) in enumerate(
(
(ParserLoadLog.Source.TRUDVSEM, "04.12.2025", "2025-12-04"),
(ParserLoadLog.Source.ARBITRATION, "14.07.2026", "2026-07-14"),
),
start=1,
):
external_id = f"canonical-date-{index}"
saved = GenericParserRecordService.save_records(
[
GenericParserItem(
source=source,
external_id=external_id,
inn="7707083803",
ogrn="",
organisation_name='ООО "Каноническая дата"',
title="Запись с датой",
record_date=raw_date,
)
],
batch_id=60 + index,
source=source,
)
self.assertEqual(saved, 1)
record = OrganizationSourceRecord.objects.get(
source=source,
external_id=external_id,
)
self.assertEqual(record.record_date, expected_date)
self.assertEqual(record.payload["record_date"], expected_date)
def test_inspection_save_records_writes_organization_source_records(self):
saved = InspectionService.save_inspections(
[
Inspection(
registration_number="INSP-DIRECT-1",
inn="7707083804",
ogrn="1027700132004",
organisation_name='ООО "Проверка"',
control_authority="Контроль",
inspection_type="Плановая",
inspection_form="Документарная",
start_date="01.03.2026",
end_date="2026-03-15",
status="planned",
legal_basis="ФЗ",
result="",
)
],
batch_id=52,
data_year=2026,
data_month=3,
)
self.assertEqual(saved, 1)
self.assertEqual(InspectionRecord.objects.count(), 0)
record = OrganizationSourceRecord.objects.get(
source=ParserLoadLog.Source.INSPECTIONS,
external_id="INSP-DIRECT-1",
)
self.assertEqual(record.record_type, "inspection")
self.assertEqual(record.record_date, "2026-03-01")
self.assertEqual(record.payload["control_authority"], "Контроль")
self.assertEqual(record.payload["start_date_normalized"], "2026-03-01")
self.assertEqual(record.payload["data_year"], 2026)
self.assertEqual(record.payload["data_month"], 3)
def test_fns_save_report_writes_source_record_and_financial_lines(self):
report = FNSReportService.save_report(
external_id="fns-direct-1",
ogrn="1027700132005",
file_name="fin_001_1027700132005.xlsx",
file_hash="b" * 64,
source="file_watch",
batch_id=53,
lines_data=[
{
"form_code": "1",
"line_code": "1600",
"line_name": "Баланс",
"year": 2025,
"period_start": 100,
"period_end": 200,
}
],
)
self.assertEqual(FinancialReport.objects.count(), 0)
self.assertEqual(str(report.external_id), "fns-direct-1")
source_record = OrganizationSourceRecord.objects.get(
source=ParserLoadLog.Source.FNS_REPORTS,
external_id="fns-direct-1",
)
self.assertEqual(report.uid, source_record.uid)
self.assertEqual(source_record.payload["file_hash"], "b" * 64)
line = OrganizationSourceFinancialLine.objects.get(source_record=source_record)
self.assertEqual(line.line_code, "1600")
self.assertEqual(line.period_end, 200)
def test_fns_exists_by_hash_reads_source_record_payload(self):
FNSReportService.save_report(
external_id="fns-direct-2",
ogrn="1027700132006",
file_name="fin_002_1027700132006.xlsx",
file_hash="c" * 64,
source="file_watch",
batch_id=54,
lines_data=[
ReportLine(
form_code="2",
line_code="2110",
line_name="Выручка",
year=2025,
period_end=500,
).__dict__
],
)
self.assertTrue(FNSReportService.exists_by_hash("c" * 64))
self.assertTrue(FNSReportService.exists_by_external_id("fns-direct-2"))