2 Commits

Author SHA1 Message Date
e75d4cd586 Merge dev into main for customer release
All checks were successful
CI/CD Pipeline / Quality Gate (push) Successful in 26s
CI/CD Pipeline / Build and Push Images (push) Successful in 0s
CI/CD Pipeline / Internal Notify (push) Successful in 0s
CI/CD Pipeline / Deploy Dev via Compose (push) Successful in 1s
CI/CD Pipeline / Quality Gate (pull_request) Successful in 24s
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
2026-07-19 20:35:29 +02:00
avm
1c05378968 Merge pull request 'Merge dev into main' (#32) from codex/merge-main-20260616 into main
Some checks failed
Deploy Customer Main / Build, Push, Deploy (push) Failing after 1s
CI/CD Pipeline / Quality Gate (push) Successful in 17s
CI/CD Pipeline / Build and Push Images (push) Successful in 1s
CI/CD Pipeline / Internal Notify (push) Successful in 0s
CI/CD Pipeline / Deploy Dev via Compose (push) Successful in 1s
2026-06-16 16:14:22 +03:00
6 changed files with 25 additions and 217 deletions

View File

@@ -35,7 +35,6 @@ from django.conf import settings
from django.db.models import Q from django.db.models import Q
from django.utils import timezone from django.utils import timezone
from organizations.models import Organization, OrganizationSourceRecord from organizations.models import Organization, OrganizationSourceRecord
from organizations.test_companies import TestCompanyDatasetService
class StateCorpExchangeError(ValueError): class StateCorpExchangeError(ValueError):
@@ -290,9 +289,7 @@ class StateCorpExchangeService:
@classmethod @classmethod
def _rosatom_roscosmos_queryset(cls): def _rosatom_roscosmos_queryset(cls):
queryset = Organization.objects.exclude(inn="").exclude( queryset = Organization.objects.exclude(inn="")
uid__in=TestCompanyDatasetService.company_uids()
)
name_query = Q() name_query = Q()
for keyword in cls.ROSATOM_ROSCOSMOS_GK_NAME_KEYWORDS: for keyword in cls.ROSATOM_ROSCOSMOS_GK_NAME_KEYWORDS:
name_query |= Q(gk_name__icontains=keyword) name_query |= Q(gk_name__icontains=keyword)

View File

@@ -701,8 +701,6 @@ class ParserResultQuerySerializer(serializers.Serializer):
batch_id = serializers.IntegerField(required=False, min_value=1) batch_id = serializers.IntegerField(required=False, min_value=1)
status = serializers.CharField(required=False, allow_blank=True) status = serializers.CharField(required=False, allow_blank=True)
record_date = serializers.CharField(required=False, allow_blank=True) record_date = serializers.CharField(required=False, allow_blank=True)
date_from = serializers.DateField(required=False)
date_to = serializers.DateField(required=False)
search = serializers.CharField(required=False, allow_blank=True) search = serializers.CharField(required=False, allow_blank=True)
ordering = serializers.CharField(required=False, allow_blank=True) ordering = serializers.CharField(required=False, allow_blank=True)
include_payload = serializers.BooleanField(required=False, default=True) include_payload = serializers.BooleanField(required=False, default=True)
@@ -714,14 +712,6 @@ class ParserResultQuerySerializer(serializers.Serializer):
attrs["page_size"] = attrs["limit"] attrs["page_size"] = attrs["limit"]
if attrs.get("batch_id") and not attrs.get("load_batch"): if attrs.get("batch_id") and not attrs.get("load_batch"):
attrs["load_batch"] = attrs["batch_id"] attrs["load_batch"] = attrs["batch_id"]
if (
attrs.get("date_from")
and attrs.get("date_to")
and attrs["date_from"] > attrs["date_to"]
):
raise serializers.ValidationError(
{"date_to": "date_to must be greater than or equal to date_from"}
)
return attrs return attrs

View File

@@ -10,7 +10,6 @@ import json
import uuid import uuid
from collections import defaultdict from collections import defaultdict
from apps.core.filters import BaseFilterSet
from apps.core.openapi import CommonResponses, ErrorResponses, swagger_tag from apps.core.openapi import CommonResponses, ErrorResponses, swagger_tag
from apps.core.response import api_error_response, api_response from apps.core.response import api_error_response, api_response
from apps.core.serializers import BackgroundJobListSerializer from apps.core.serializers import BackgroundJobListSerializer
@@ -74,7 +73,6 @@ from django.db.models.functions import Cast, Lower
from django.http import HttpResponse from django.http import HttpResponse
from django.utils.text import get_valid_filename from django.utils.text import get_valid_filename
from django_celery_beat.models import CrontabSchedule, IntervalSchedule, PeriodicTask from django_celery_beat.models import CrontabSchedule, IntervalSchedule, PeriodicTask
from django_filters import rest_framework as filters
from drf_yasg import openapi from drf_yasg import openapi
from drf_yasg.inspectors import SwaggerAutoSchema from drf_yasg.inspectors import SwaggerAutoSchema
from drf_yasg.utils import no_body, swagger_auto_schema from drf_yasg.utils import no_body, swagger_auto_schema
@@ -333,20 +331,6 @@ ORDERING_PARAM = openapi.Parameter(
description="Сортировка", description="Сортировка",
type=openapi.TYPE_STRING, type=openapi.TYPE_STRING,
) )
DATE_FROM_PARAM = openapi.Parameter(
"date_from",
openapi.IN_QUERY,
description="Дата записи с которой включительно отбирать результаты (YYYY-MM-DD)",
type=openapi.TYPE_STRING,
format=openapi.FORMAT_DATE,
)
DATE_TO_PARAM = openapi.Parameter(
"date_to",
openapi.IN_QUERY,
description="Дата записи по которую включительно отбирать результаты (YYYY-MM-DD)",
type=openapi.TYPE_STRING,
format=openapi.FORMAT_DATE,
)
INCLUDE_PAYLOAD_PARAM = openapi.Parameter( INCLUDE_PAYLOAD_PARAM = openapi.Parameter(
"include_payload", "include_payload",
openapi.IN_QUERY, openapi.IN_QUERY,
@@ -371,8 +355,6 @@ RESULT_LIST_PARAMS = [
OGRN_PARAM, OGRN_PARAM,
LOAD_BATCH_PARAM, LOAD_BATCH_PARAM,
STATUS_PARAM, STATUS_PARAM,
DATE_FROM_PARAM,
DATE_TO_PARAM,
SEARCH_PARAM, SEARCH_PARAM,
ORDERING_PARAM, ORDERING_PARAM,
INCLUDE_PAYLOAD_PARAM, INCLUDE_PAYLOAD_PARAM,
@@ -808,32 +790,6 @@ class IndustrialProductViewSet(ReadOnlyModelViewSet):
# ============================================================================= # =============================================================================
class InspectionFilter(BaseFilterSet):
"""Filters inspections by their normalized start date."""
date_from = filters.DateFilter(
field_name="start_date_normalized",
lookup_expr="gte",
)
date_to = filters.DateFilter(
field_name="start_date_normalized",
lookup_expr="lte",
)
class Meta:
model = InspectionRecord
fields = [
"inn",
"ogrn",
"registration_number",
"is_federal_law_248",
"data_year",
"data_month",
"load_batch",
"registry_organization",
]
class InspectionViewSet(ReadOnlyModelViewSet): class InspectionViewSet(ReadOnlyModelViewSet):
""" """
API для просмотра проверок из Единого реестра проверок. API для просмотра проверок из Единого реестра проверок.
@@ -848,15 +804,16 @@ class InspectionViewSet(ReadOnlyModelViewSet):
).order_by("-created_at") ).order_by("-created_at")
serializer_class = InspectionSerializer serializer_class = InspectionSerializer
permission_classes = [IsAuthenticated] permission_classes = [IsAuthenticated]
filterset_class = InspectionFilter filterset_fields = [
ordering_fields = [ "inn",
"id", "ogrn",
"start_date_normalized", "registration_number",
"end_date_normalized", "is_federal_law_248",
"created_at", "data_year",
"updated_at", "data_month",
"load_batch",
"registry_organization",
] ]
ordering = ["-start_date_normalized", "-created_at"]
search_fields = [ search_fields = [
"organisation_name", "organisation_name",
"registration_number", "registration_number",
@@ -872,10 +829,7 @@ class InspectionViewSet(ReadOnlyModelViewSet):
operation_description=( operation_description=(
"Возвращает список проверок из Единого реестра.\n" "Возвращает список проверок из Единого реестра.\n"
"Поддерживает фильтрацию по: inn, ogrn, registration_number, " "Поддерживает фильтрацию по: inn, ogrn, registration_number, "
"is_federal_law_248, data_year, data_month, load_batch, " "is_federal_law_248, data_year, data_month, load_batch.\n"
"date_from, date_to.\n"
"Поддерживает сортировку по: start_date_normalized, "
"end_date_normalized, created_at, updated_at.\n"
"Поддерживает поиск по: organisation_name, registration_number, " "Поддерживает поиск по: organisation_name, registration_number, "
"inn, ogrn, control_authority.\n" "inn, ogrn, control_authority.\n"
f"{REGISTRY_ORGANIZATION_SEARCH_DESCRIPTION}" f"{REGISTRY_ORGANIZATION_SEARCH_DESCRIPTION}"
@@ -2248,7 +2202,7 @@ def _native_field_map(source: str) -> dict[str, str]:
"external_id": "registration_number", "external_id": "registration_number",
"organisation_name": "organisation_name", "organisation_name": "organisation_name",
"title": "control_authority", "title": "control_authority",
"record_date": "start_date_normalized", "record_date": "start_date",
"status": "status", "status": "status",
} }
@@ -2319,19 +2273,6 @@ def _apply_native_search(queryset, source: str, search: str):
) )
def _apply_record_date_range(queryset, record_date_field: str, params: dict):
"""Filter normalized or ISO-compatible record dates inclusively."""
date_from = params.get("date_from")
if date_from:
queryset = queryset.filter(
**{f"{record_date_field}__gte": date_from.isoformat()}
)
date_to = params.get("date_to")
if date_to:
queryset = queryset.filter(**{f"{record_date_field}__lte": date_to.isoformat()})
return queryset
def _route_model_sources(descriptor) -> set[str]: def _route_model_sources(descriptor) -> set[str]:
if descriptor.source == ParserLoadLog.Source.TRUDVSEM: if descriptor.source == ParserLoadLog.Source.TRUDVSEM:
return set(VACANCY_RECORD_SOURCES) return set(VACANCY_RECORD_SOURCES)
@@ -2376,8 +2317,6 @@ def _filter_native_result_queryset(source: str, params: dict, sources: set[str])
queryset = queryset.filter(**{model_field: value}) queryset = queryset.filter(**{model_field: value})
if params.get("record_date") and field_map.get("record_date"): if params.get("record_date") and field_map.get("record_date"):
queryset = queryset.filter(**{field_map["record_date"]: params["record_date"]}) queryset = queryset.filter(**{field_map["record_date"]: params["record_date"]})
if field_map.get("record_date"):
queryset = _apply_record_date_range(queryset, field_map["record_date"], params)
if params.get("search"): if params.get("search"):
queryset = _apply_native_search(queryset, source, params["search"]) queryset = _apply_native_search(queryset, source, params["search"])
ordering = _safe_ordering( ordering = _safe_ordering(
@@ -2417,7 +2356,6 @@ def _filter_generic_result_queryset(
queryset = queryset.filter(**{field: value}) queryset = queryset.filter(**{field: value})
if params.get("record_date"): if params.get("record_date"):
queryset = queryset.filter(record_date=params["record_date"]) queryset = queryset.filter(record_date=params["record_date"])
queryset = _apply_record_date_range(queryset, "record_date", params)
if params.get("search"): if params.get("search"):
search = params["search"] search = params["search"]
queryset = queryset.filter( queryset = queryset.filter(

View File

@@ -23,7 +23,6 @@ from apps.parsers.models import (
from cryptography.hazmat.primitives.ciphers.aead import AESGCM from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from django.test import TestCase, override_settings from django.test import TestCase, override_settings
from organizations.models import Organization from organizations.models import Organization
from organizations.test_companies import TestCompanyDatasetService
from tests.apps.parsers.factories import ( from tests.apps.parsers.factories import (
IndustrialCertificateRecordFactory, IndustrialCertificateRecordFactory,
@@ -68,30 +67,6 @@ TEST_STATE_CORP_TOKEN = "state-corp-test-exchange-token" # noqa: S105
class StateCorpExchangeServiceTest(TestCase): class StateCorpExchangeServiceTest(TestCase):
"""Verify package compatibility with state-corp receiver contract.""" """Verify package compatibility with state-corp receiver contract."""
def test_default_package_excludes_deterministic_test_companies(self):
real_organization = Organization.objects.create(
rn=1001,
name="Реальная организация",
inn="7707083893",
gk_name="Росатом",
)
Organization.objects.create(
uid=TestCompanyDatasetService.company_uids()[0],
rn=9001,
name="Тестовая компания 1",
inn="7700000001",
gk_name="Росатом",
)
package = StateCorpExchangeService.build_package()
payload = _decode_package_payload(package)
self.assertEqual(package.payload_counts["organizations"], 1)
self.assertEqual(
[row["mostovik_uid"] for row in payload["data"]["organizations"]],
[str(real_organization.uid)],
)
def test_build_package_contains_expected_payload(self): def test_build_package_contains_expected_payload(self):
organization = Organization.objects.create( organization = Organization.objects.create(
rn=1001, rn=1001,

View File

@@ -133,7 +133,7 @@ class TestCompaniesCommandsTest(TestCase):
self.assertEqual(OrganizationSourceRecord.objects.count(), 20 * 15) self.assertEqual(OrganizationSourceRecord.objects.count(), 20 * 15)
@override_settings(STATE_CORP_EXCHANGE_TOKEN=TEST_EXCHANGE_TOKEN) @override_settings(STATE_CORP_EXCHANGE_TOKEN=TEST_EXCHANGE_TOKEN)
def test_created_source_records_are_excluded_from_state_corp_package(self): def test_created_source_records_are_exported_to_state_corp_package(self):
call_command("create_test_companies", stdout=StringIO()) call_command("create_test_companies", stdout=StringIO())
company_inns = list( company_inns = list(
@@ -146,18 +146,18 @@ class TestCompaniesCommandsTest(TestCase):
self.assertEqual( self.assertEqual(
package.payload_counts, package.payload_counts,
{ {
"organizations": 0, "organizations": 20,
"industrial_certificates": 0, "industrial_certificates": 20,
"manufacturers": 0, "manufacturers": 20,
"industrial_products": 0, "industrial_products": 20,
"prosecutor_checks": 0, "prosecutor_checks": 20,
"public_procurements": 0, "public_procurements": 60,
"financial_reports": 0, "financial_reports": 20,
"arbitration_cases": 0, "arbitration_cases": 20,
"bankruptcy_procedures": 0, "bankruptcy_procedures": 20,
"defense_unreliable_suppliers": 0, "defense_unreliable_suppliers": 40,
"information_security_registries": 0, "information_security_registries": 20,
"labor_vacancies": 0, "labor_vacancies": 20,
}, },
) )

View File

@@ -8,7 +8,6 @@ import io
import os import os
import tempfile import tempfile
import zipfile import zipfile
from datetime import date
from unittest.mock import Mock, patch from unittest.mock import Mock, patch
from apps.parsers.models import ( from apps.parsers.models import (
@@ -481,97 +480,6 @@ class ParsersViewSetTest(APITestCase):
self.assertEqual(response.data["data"][0]["id"], hh_record.id) self.assertEqual(response.data["data"][0]["id"], hh_record.id)
self.assertEqual(response.data["data"][0]["source"], "hh") self.assertEqual(response.data["data"][0]["source"], "hh")
def test_source_results_filter_and_sort_by_record_date(self):
arbitration_older = GenericParserRecord.objects.create(
load_batch=1,
source=ParserLoadLog.Source.ARBITRATION,
external_id="arbitration-older",
record_date="2024-01-15",
)
arbitration_newer = GenericParserRecord.objects.create(
load_batch=1,
source=ParserLoadLog.Source.ARBITRATION,
external_id="arbitration-newer",
record_date="2024-03-15",
)
vacancy_older = GenericParserRecord.objects.create(
load_batch=1,
source="hh",
external_id="hh-older",
record_date="2024-02-01",
payload={"vacancy_source": "hh"},
)
vacancy_newer = GenericParserRecord.objects.create(
load_batch=1,
source="hh",
external_id="hh-newer",
record_date="2024-04-01",
payload={"vacancy_source": "hh"},
)
self.client.force_authenticate(self.user)
arbitration_response = self.client.get(
"/api/v1/arbitration/cases/",
{"date_from": "2024-02-01", "ordering": "-record_date"},
)
vacancy_response = self.client.get(
"/api/v1/trudvsem/vacancies/",
{"date_to": "2024-02-15", "ordering": "record_date"},
)
self.assertEqual(arbitration_response.status_code, status.HTTP_200_OK)
self.assertEqual(
[item["id"] for item in arbitration_response.data["data"]],
[arbitration_newer.id],
)
self.assertNotIn(
arbitration_older.id,
[item["id"] for item in arbitration_response.data["data"]],
)
self.assertEqual(vacancy_response.status_code, status.HTTP_200_OK)
self.assertEqual(
[item["id"] for item in vacancy_response.data["data"]],
[vacancy_older.id],
)
self.assertNotIn(
vacancy_newer.id,
[item["id"] for item in vacancy_response.data["data"]],
)
def test_inspection_source_results_filter_and_sort_by_record_date(self):
older = InspectionRecordFactory(
start_date="2024-01-10",
start_date_normalized=date(2024, 1, 10),
)
newer = InspectionRecordFactory(
start_date="2024-03-10",
start_date_normalized=date(2024, 3, 10),
)
self.client.force_authenticate(self.user)
response = self.client.get(
"/api/v1/proverki/",
{"date_from": "2024-02-01", "ordering": "-start_date_normalized"},
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(
[item["id"] for item in response.data["data"]],
[newer.id],
)
self.assertNotIn(older.id, [item["id"] for item in response.data["data"]])
source_response = self.client.get(
"/api/v1/parsers/results/inspections/",
{"date_from": "2024-02-01", "ordering": "-record_date"},
)
self.assertEqual(source_response.status_code, status.HTTP_200_OK)
self.assertEqual(
[item["id"] for item in source_response.data["data"]],
[newer.id],
)
def test_parser_results_v1_enrich_missing_organization_fields_without_contract_change( def test_parser_results_v1_enrich_missing_organization_fields_without_contract_change(
self, self,
): ):