Skip to content

Commit 605729e

Browse files
authored
Merge pull request #747 from maykinmedia/feature/709-add-internal-choices-selectielijstklasse
[#709] Add internal choices selectielijstklasse
2 parents 93b9360 + 61ff780 commit 605729e

File tree

13 files changed

+356
-33
lines changed

13 files changed

+356
-33
lines changed

backend/src/openarchiefbeheer/api/urls.py

+8-2
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,12 @@
3333
BehandelendAfdelingInternalChoicesView,
3434
CacheZakenView,
3535
ExternalResultaattypeChoicesView,
36+
ExternalSelectielijstklasseChoicesView,
3637
ExternalZaaktypenChoicesView,
3738
InformatieobjecttypeChoicesView,
3839
InternalResultaattypeChoicesView,
40+
InternalSelectielijstklasseChoicesView,
3941
InternalZaaktypenChoicesView,
40-
SelectielijstklasseChoicesView,
4142
StatustypeChoicesView,
4243
)
4344
from openarchiefbeheer.zaken.api.viewsets import ZakenViewSet
@@ -160,9 +161,14 @@
160161
),
161162
path(
162163
"_selectielijstklasse-choices/",
163-
SelectielijstklasseChoicesView.as_view(),
164+
ExternalSelectielijstklasseChoicesView.as_view(),
164165
name="retrieve-selectielijstklasse-choices",
165166
),
167+
path(
168+
"_internal-selectielijstklasse-choices/",
169+
InternalSelectielijstklasseChoicesView.as_view(),
170+
name="retrieve-internal-selectielijstklasse-choices",
171+
),
166172
path(
167173
"_statustype-choices/",
168174
StatustypeChoicesView.as_view(),

backend/src/openarchiefbeheer/utils/tests/gherkin.py

+3
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,9 @@ async def handle(route):
364364
await route.fulfill(json=json)
365365

366366
await page.route("**/*/api/v1/_selectielijstklasse-choices/*", handle)
367+
await page.route(
368+
"**/*/api/v1/_internal-selectielijstklasse-choices/*", handle
369+
)
367370

368371
async def statustype_choices_are_available(self, page):
369372
async def handle(route):

backend/src/openarchiefbeheer/zaken/api/views.py

+61-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
from django.db.models import Case, F, Q, URLField, When
2+
from django.db.models.fields.json import KT
3+
from django.db.models.functions import Cast
14
from django.shortcuts import get_object_or_404
25
from django.utils.decorators import method_decorator
36
from django.utils.translation import gettext_lazy as _
@@ -134,7 +137,7 @@ def get(self, request, *args, **kwargs):
134137
return Response(zaaktypen_choices, status=status.HTTP_200_OK)
135138

136139

137-
class SelectielijstklasseChoicesView(APIView):
140+
class ExternalSelectielijstklasseChoicesView(APIView):
138141
permission_classes = [IsAuthenticated]
139142

140143
@extend_schema(
@@ -164,6 +167,63 @@ def get(self, request, *args, **kwargs):
164167
return Response(data=choices)
165168

166169

170+
class InternalSelectielijstklasseChoicesView(FilterOnZaaktypeMixin, APIView):
171+
permission_classes = [IsAuthenticated]
172+
173+
@extend_schema(
174+
summary=_("Retrieve internal selectielijstklasse choices"),
175+
description=_(
176+
"Retrieve the selectielijstklasse choices for zaken in the database. "
177+
"Note: if a zaak does not have a selectielijstklasse specified, it is deduced from the "
178+
"resultaat -> resultaattype -> selectielijstklasse."
179+
),
180+
parameters=[ZaaktypeFilterSerializer],
181+
tags=["private"],
182+
responses={
183+
200: ChoiceSerializer(many=True),
184+
},
185+
)
186+
@method_decorator(cache_page(60 * 15))
187+
def get(self, request, *args, **kwargs):
188+
filterset = ZaakFilterSet(data=request.query_params or request.data)
189+
is_valid = filterset.is_valid()
190+
if not is_valid:
191+
raise ValidationError(filterset.errors)
192+
193+
zaken_selectielijstklasse = (
194+
filterset.qs.annotate(
195+
resolved_selectielijstklasse=Case(
196+
When(~Q(selectielijstklasse=""), then=F("selectielijstklasse")),
197+
When(
198+
selectielijstklasse="",
199+
then=Cast(
200+
# KT is needed otherwise we get '"http://bla.bla"' (extra quotes)
201+
KT(
202+
"_expand__resultaat___expand__resultaattype__selectielijstklasse"
203+
),
204+
output_field=URLField(),
205+
),
206+
),
207+
)
208+
)
209+
.distinct("resolved_selectielijstklasse")
210+
.order_by("resolved_selectielijstklasse")
211+
.values_list("resolved_selectielijstklasse", flat=True)
212+
)
213+
214+
all_selectielijstklasse_choices = {
215+
choice["value"]: choice for choice in retrieve_selectielijstklasse_choices()
216+
}
217+
218+
formatted_choices = []
219+
for item in zaken_selectielijstklasse:
220+
formatted_choice = all_selectielijstklasse_choices.get(item)
221+
if formatted_choice:
222+
formatted_choices.append(formatted_choice)
223+
224+
return Response(formatted_choices, status=status.HTTP_200_OK)
225+
226+
167227
class StatustypeChoicesView(FilterOnZaaktypeMixin, APIView):
168228
permission_classes = [IsAuthenticated]
169229

backend/src/openarchiefbeheer/zaken/tests/test_views.py

+209-1
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@
2121
DestructionListReviewFactory,
2222
)
2323
from openarchiefbeheer.utils.tests.mixins import ClearCacheMixin
24-
from openarchiefbeheer.zaken.utils import retrieve_paginated_type
2524

2625
from ..tasks import retrieve_and_cache_zaken_from_openzaak
26+
from ..utils import retrieve_paginated_type, retrieve_selectielijstklasse_choices
2727
from .factories import ZaakFactory
2828

2929

@@ -655,6 +655,214 @@ def test_response_cached(self, m):
655655
self.assertEqual(len(m.request_history), 1)
656656

657657

658+
class InternalSelectielijstklasseChoicesViewTests(ClearCacheMixin, APITestCase):
659+
def setUp(self):
660+
super().setUp()
661+
662+
retrieve_selectielijstklasse_choices.cache_clear()
663+
664+
self.addCleanup(retrieve_selectielijstklasse_choices.cache_clear)
665+
666+
def test_not_authenticated(self):
667+
endpoint = reverse("api:retrieve-internal-selectielijstklasse-choices")
668+
669+
response = self.client.get(endpoint)
670+
671+
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
672+
673+
@Mocker()
674+
def test_retrieve_internal_choices(self, m):
675+
"""
676+
Test that only the selectielijstklassen of the zaken in the database are returned.
677+
678+
We have:
679+
680+
- A zaak with selectielijstklasse specified on the zaak
681+
- A zaak with selectielijstklasse derived from resultaat -> resultaattype -> selectielijstklasse
682+
- A zaak without selectielijstklasse
683+
684+
We mock the selectielijstklasse API endpoint to return 3 resultaten, two of which are referenced in the zaken
685+
in the db. So the internal selectielijstklasse choices endpoint should only return 2 choices.
686+
"""
687+
selectielist_service = ServiceFactory.create(
688+
api_type=APITypes.orc,
689+
api_root="http://selectielijst.nl/api/v1",
690+
)
691+
user = UserFactory.create(post__can_start_destruction=True)
692+
ZaakFactory.create(
693+
selectielijstklasse="http://selectielijst.nl/api/v1/resultaten/5038528b-0eb7-4502-a415-a3093987d69b",
694+
post___expand={
695+
"resultaat": {
696+
"url": "http://localhost:8003/zaken/api/v1/resultaten/821b4d8f-3244-4ece-8d33-791fa6d2a2f3",
697+
"uuid": "821b4d8f-3244-4ece-8d33-791fa6d2a2f3",
698+
"_expand": {
699+
"resultaattype": {
700+
"url": "http://localhost:8003/catalogi/api/v1/resultaattypen/111-111-111",
701+
"omschrijving": "Afgehandeld",
702+
"selectielijstklasse": "http://selectielijst.nl/api/v1/resultaten/2e86a8ca-0269-446c-8da2-6f4d08be422d",
703+
}
704+
},
705+
"toelichting": "Testing resultaten",
706+
"resultaattype": "http://localhost:8003/catalogi/api/v1/resultaattypen/111-111-111",
707+
}
708+
},
709+
)
710+
ZaakFactory.create(
711+
selectielijstklasse="",
712+
post___expand={
713+
"resultaat": {
714+
"url": "http://localhost:8003/zaken/api/v1/resultaten/821b4d8f-3244-4ece-8d33-791fa6d2a2f3",
715+
"uuid": "821b4d8f-3244-4ece-8d33-791fa6d2a2f3",
716+
"_expand": {
717+
"resultaattype": {
718+
"url": "http://localhost:8003/catalogi/api/v1/resultaattypen/222-222-222",
719+
"omschrijving": "Lopend",
720+
"selectielijstklasse": "http://selectielijst.nl/api/v1/resultaten/5d102cc6-4a74-4262-a14a-538bbfe3f2da",
721+
}
722+
},
723+
"toelichting": "Testing resultaten",
724+
"resultaattype": "http://localhost:8003/catalogi/api/v1/resultaattypen/222-222-222",
725+
}
726+
},
727+
)
728+
ZaakFactory.create(selectielijstklasse="", post___expand={})
729+
730+
m.get(
731+
"http://selectielijst.nl/api/v1/resultaten",
732+
json={
733+
"count": 3,
734+
"results": [
735+
{
736+
"url": "http://selectielijst.nl/api/v1/resultaten/2e86a8ca-0269-446c-8da2-6f4d08be422d",
737+
"nummer": 1,
738+
"volledigNummer": "11.1",
739+
"naam": "Verleend",
740+
"waardering": "vernietigen",
741+
"bewaartermijn": "P1Y",
742+
},
743+
{
744+
"url": "http://selectielijst.nl/api/v1/resultaten/5038528b-0eb7-4502-a415-a3093987d69b",
745+
"nummer": 1,
746+
"naam": "Verleend",
747+
"waardering": "vernietigen",
748+
"bewaartermijn": "P2Y",
749+
},
750+
{
751+
"url": "http://selectielijst.nl/api/v1/resultaten/5d102cc6-4a74-4262-a14a-538bbfe3f2da",
752+
"nummer": 2,
753+
"volledigNummer": "11.1.2",
754+
"naam": "Verleend",
755+
"waardering": "vernietigen",
756+
},
757+
],
758+
},
759+
)
760+
761+
self.client.force_authenticate(user=user)
762+
endpoint = furl(reverse("api:retrieve-internal-selectielijstklasse-choices"))
763+
764+
with patch(
765+
"openarchiefbeheer.zaken.utils.APIConfig.get_solo",
766+
return_value=APIConfig(selectielijst_api_service=selectielist_service),
767+
):
768+
response = self.client.get(endpoint.url)
769+
770+
self.assertEqual(response.status_code, status.HTTP_200_OK)
771+
self.assertEqual(
772+
response.json(),
773+
[
774+
{
775+
"value": "http://selectielijst.nl/api/v1/resultaten/5038528b-0eb7-4502-a415-a3093987d69b",
776+
"label": "1 - Verleend - vernietigen - P2Y",
777+
"extraData": {
778+
"bewaartermijn": "P2Y",
779+
},
780+
},
781+
{
782+
"value": "http://selectielijst.nl/api/v1/resultaten/5d102cc6-4a74-4262-a14a-538bbfe3f2da",
783+
"label": "11.1.2 - Verleend - vernietigen",
784+
"extraData": {
785+
"bewaartermijn": None,
786+
},
787+
},
788+
],
789+
)
790+
791+
@Mocker()
792+
def test_retrieve_internal_choices_with_filters(self, m):
793+
"""
794+
Test that only the selectielijstklassen of the filtered zaken in the database are returned.
795+
"""
796+
selectielist_service = ServiceFactory.create(
797+
api_type=APITypes.orc,
798+
api_root="http://selectielijst.nl/api/v1",
799+
)
800+
user = UserFactory.create(post__can_start_destruction=True)
801+
ZaakFactory.create(
802+
identificatie="ZAAK-1",
803+
selectielijstklasse="http://selectielijst.nl/api/v1/resultaten/5038528b-0eb7-4502-a415-a3093987d69b",
804+
)
805+
ZaakFactory.create(
806+
identificatie="ZAAK-2",
807+
selectielijstklasse="http://selectielijst.nl/api/v1/resultaten/2e86a8ca-0269-446c-8da2-6f4d08be422d",
808+
)
809+
810+
m.get(
811+
"http://selectielijst.nl/api/v1/resultaten",
812+
json={
813+
"count": 3,
814+
"results": [
815+
{
816+
"url": "http://selectielijst.nl/api/v1/resultaten/2e86a8ca-0269-446c-8da2-6f4d08be422d",
817+
"nummer": 1,
818+
"volledigNummer": "11.1",
819+
"naam": "Verleend",
820+
"waardering": "vernietigen",
821+
"bewaartermijn": "P1Y",
822+
},
823+
{
824+
"url": "http://selectielijst.nl/api/v1/resultaten/5038528b-0eb7-4502-a415-a3093987d69b",
825+
"nummer": 1,
826+
"naam": "Verleend",
827+
"waardering": "vernietigen",
828+
"bewaartermijn": "P2Y",
829+
},
830+
{
831+
"url": "http://selectielijst.nl/api/v1/resultaten/5d102cc6-4a74-4262-a14a-538bbfe3f2da",
832+
"nummer": 2,
833+
"volledigNummer": "11.1.2",
834+
"naam": "Verleend",
835+
"waardering": "vernietigen",
836+
},
837+
],
838+
},
839+
)
840+
841+
self.client.force_authenticate(user=user)
842+
endpoint = furl(reverse("api:retrieve-internal-selectielijstklasse-choices"))
843+
endpoint.args["identificatie"] = "ZAAK-1"
844+
845+
with patch(
846+
"openarchiefbeheer.zaken.utils.APIConfig.get_solo",
847+
return_value=APIConfig(selectielijst_api_service=selectielist_service),
848+
):
849+
response = self.client.get(endpoint.url)
850+
851+
self.assertEqual(response.status_code, status.HTTP_200_OK)
852+
self.assertEqual(
853+
response.json(),
854+
[
855+
{
856+
"value": "http://selectielijst.nl/api/v1/resultaten/5038528b-0eb7-4502-a415-a3093987d69b",
857+
"label": "1 - Verleend - vernietigen - P2Y",
858+
"extraData": {
859+
"bewaartermijn": "P2Y",
860+
},
861+
}
862+
],
863+
)
864+
865+
658866
class ResultaattypenChoicesViewTests(ClearCacheMixin, APITestCase):
659867
def setUp(self):
660868
super().setUp()

frontend/.storybook/mockData.ts

+13-1
Original file line numberDiff line numberDiff line change
@@ -158,18 +158,30 @@ export const MOCKS = {
158158
status: 200,
159159
response: FIXTURE_SELECTIELIJSTKLASSE_CHOICES,
160160
},
161+
INTERNAL_SELECTIE_LIJST_CHOICES: {
162+
url: "http://localhost:8000/api/v1/_internal-selectielijstklasse-choices/",
163+
method: "GET",
164+
status: 200,
165+
response: FIXTURE_SELECTIELIJSTKLASSE_CHOICES,
166+
},
161167
WHOAMI: {
162168
url: "http://localhost:8000/api/v1/whoami/",
163169
method: "GET",
164170
status: 200,
165171
response: userFactory(),
166172
},
167173
ZAAKTYPE_CHOICES: {
168-
url: "http://localhost:8000/api/v1/_zaaktypen-choices",
174+
url: "http://localhost:8000/api/v1/_zaaktypen-choices/",
169175
method: "GET",
170176
status: 200,
171177
response: zaaktypeChoicesFactory(),
172178
},
179+
ZAAKTYPE_CHOICES_POST: {
180+
url: "http://localhost:8000/api/v1/_zaaktypen-choices/",
181+
method: "POST",
182+
status: 200,
183+
response: zaaktypeChoicesFactory(),
184+
},
173185
ZAAKTYPE_CHOICES_IN_REVIEW: {
174186
url: "http://localhost:8000/api/v1/_zaaktypen-choices/?inReview=1",
175187
method: "GET",

frontend/src/components/DestructionListReviewer/DestructionListReviewer.stories.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ const meta: Meta<typeof DestructionListReviewerComponent> = {
6060
MOCKS.REVIEWERS,
6161
MOCKS.DESTRUCTION_LIST_CO_REVIEWERS,
6262
MOCKS.OIDC_INFO,
63+
MOCKS.INTERNAL_SELECTIE_LIJST_CHOICES,
6364
{
6465
url: "http://localhost:8000/api/v1/users?role=co_reviewer",
6566
method: "GET",

0 commit comments

Comments
 (0)