diff --git a/src/apps/organization/dictionary_urls.py b/src/apps/organization/dictionary_urls.py new file mode 100644 index 0000000..cc7b691 --- /dev/null +++ b/src/apps/organization/dictionary_urls.py @@ -0,0 +1,14 @@ +"""URL-маршруты для справочников организации.""" + +from apps.organization.dictionary_views import CorporationScopeDictionaryView +from django.urls import path + +app_name = "organization_dictionaries" + +urlpatterns = [ + path( + "corporation-scopes/", + CorporationScopeDictionaryView.as_view(), + name="corporation-scopes", + ), +] diff --git a/src/apps/organization/dictionary_views.py b/src/apps/organization/dictionary_views.py new file mode 100644 index 0000000..35bfb59 --- /dev/null +++ b/src/apps/organization/dictionary_views.py @@ -0,0 +1,18 @@ +"""Dictionary endpoints for organization domain.""" + +from apps.organization.scope_utils import get_corporation_scope_dictionary +from apps.organization.serializers import CorporationScopeDictionarySerializer +from rest_framework.permissions import IsAuthenticated +from rest_framework.response import Response +from rest_framework.views import APIView + + +class CorporationScopeDictionaryView(APIView): + """Return static dictionary for organization corporation scopes.""" + + permission_classes = [IsAuthenticated] + + def get(self, request): # noqa: ARG002 + items = get_corporation_scope_dictionary() + serializer = CorporationScopeDictionarySerializer(items, many=True) + return Response({"results": serializer.data}) diff --git a/src/apps/organization/scope_utils.py b/src/apps/organization/scope_utils.py index d7a1210..a52e7f0 100644 --- a/src/apps/organization/scope_utils.py +++ b/src/apps/organization/scope_utils.py @@ -16,6 +16,21 @@ SCOPE_LABELS: dict[str, str] = { "rosatom": "Госкорпорация «Росатом»", "roscosmos": "Госкорпорация «Роскосмос»", "opk": "Организации ОПК", + "other": "Иная корпорация", +} + +SCOPE_SHORT_NAMES: dict[str, str] = { + "rosatom": "Росатом", + "roscosmos": "Роскосмос", + "opk": "ОПК", + "other": "Иная", +} + +SCOPE_SORT_ORDER: dict[str, int] = { + "rosatom": 10, + "roscosmos": 20, + "opk": 30, + "other": 90, } @@ -38,6 +53,27 @@ def scope_labels(scope_codes: Iterable[str]) -> list[str]: return [SCOPE_LABELS[code] for code in scope_codes if code in SCOPE_LABELS] +def get_corporation_scope_dictionary() -> list[dict[str, str | int]]: + """Возвращает справочник корпусов для API-словаря.""" + items: list[dict[str, str | int]] = [] + for code, sort_order in sorted( + SCOPE_SORT_ORDER.items(), key=lambda item: item[1] + ): + label = SCOPE_LABELS.get(code) + short_name = SCOPE_SHORT_NAMES.get(code) + if not label or not short_name: + continue + items.append( + { + "code": code, + "name": label, + "short_name": short_name, + "sort_order": sort_order, + } + ) + return items + + def build_scope_query(scope_codes: Iterable[str]) -> Q: query = Q() for scope_code in scope_codes: diff --git a/src/apps/organization/serializers.py b/src/apps/organization/serializers.py index a65227f..90d3b17 100644 --- a/src/apps/organization/serializers.py +++ b/src/apps/organization/serializers.py @@ -7,6 +7,7 @@ """ from apps.organization.models import Organization +from apps.organization.scope_utils import SCOPE_LABELS from apps.registers.models import Register from rest_framework import serializers @@ -108,12 +109,25 @@ class OrganizationCatalogBaseSerializer(serializers.ModelSerializer): return obj.get_active_registry_names() @staticmethod - def get_corporation_scope(obj: Organization) -> list[str]: - return obj.get_corporation_scopes() + def _primary_scope_or_default(obj: Organization) -> str: + scopes = obj.get_corporation_scopes() + return scopes[0] if scopes else "" @staticmethod - def get_corporation_scope_label(obj: Organization) -> list[str]: - return obj.get_corporation_scope_labels() + def get_corporation_scope(obj: Organization) -> str: + return OrganizationCatalogBaseSerializer._primary_scope_or_default(obj) + + @staticmethod + def get_corporation_scope_label(obj: Organization) -> str: + scope = OrganizationCatalogBaseSerializer._primary_scope_or_default(obj) + return OrganizationCatalogBaseSerializer._label_or_empty(scope) + + @staticmethod + def _label_or_empty(scope_code: str) -> str: + if not scope_code: + return "" + return SCOPE_LABELS.get(scope_code, "") + class OrganizationCatalogListSerializer(OrganizationCatalogBaseSerializer): @@ -193,3 +207,12 @@ class OrganizationCatalogDetailSerializer(OrganizationCatalogBaseSerializer): "created_at", "updated_at", ] + + +class CorporationScopeDictionarySerializer(serializers.Serializer): + """Serializer for corporation scope dictionary entries.""" + + code = serializers.CharField() + name = serializers.CharField() + short_name = serializers.CharField() + sort_order = serializers.IntegerField() diff --git a/src/core/api_v1_urls.py b/src/core/api_v1_urls.py index 01ad31f..37acc04 100644 --- a/src/core/api_v1_urls.py +++ b/src/core/api_v1_urls.py @@ -22,6 +22,7 @@ jobs_urlpatterns = [ urlpatterns = [ path("analytics/", include("apps.organization.analytics_root_urls")), path("exchange/", include("apps.exchange.urls")), + path("dictionaries/", include("apps.organization.dictionary_urls")), path("users/", include("apps.user.urls")), path("jobs/", include((jobs_urlpatterns, "jobs"))), path("organizations/", include("apps.organization.urls")), diff --git a/tests/apps/organization/test_api.py b/tests/apps/organization/test_api.py index 77c35e6..d5e7c61 100644 --- a/tests/apps/organization/test_api.py +++ b/tests/apps/organization/test_api.py @@ -65,7 +65,11 @@ class OrganizationApiTest(APITestCase): self.assertEqual( response.data["results"][0]["active_registry_names"], ["Реестр ОПК"] ) - self.assertEqual(response.data["results"][0]["corporation_scope"], ["opk"]) + self.assertEqual(response.data["results"][0]["corporation_scope"], "opk") + self.assertEqual( + response.data["results"][0]["corporation_scope_label"], + "Организации ОПК", + ) self.assertEqual(response.data["results"][0]["short_name"], "АО «Альфа»") def test_detail_includes_active_registries(self): @@ -102,7 +106,11 @@ class OrganizationApiTest(APITestCase): response.data["active_registries"], [{"id": str(register.id), "name": "Реестр Роскосмос"}], ) - self.assertEqual(response.data["corporation_scope"], ["roscosmos"]) + self.assertEqual(response.data["corporation_scope"], "roscosmos") + self.assertEqual( + response.data["corporation_scope_label"], + "Госкорпорация «Роскосмос»", + ) self.assertEqual( response.data["general_director"]["full_name"], "Иванов Иван Иванович", @@ -180,3 +188,26 @@ class OrganizationApiTest(APITestCase): self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(len(response.data["results"]), 1) self.assertEqual(response.data["results"][0]["id"], str(rosatom_org.id)) + + +class OrganizationDictionaryApiTest(APITestCase): + """Tests for organization dictionaries endpoints.""" + + def setUp(self): + self.user = UserFactory.create_user() + self.client.force_authenticate(self.user) + + def test_corporation_scopes_dictionary_returns_sorted_results(self): + response = self.client.get("/api/v1/dictionaries/corporation-scopes/") + + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertIn("results", response.data) + self.assertGreaterEqual(len(response.data["results"]), 3) + sort_orders = [item["sort_order"] for item in response.data["results"]] + self.assertEqual(sort_orders, sorted(sort_orders)) + + first_item = response.data["results"][0] + self.assertEqual(first_item["code"], "rosatom") + self.assertIn("short_name", first_item) + self.assertIn("name", first_item) + self.assertIn("sort_order", first_item)