diff --git a/src/openforms/contrib/zgw/api/serializers.py b/src/openforms/contrib/zgw/api/serializers.py index 79ed39bbe9..d5e5f3bacb 100644 --- a/src/openforms/contrib/zgw/api/serializers.py +++ b/src/openforms/contrib/zgw/api/serializers.py @@ -81,6 +81,23 @@ class DocumentTypeSerializer(serializers.Serializer): ) +class RoleTypeSerializer(serializers.Serializer): + description = serializers.CharField( + label=_("description"), + help_text=_( + "The description/label given to the role type in the Catalogi API. It " + "identifies the role type within a case type." + ), + ) + description_generic = serializers.CharField( + label=_("generic description"), + help_text=_( + "One of the pre-determined generic descriptions, such as 'behandelaar' " + "or 'belanghebbende'." + ), + ) + + class CaseTypeProductSerializer(serializers.Serializer): url = serializers.CharField( label=_("url"), diff --git a/src/openforms/contrib/zgw/clients/catalogi.py b/src/openforms/contrib/zgw/clients/catalogi.py index a6ab02329f..f295a64b42 100644 --- a/src/openforms/contrib/zgw/clients/catalogi.py +++ b/src/openforms/contrib/zgw/clients/catalogi.py @@ -50,6 +50,7 @@ class CaseType(TypedDict): concept: NotRequired[bool] productenOfDiensten: list[str] # URL pointers to products informatieobjecttypen: NotRequired[list[str]] # URL pointers to document types + roltypen: NotRequired[list[str]] # URL pointers to role types class InformatieObjectType(TypedDict): @@ -63,6 +64,28 @@ class InformatieObjectType(TypedDict): concept: NotRequired[bool] +type PublicationStatusFilter = Literal["alles", "concept", "definitief"] + +type RoleDescriptionGeneric = Literal[ + "adviseur", + "behandelaar", + "belanghebbende", + "beslisser", + "initiator", + "klantcontacter", + "zaakcoordinator", + "mede_initiator", +] + + +class RoleType(TypedDict): + url: str + zaaktype: str + zaaktypeIdentificatie: str # since 1.2 + omschrijving: str + omschrijvingGeneriek: RoleDescriptionGeneric + + class EigenschapSpecificatie(TypedDict): groep: NotRequired[str] formaat: Literal["tekst", "getal", "datum", "datum_tijd"] @@ -91,19 +114,28 @@ class Eigenschap(TypedDict): class CaseTypeListParams(TypedDict, total=False): catalogus: str identificatie: str - status: Literal["alles", "concept", "definitief"] + status: PublicationStatusFilter datumGeldigheid: str page: int class InformatieObjectTypeListParams(TypedDict, total=False): catalogus: str - status: Literal["alles", "concept", "definitief"] + status: PublicationStatusFilter omschrijving: str datumGeldigheid: str page: int +class RoleTypeListParams(TypedDict, total=False): + zaaktype: str + zaaktypeIdentificatie: str # from 1.2 onwards + omschrijvingGeneriek: RoleDescriptionGeneric + status: PublicationStatusFilter + datumGeldigheid: str + page: int + + class CatalogiClient(NLXClient): _api_version: CatalogiAPIVersion | None = None @@ -325,6 +357,46 @@ def list_statustypen(self, zaaktype: str) -> list[dict]: results = response.json()["results"] return results + def get_all_role_types( + self, + *, + catalogus: str, + within_casetype: str, + ) -> Iterator[RoleType]: + params: RoleTypeListParams = { + "zaaktypeIdentificatie": within_casetype, + } + if self.allow_drafts: + params["status"] = "alles" + + # get the case types so that we are filtering within the right catalogue, as + # the same case type identification may be defined in different catalogues + case_type_versions = ( + self.find_case_types( + catalogus=catalogus, + identification=within_casetype, + ) + or [] + ) + all_valid_roltype_urls: list[str] = sum( + ( + case_type_version.get("roltypen", []) + for case_type_version in case_type_versions + ), + [], + ) + if not all_valid_roltype_urls: + return [] + + response = self.get("roltypen", params=params) # type: ignore + response.raise_for_status() + data: PaginatedResponseData[RoleType] = response.json() + + for role_type in pagination_helper(self, data): + if role_type["url"] not in all_valid_roltype_urls: + continue + yield role_type + def list_roltypen( self, zaaktype: str, diff --git a/src/openforms/registrations/contrib/zgw_apis/api/filters.py b/src/openforms/registrations/contrib/zgw_apis/api/filters.py index 43682c1de5..255f51381f 100644 --- a/src/openforms/registrations/contrib/zgw_apis/api/filters.py +++ b/src/openforms/registrations/contrib/zgw_apis/api/filters.py @@ -54,7 +54,7 @@ def get_fields(self): return fields -class ListProductsQueryParamsSerializer(ZGWAPIGroupMixin, serializers.Serializer): +class FilterForCaseTypeQueryParamsSerializer(ZGWAPIGroupMixin, serializers.Serializer): catalogue_url = serializers.URLField( label=_("catalogus URL"), help_text=_("Filter case types against this catalogue URL."), diff --git a/src/openforms/registrations/contrib/zgw_apis/api/urls.py b/src/openforms/registrations/contrib/zgw_apis/api/urls.py index 8dce4caece..407d30b817 100644 --- a/src/openforms/registrations/contrib/zgw_apis/api/urls.py +++ b/src/openforms/registrations/contrib/zgw_apis/api/urls.py @@ -5,6 +5,7 @@ CatalogueListView, DocumentTypesListView, ProductsListView, + RoleTypeListView, ) app_name = "zgw_apis" @@ -13,5 +14,6 @@ path("catalogues", CatalogueListView.as_view(), name="catalogue-list"), path("case-types", CaseTypesListView.as_view(), name="case-type-list"), path("document-types", DocumentTypesListView.as_view(), name="document-type-list"), + path("role-types", RoleTypeListView.as_view(), name="role-type-list"), path("products", ProductsListView.as_view(), name="product-list"), ] diff --git a/src/openforms/registrations/contrib/zgw_apis/api/views.py b/src/openforms/registrations/contrib/zgw_apis/api/views.py index 21a64f3510..278aa9a4ac 100644 --- a/src/openforms/registrations/contrib/zgw_apis/api/views.py +++ b/src/openforms/registrations/contrib/zgw_apis/api/views.py @@ -11,6 +11,7 @@ from openforms.contrib.zgw.api.serializers import ( CaseTypeProductSerializer, CaseTypeSerializer, + RoleTypeSerializer, ) from openforms.contrib.zgw.api.views import ( BaseCatalogueListView, @@ -21,9 +22,9 @@ from .filters import ( APIGroupQueryParamsSerializer, + FilterForCaseTypeQueryParamsSerializer, ListCaseTypesQueryParamsSerializer, ListDocumentTypesQueryParamsSerializer, - ListProductsQueryParamsSerializer, ) @@ -90,6 +91,58 @@ class DocumentTypesListView(BaseDocumentTypesListView): filter_serializer_class = ListDocumentTypesQueryParamsSerializer +@dataclass +class RoleType: + description: str + description_generic: str + + +@extend_schema_view( + get=extend_schema( + summary=_( + "List the available role types bound to a case type within a catalogue " + "(ZGW APIs)" + ), + parameters=[FilterForCaseTypeQueryParamsSerializer], + ), +) +class RoleTypeListView(ListMixin[RoleType], APIView): + authentication_classes = (authentication.SessionAuthentication,) + permission_classes = (permissions.IsAdminUser,) + serializer_class = RoleTypeSerializer + + def get_objects(self) -> list[RoleType]: + filter_serializer = FilterForCaseTypeQueryParamsSerializer( + data=self.request.query_params + ) + filter_serializer.is_valid(raise_exception=True) + + catalogue_url = filter_serializer.validated_data["catalogue_url"] + case_type_identification = filter_serializer.validated_data[ + "case_type_identification" + ] + role_types: list[RoleType] = [] + with filter_serializer.get_ztc_client() as client: + _role_types = client.get_all_role_types( + catalogus=catalogue_url, + within_casetype=case_type_identification, + ) + for _role_type in _role_types: + # skip over initiator, since that one is set automatically and there can + # only be one + if ( + description_generic := _role_type["omschrijvingGeneriek"] + ) == "initiator": + continue + role_type = RoleType( + description=_role_type["omschrijving"], + description_generic=description_generic, + ) + if role_type not in role_types: + role_types.append(role_type) + return role_types + + @dataclass class Product: url: str @@ -101,7 +154,7 @@ class Product: summary=_( "List the available products bound to a case type within a catalogue (ZGW APIs)" ), - parameters=[ListProductsQueryParamsSerializer], + parameters=[FilterForCaseTypeQueryParamsSerializer], ), ) class ProductsListView(ListMixin[Product], APIView): @@ -110,7 +163,7 @@ class ProductsListView(ListMixin[Product], APIView): serializer_class = CaseTypeProductSerializer def get_objects(self): - filter_serializer = ListProductsQueryParamsSerializer( + filter_serializer = FilterForCaseTypeQueryParamsSerializer( data=self.request.query_params ) filter_serializer.is_valid(raise_exception=True)