Compare commits
2 Commits
main
...
feature/ba
| Author | SHA1 | Date | |
|---|---|---|---|
| d0325ec271 | |||
| 4c5ceb74f6 |
@@ -19,19 +19,6 @@ FROM base AS builder
|
|||||||
|
|
||||||
ARG INSTALL_DEV=false
|
ARG INSTALL_DEV=false
|
||||||
|
|
||||||
# hadolint ignore=DL3008
|
|
||||||
RUN apt-get update \
|
|
||||||
&& apt-get install -y --no-install-recommends \
|
|
||||||
build-essential \
|
|
||||||
gcc \
|
|
||||||
libpq-dev \
|
|
||||||
libffi-dev \
|
|
||||||
libxml2-dev \
|
|
||||||
libxslt1-dev \
|
|
||||||
zlib1g-dev \
|
|
||||||
pkg-config \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
COPY pyproject.toml uv.lock ./
|
COPY pyproject.toml uv.lock ./
|
||||||
|
|
||||||
RUN if [ "${INSTALL_DEV}" = "true" ]; then \
|
RUN if [ "${INSTALL_DEV}" = "true" ]; then \
|
||||||
@@ -43,17 +30,6 @@ RUN if [ "${INSTALL_DEV}" = "true" ]; then \
|
|||||||
|
|
||||||
FROM base AS runtime-base
|
FROM base AS runtime-base
|
||||||
|
|
||||||
# hadolint ignore=DL3008
|
|
||||||
RUN apt-get update \
|
|
||||||
&& apt-get install -y --no-install-recommends \
|
|
||||||
curl \
|
|
||||||
libpq5 \
|
|
||||||
libffi8 \
|
|
||||||
libxml2 \
|
|
||||||
libxslt1.1 \
|
|
||||||
zlib1g \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
COPY --from=builder /app/.venv /app/.venv
|
COPY --from=builder /app/.venv /app/.venv
|
||||||
COPY src/ ./src/
|
COPY src/ ./src/
|
||||||
COPY docker/scripts/ ./docker/scripts/
|
COPY docker/scripts/ ./docker/scripts/
|
||||||
|
|||||||
@@ -82,7 +82,9 @@ def build_upload_success_payload(
|
|||||||
report_quarter=report_quarter,
|
report_quarter=report_quarter,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
payload["report_period_display"] = report_annual_display(report_year=report_year)
|
payload["report_period_display"] = report_annual_display(
|
||||||
|
report_year=report_year
|
||||||
|
)
|
||||||
|
|
||||||
if result is not None:
|
if result is not None:
|
||||||
payload["result"] = result
|
payload["result"] = result
|
||||||
|
|||||||
@@ -4,16 +4,16 @@ from apps.core.viewsets import ClassicReadOnlyViewSet
|
|||||||
from apps.external_data.models import (
|
from apps.external_data.models import (
|
||||||
ArbitrationCase,
|
ArbitrationCase,
|
||||||
IndustrialProduct,
|
IndustrialProduct,
|
||||||
|
InformationSecurityRegistryEntry,
|
||||||
ProsecutorCheck,
|
ProsecutorCheck,
|
||||||
PublicProcurement,
|
PublicProcurement,
|
||||||
InformationSecurityRegistryEntry,
|
|
||||||
)
|
)
|
||||||
from apps.external_data.serializers import (
|
from apps.external_data.serializers import (
|
||||||
ArbitrationCaseSerializer,
|
ArbitrationCaseSerializer,
|
||||||
|
CorporationMembershipSerializer,
|
||||||
IndustrialProductSerializer,
|
IndustrialProductSerializer,
|
||||||
ProsecutorCheckSerializer,
|
ProsecutorCheckSerializer,
|
||||||
PublicProcurementSerializer,
|
PublicProcurementSerializer,
|
||||||
CorporationMembershipSerializer,
|
|
||||||
)
|
)
|
||||||
from django_filters import rest_framework as filters
|
from django_filters import rest_framework as filters
|
||||||
from rest_framework.permissions import IsAuthenticated
|
from rest_framework.permissions import IsAuthenticated
|
||||||
@@ -122,7 +122,9 @@ class ArbitrationCaseViewSet(ClassicReadOnlyViewSet[ArbitrationCase]):
|
|||||||
class CorporationMembershipViewSet(
|
class CorporationMembershipViewSet(
|
||||||
ClassicReadOnlyViewSet[InformationSecurityRegistryEntry]
|
ClassicReadOnlyViewSet[InformationSecurityRegistryEntry]
|
||||||
):
|
):
|
||||||
queryset = InformationSecurityRegistryEntry.objects.select_related("organization").all()
|
queryset = InformationSecurityRegistryEntry.objects.select_related(
|
||||||
|
"organization"
|
||||||
|
).all()
|
||||||
serializer_class = CorporationMembershipSerializer
|
serializer_class = CorporationMembershipSerializer
|
||||||
permission_classes = [IsAuthenticated]
|
permission_classes = [IsAuthenticated]
|
||||||
filterset_class = CorporationMembershipFilter
|
filterset_class = CorporationMembershipFilter
|
||||||
|
|||||||
@@ -112,7 +112,9 @@ class ArbitrationCase(UUIDPrimaryKeyMixin, TimestampMixin, models.Model):
|
|||||||
return f"{self.case_number} ({self.organization_id})"
|
return f"{self.case_number} ({self.organization_id})"
|
||||||
|
|
||||||
|
|
||||||
class InformationSecurityRegistryEntry(UUIDPrimaryKeyMixin, TimestampMixin, models.Model):
|
class InformationSecurityRegistryEntry(
|
||||||
|
UUIDPrimaryKeyMixin, TimestampMixin, models.Model
|
||||||
|
):
|
||||||
class PresenceStatus(models.TextChoices):
|
class PresenceStatus(models.TextChoices):
|
||||||
PRESENT = "present", _("В реестре")
|
PRESENT = "present", _("В реестре")
|
||||||
ABSENT = "absent", _("Не в реестре")
|
ABSENT = "absent", _("Не в реестре")
|
||||||
|
|||||||
@@ -3,9 +3,9 @@
|
|||||||
from apps.external_data.models import (
|
from apps.external_data.models import (
|
||||||
ArbitrationCase,
|
ArbitrationCase,
|
||||||
IndustrialProduct,
|
IndustrialProduct,
|
||||||
|
InformationSecurityRegistryEntry,
|
||||||
ProsecutorCheck,
|
ProsecutorCheck,
|
||||||
PublicProcurement,
|
PublicProcurement,
|
||||||
InformationSecurityRegistryEntry,
|
|
||||||
)
|
)
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
|||||||
@@ -2,10 +2,10 @@
|
|||||||
|
|
||||||
from apps.external_data.api import (
|
from apps.external_data.api import (
|
||||||
ArbitrationCaseViewSet,
|
ArbitrationCaseViewSet,
|
||||||
|
CorporationMembershipViewSet,
|
||||||
IndustrialProductViewSet,
|
IndustrialProductViewSet,
|
||||||
ProsecutorCheckViewSet,
|
ProsecutorCheckViewSet,
|
||||||
PublicProcurementViewSet,
|
PublicProcurementViewSet,
|
||||||
CorporationMembershipViewSet,
|
|
||||||
)
|
)
|
||||||
from django.urls import include, path
|
from django.urls import include, path
|
||||||
from rest_framework.routers import DefaultRouter
|
from rest_framework.routers import DefaultRouter
|
||||||
|
|||||||
@@ -7,11 +7,11 @@
|
|||||||
- FormF2ParseResultSerializer - результат парсинга
|
- FormF2ParseResultSerializer - результат парсинга
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from apps.form_2.models import FormF2Record
|
|
||||||
from apps.core.upload_contracts import (
|
from apps.core.upload_contracts import (
|
||||||
UploadQuarterSerializer,
|
|
||||||
UploadParseResultSerializer,
|
UploadParseResultSerializer,
|
||||||
|
UploadQuarterSerializer,
|
||||||
)
|
)
|
||||||
|
from apps.form_2.models import FormF2Record
|
||||||
from apps.organization.serializers import OrganizationSerializer
|
from apps.organization.serializers import OrganizationSerializer
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
|||||||
@@ -7,11 +7,11 @@
|
|||||||
- FormF3ParseResultSerializer - результат парсинга
|
- FormF3ParseResultSerializer - результат парсинга
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from apps.form_3.models import FormF3Record
|
|
||||||
from apps.core.upload_contracts import (
|
from apps.core.upload_contracts import (
|
||||||
UploadAnnualSerializer,
|
UploadAnnualSerializer,
|
||||||
UploadParseResultSerializer,
|
UploadParseResultSerializer,
|
||||||
)
|
)
|
||||||
|
from apps.form_3.models import FormF3Record
|
||||||
from apps.organization.serializers import OrganizationSerializer
|
from apps.organization.serializers import OrganizationSerializer
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
"""Сериализаторы формы Ф-4."""
|
"""Сериализаторы формы Ф-4."""
|
||||||
|
|
||||||
from apps.form_4.models import FormF4Record
|
|
||||||
from apps.core.upload_contracts import (
|
from apps.core.upload_contracts import (
|
||||||
UploadHalfYearSerializer,
|
UploadHalfYearSerializer,
|
||||||
UploadParseResultSerializer,
|
UploadParseResultSerializer,
|
||||||
)
|
)
|
||||||
|
from apps.form_4.models import FormF4Record
|
||||||
from apps.organization.serializers import OrganizationSerializer
|
from apps.organization.serializers import OrganizationSerializer
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
"""Сериализаторы формы Ф-5."""
|
"""Сериализаторы формы Ф-5."""
|
||||||
|
|
||||||
from apps.form_5.models import FormF5Record
|
|
||||||
from apps.core.upload_contracts import (
|
from apps.core.upload_contracts import (
|
||||||
UploadAnnualSerializer,
|
UploadAnnualSerializer,
|
||||||
UploadParseResultSerializer,
|
UploadParseResultSerializer,
|
||||||
)
|
)
|
||||||
|
from apps.form_5.models import FormF5Record
|
||||||
from apps.organization.serializers import OrganizationSerializer
|
from apps.organization.serializers import OrganizationSerializer
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
"""Сериализаторы формы Ф-6."""
|
"""Сериализаторы формы Ф-6."""
|
||||||
|
|
||||||
from apps.form_6.models import FormF6Record
|
|
||||||
from apps.core.upload_contracts import (
|
from apps.core.upload_contracts import (
|
||||||
UploadAnnualSerializer,
|
UploadAnnualSerializer,
|
||||||
UploadParseResultSerializer,
|
UploadParseResultSerializer,
|
||||||
)
|
)
|
||||||
|
from apps.form_6.models import FormF6Record
|
||||||
from apps.organization.serializers import OrganizationSerializer
|
from apps.organization.serializers import OrganizationSerializer
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
|||||||
@@ -56,9 +56,7 @@ def scope_labels(scope_codes: Iterable[str]) -> list[str]:
|
|||||||
def get_corporation_scope_dictionary() -> list[dict[str, str | int]]:
|
def get_corporation_scope_dictionary() -> list[dict[str, str | int]]:
|
||||||
"""Возвращает справочник корпусов для API-словаря."""
|
"""Возвращает справочник корпусов для API-словаря."""
|
||||||
items: list[dict[str, str | int]] = []
|
items: list[dict[str, str | int]] = []
|
||||||
for code, sort_order in sorted(
|
for code, sort_order in sorted(SCOPE_SORT_ORDER.items(), key=lambda item: item[1]):
|
||||||
SCOPE_SORT_ORDER.items(), key=lambda item: item[1]
|
|
||||||
):
|
|
||||||
label = SCOPE_LABELS.get(code)
|
label = SCOPE_LABELS.get(code)
|
||||||
short_name = SCOPE_SHORT_NAMES.get(code)
|
short_name = SCOPE_SHORT_NAMES.get(code)
|
||||||
if not label or not short_name:
|
if not label or not short_name:
|
||||||
|
|||||||
@@ -129,7 +129,6 @@ class OrganizationCatalogBaseSerializer(serializers.ModelSerializer):
|
|||||||
return SCOPE_LABELS.get(scope_code, "")
|
return SCOPE_LABELS.get(scope_code, "")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class OrganizationCatalogListSerializer(OrganizationCatalogBaseSerializer):
|
class OrganizationCatalogListSerializer(OrganizationCatalogBaseSerializer):
|
||||||
"""Сериализатор списка организаций для `/api/v1/organizations/`."""
|
"""Сериализатор списка организаций для `/api/v1/organizations/`."""
|
||||||
|
|
||||||
|
|||||||
@@ -249,16 +249,19 @@ class AdminUsersManagementView(APIView):
|
|||||||
return
|
return
|
||||||
|
|
||||||
latest_jobs: dict[int, BackgroundJob] = {}
|
latest_jobs: dict[int, BackgroundJob] = {}
|
||||||
jobs = BackgroundJobService.get_queryset().filter(
|
jobs = (
|
||||||
user_id__in=user_ids
|
BackgroundJobService.get_queryset()
|
||||||
).annotate(
|
.filter(user_id__in=user_ids)
|
||||||
|
.annotate(
|
||||||
_effective_job_ts=Coalesce(
|
_effective_job_ts=Coalesce(
|
||||||
F("completed_at"),
|
F("completed_at"),
|
||||||
F("started_at"),
|
F("started_at"),
|
||||||
F("updated_at"),
|
F("updated_at"),
|
||||||
F("created_at"),
|
F("created_at"),
|
||||||
)
|
)
|
||||||
).order_by("user_id", "-_effective_job_ts")
|
)
|
||||||
|
.order_by("user_id", "-_effective_job_ts")
|
||||||
|
)
|
||||||
|
|
||||||
for job in jobs:
|
for job in jobs:
|
||||||
if job.user_id not in latest_jobs:
|
if job.user_id not in latest_jobs:
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ import factory
|
|||||||
from apps.external_data.models import (
|
from apps.external_data.models import (
|
||||||
ArbitrationCase,
|
ArbitrationCase,
|
||||||
IndustrialProduct,
|
IndustrialProduct,
|
||||||
|
InformationSecurityRegistryEntry,
|
||||||
ProsecutorCheck,
|
ProsecutorCheck,
|
||||||
PublicProcurement,
|
PublicProcurement,
|
||||||
InformationSecurityRegistryEntry,
|
|
||||||
)
|
)
|
||||||
from faker import Faker
|
from faker import Faker
|
||||||
|
|
||||||
@@ -71,9 +71,7 @@ class ArbitrationCaseFactory(factory.django.DjangoModelFactory):
|
|||||||
decision_date = factory.LazyAttribute(lambda _: fake.date_this_year())
|
decision_date = factory.LazyAttribute(lambda _: fake.date_this_year())
|
||||||
|
|
||||||
|
|
||||||
class InformationSecurityRegistryEntryFactory(
|
class InformationSecurityRegistryEntryFactory(factory.django.DjangoModelFactory):
|
||||||
factory.django.DjangoModelFactory
|
|
||||||
):
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = InformationSecurityRegistryEntry
|
model = InformationSecurityRegistryEntry
|
||||||
|
|
||||||
|
|||||||
@@ -11,9 +11,9 @@ from rest_framework.test import APITestCase
|
|||||||
from tests.apps.external_data.factories import (
|
from tests.apps.external_data.factories import (
|
||||||
ArbitrationCaseFactory,
|
ArbitrationCaseFactory,
|
||||||
IndustrialProductFactory,
|
IndustrialProductFactory,
|
||||||
|
InformationSecurityRegistryEntryFactory,
|
||||||
ProsecutorCheckFactory,
|
ProsecutorCheckFactory,
|
||||||
PublicProcurementFactory,
|
PublicProcurementFactory,
|
||||||
InformationSecurityRegistryEntryFactory,
|
|
||||||
)
|
)
|
||||||
from tests.apps.organization.factories import OrganizationFactory
|
from tests.apps.organization.factories import OrganizationFactory
|
||||||
from tests.apps.user.factories import UserFactory
|
from tests.apps.user.factories import UserFactory
|
||||||
|
|||||||
@@ -193,8 +193,7 @@ class FormUploadContractsApiTest(APITestCase):
|
|||||||
|
|
||||||
def test_upload_processing_error_contract(self):
|
def test_upload_processing_error_contract(self):
|
||||||
for _, case in self.CASES.items():
|
for _, case in self.CASES.items():
|
||||||
with self.subTest(form=case["form"]):
|
with self.subTest(form=case["form"]), patch(
|
||||||
with patch(
|
|
||||||
case["parse_target"],
|
case["parse_target"],
|
||||||
side_effect=RuntimeError("parse failed"),
|
side_effect=RuntimeError("parse failed"),
|
||||||
) as parse_mock:
|
) as parse_mock:
|
||||||
|
|||||||
@@ -183,9 +183,18 @@ class OrganizationAnalyticsApiTest(APITestCase):
|
|||||||
self.assertEqual(response.data["insurance_contributions"]["amount"], 302000)
|
self.assertEqual(response.data["insurance_contributions"]["amount"], 302000)
|
||||||
self.assertEqual(response.data["organization_id"], str(self.organization.id))
|
self.assertEqual(response.data["organization_id"], str(self.organization.id))
|
||||||
self.assertEqual(response.data["report_period"], {"year": 2026, "quarter": 1})
|
self.assertEqual(response.data["report_period"], {"year": 2026, "quarter": 1})
|
||||||
self.assertEqual(set(response.data["revenue"]), {"amount", "previous_amount", "delta_percent"})
|
self.assertEqual(
|
||||||
self.assertEqual(set(response.data["net_profit"]), {"amount", "previous_amount", "delta_percent"})
|
set(response.data["revenue"]),
|
||||||
self.assertEqual(set(response.data["taxes_paid"]), {"amount", "previous_amount", "delta_percent"})
|
{"amount", "previous_amount", "delta_percent"},
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
set(response.data["net_profit"]),
|
||||||
|
{"amount", "previous_amount", "delta_percent"},
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
set(response.data["taxes_paid"]),
|
||||||
|
{"amount", "previous_amount", "delta_percent"},
|
||||||
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
set(response.data["insurance_contributions"]),
|
set(response.data["insurance_contributions"]),
|
||||||
{"amount", "previous_amount", "delta_percent"},
|
{"amount", "previous_amount", "delta_percent"},
|
||||||
@@ -225,7 +234,9 @@ class OrganizationAnalyticsApiTest(APITestCase):
|
|||||||
"?report_year=2026&history_years=2"
|
"?report_year=2026&history_years=2"
|
||||||
)
|
)
|
||||||
self.assertEqual(personnel_response.status_code, status.HTTP_200_OK)
|
self.assertEqual(personnel_response.status_code, status.HTTP_200_OK)
|
||||||
self.assertEqual(personnel_response.data["organization_id"], str(self.organization.id))
|
self.assertEqual(
|
||||||
|
personnel_response.data["organization_id"], str(self.organization.id)
|
||||||
|
)
|
||||||
self.assertEqual(personnel_response.data["report_year"], 2026)
|
self.assertEqual(personnel_response.data["report_year"], 2026)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
personnel_response.data["headcount"]["average_employees"],
|
personnel_response.data["headcount"]["average_employees"],
|
||||||
@@ -285,13 +296,21 @@ class OrganizationAnalyticsApiTest(APITestCase):
|
|||||||
"?frequency=quarterly&price_mode=actual&report_year=2026"
|
"?frequency=quarterly&price_mode=actual&report_year=2026"
|
||||||
)
|
)
|
||||||
self.assertEqual(products_response.status_code, status.HTTP_200_OK)
|
self.assertEqual(products_response.status_code, status.HTTP_200_OK)
|
||||||
self.assertEqual(products_response.data["organization_id"], str(self.organization.id))
|
self.assertEqual(
|
||||||
|
products_response.data["organization_id"], str(self.organization.id)
|
||||||
|
)
|
||||||
self.assertEqual(products_response.data["report_year"], 2026)
|
self.assertEqual(products_response.data["report_year"], 2026)
|
||||||
self.assertEqual(products_response.data["frequency"], "quarterly")
|
self.assertEqual(products_response.data["frequency"], "quarterly")
|
||||||
self.assertEqual(products_response.data["price_mode"], "actual")
|
self.assertEqual(products_response.data["price_mode"], "actual")
|
||||||
self.assertEqual(products_response.data["summary"]["military_output_amount"], 11000000)
|
self.assertEqual(
|
||||||
self.assertEqual(products_response.data["summary"]["civilian_output_amount"], 7000000)
|
products_response.data["summary"]["military_output_amount"], 11000000
|
||||||
self.assertEqual(products_response.data["summary"]["hightech_output_amount"], 1500000)
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
products_response.data["summary"]["civilian_output_amount"], 7000000
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
products_response.data["summary"]["hightech_output_amount"], 1500000
|
||||||
|
)
|
||||||
self.assertEqual(products_response.data["summary"]["rd_volume_amount"], 900000)
|
self.assertEqual(products_response.data["summary"]["rd_volume_amount"], 900000)
|
||||||
self.assertEqual(len(products_response.data["production_series"]), 1)
|
self.assertEqual(len(products_response.data["production_series"]), 1)
|
||||||
self.assertEqual(len(products_response.data["sales_series"]), 1)
|
self.assertEqual(len(products_response.data["sales_series"]), 1)
|
||||||
|
|||||||
Reference in New Issue
Block a user