Skip to content

Commit e4ca185

Browse files
authored
Merge pull request #708 from maykinmedia/feature/706-resutaattype-filter
[#706] Resutaattype filter - Backend
2 parents b10f830 + 9bd92fb commit e4ca185

File tree

11 files changed

+258
-22
lines changed

11 files changed

+258
-22
lines changed

backend/docs/developers/logic.rst

+10-1
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,13 @@ Some aspects about the selectielijst resources that can be confusing:
3535
- Each zaak has an associated selectielijstklasse (a URL to a resource in the https://selectielijst.openzaak.nl/api/v1/resultaten API).
3636
Each selectielijstklasse is associated with a selectielijstprocestype.
3737
The zaak can only be related to a selectielijstklasse whose procestype matches the procestype related to the zaaktype
38-
of the zaak.
38+
of the zaak.
39+
40+
Zaaktypen
41+
=========
42+
43+
Facts to remember about the zaaktypen:
44+
45+
- Published zaaktypen only have a unique constraint on the ``uuid`` in the database.
46+
- However, they should be unique within a ``catalogus`` on ``identificatie`` and begin/eind geldigheid.
47+
- This means that different version of a same zaaktype would have same identificatie but different begin/eind geldigheid. Their ``uuid`` would be different.

backend/src/openarchiefbeheer/api/urls.py

+10-4
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,11 @@
3131
)
3232
from openarchiefbeheer.zaken.api.views import (
3333
CacheZakenView,
34+
ExternalResultaattypeChoicesView,
3435
ExternalZaaktypenChoicesView,
3536
InformatieobjecttypeChoicesView,
37+
InternalResultaattypeChoicesView,
3638
InternalZaaktypenChoicesView,
37-
ResultaattypeChoicesView,
3839
SelectielijstklasseChoicesView,
3940
StatustypeChoicesView,
4041
)
@@ -172,9 +173,14 @@
172173
name="retrieve-informatieobjecttype-choices",
173174
),
174175
path(
175-
"_resultaattype-choices/",
176-
ResultaattypeChoicesView.as_view(),
177-
name="retrieve-resultaattype-choices",
176+
"_external-resultaattype-choices/",
177+
ExternalResultaattypeChoicesView.as_view(),
178+
name="retrieve-external-resultaattype-choices",
179+
),
180+
path(
181+
"_internal-resultaattype-choices/",
182+
InternalResultaattypeChoicesView.as_view(),
183+
name="retrieve-internal-resultaattype-choices",
178184
),
179185
path("", include(router.urls)),
180186
path("", include(destruction_list_router.urls)),

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,7 @@ async def handle(route):
334334
]
335335
await route.fulfill(json=json)
336336

337-
await page.route("**/*/api/v1/_resultaattype-choices/*", handle)
337+
await page.route("**/*/api/v1/_external-resultaattype-choices/*", handle)
338338

339339
async def selectielijstklasse_choices_are_available(self, page):
340340
async def handle(route):

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

+3-3
Original file line numberDiff line numberDiff line change
@@ -106,9 +106,9 @@ class ZaakFilterSet(FilterSet):
106106
field_name="_expand__zaaktype__omschrijving", lookup_expr="icontains"
107107
)
108108

109-
resultaat__resultaattype__omschrijving__icontains = CharFilter(
110-
field_name="_expand__resultaat___expand__resultaattype__omschrijving",
111-
lookup_expr="icontains",
109+
resultaat__resultaattype = CharFilter(
110+
field_name="_expand__resultaat___expand__resultaattype__url",
111+
help_text="Filter the zaken that have a resultaat of this resultaattype.",
112112
)
113113

114114
resultaat__resultaattype__archiefactietermijn__icontains = CharFilter(

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

+42-1
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ def get(self, request, *args, **kwargs):
213213
return Response(serializer.data, status=status.HTTP_200_OK)
214214

215215

216-
class ResultaattypeChoicesView(FilterOnZaaktypeMixin, APIView):
216+
class ExternalResultaattypeChoicesView(FilterOnZaaktypeMixin, APIView):
217217
permission_classes = [IsAuthenticated]
218218

219219
@extend_schema(
@@ -237,3 +237,44 @@ def get(self, request, *args, **kwargs):
237237
serializer.is_valid(raise_exception=True)
238238

239239
return Response(serializer.data, status=status.HTTP_200_OK)
240+
241+
242+
class InternalResultaattypeChoicesView(APIView):
243+
permission_classes = [IsAuthenticated]
244+
filter_backends = (NoModelFilterBackend,)
245+
filterset_class = ZaakFilterSet
246+
247+
@extend_schema(
248+
summary=_("Retrieve internal resultaattype choices"),
249+
description=_("Retrieve the resultaattypen of the zaken in the database. "),
250+
tags=["private"],
251+
responses={
252+
200: ChoiceSerializer(many=True),
253+
},
254+
)
255+
@method_decorator(cache_page(60 * 15))
256+
def get(self, request, *args, **kwargs):
257+
filterset = ZaakFilterSet(data=request.query_params or request.data)
258+
is_valid = filterset.is_valid()
259+
if not is_valid:
260+
raise ValidationError(filterset.errors)
261+
262+
zaken_resultaattypen = filterset.qs.filter(
263+
_expand__resultaat___expand__resultaattype__isnull=False
264+
).values_list("_expand__resultaat___expand__resultaattype", flat=True)
265+
266+
existing_resultaattypen = []
267+
formatted_choices = []
268+
for resultaattype in zaken_resultaattypen:
269+
if resultaattype["url"] in existing_resultaattypen:
270+
continue
271+
272+
existing_resultaattypen.append(resultaattype["url"])
273+
formatted_choices.append(
274+
{
275+
"label": resultaattype["omschrijving"],
276+
"value": resultaattype["url"],
277+
}
278+
)
279+
280+
return Response(formatted_choices, status=status.HTTP_200_OK)

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

+144-5
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ def test_retrieve_zaaktypen_choices(self):
114114

115115
self.assertEqual(response.status_code, status.HTTP_200_OK)
116116
self.assertEqual(
117-
response.json(),
117+
sorted(response.json(), key=lambda choice: choice["label"]),
118118
[
119119
{
120120
"value": "http://catalogi-api.nl/catalogi/api/v1/zaakypen/111-111-111,http://catalogi-api.nl/catalogi/api/v1/zaakypen/222-222-222",
@@ -685,7 +685,7 @@ def setUp(self):
685685
self.addCleanup(retrieve_paginated_type.cache_clear)
686686

687687
def test_not_authenticated(self):
688-
endpoint = reverse("api:retrieve-resultaattype-choices")
688+
endpoint = reverse("api:retrieve-external-resultaattype-choices")
689689

690690
response = self.client.get(endpoint)
691691

@@ -717,7 +717,9 @@ def test_retrieve_choices(self, m):
717717
)
718718

719719
self.client.force_authenticate(user=user)
720-
response = self.client.get(reverse("api:retrieve-resultaattype-choices"))
720+
response = self.client.get(
721+
reverse("api:retrieve-external-resultaattype-choices")
722+
)
721723

722724
self.assertEqual(response.status_code, status.HTTP_200_OK)
723725
self.assertEqual(
@@ -760,12 +762,16 @@ def test_retrieve_choices_caches_request(self, m):
760762
)
761763

762764
self.client.force_authenticate(user=user)
763-
response = self.client.get(reverse("api:retrieve-resultaattype-choices"))
765+
response = self.client.get(
766+
reverse("api:retrieve-external-resultaattype-choices")
767+
)
764768

765769
self.assertEqual(response.status_code, status.HTTP_200_OK)
766770

767771
# Repeat request
768-
response = self.client.get(reverse("api:retrieve-resultaattype-choices"))
772+
response = self.client.get(
773+
reverse("api:retrieve-external-resultaattype-choices")
774+
)
769775

770776
self.assertEqual(response.status_code, status.HTTP_200_OK)
771777

@@ -963,3 +969,136 @@ def test_retrieve_choices_caches_request(self, m):
963969

964970
# Only one call to openzaak
965971
self.assertEqual(len(m.request_history), 1)
972+
973+
974+
class InternalResultaattypeChoicesViewTest(APITestCase):
975+
def setUp(self):
976+
super().setUp()
977+
978+
self.addCleanup(cache.clear)
979+
980+
def test_not_authenticated(self):
981+
endpoint = reverse("api:retrieve-internal-resultaattype-choices")
982+
983+
response = self.client.get(endpoint)
984+
985+
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
986+
987+
def test_get_all_choices(self):
988+
user = UserFactory.create()
989+
990+
ZaakFactory.create(
991+
post___expand={
992+
"resultaat": {
993+
"url": "http://localhost:8003/zaken/api/v1/resultaten/821b4d8f-3244-4ece-8d33-791fa6d2a2f3",
994+
"uuid": "821b4d8f-3244-4ece-8d33-791fa6d2a2f3",
995+
"_expand": {
996+
"resultaattype": {
997+
"url": "http://localhost:8003/catalogi/api/v1/resultaattypen/111-111-111",
998+
"omschrijving": "Afgehandeld",
999+
}
1000+
},
1001+
"toelichting": "Testing resultaten",
1002+
"resultaattype": "http://localhost:8003/catalogi/api/v1/resultaattypen/111-111-111",
1003+
}
1004+
},
1005+
)
1006+
ZaakFactory.create(
1007+
post___expand={
1008+
"resultaat": {
1009+
"url": "http://localhost:8003/zaken/api/v1/resultaten/821b4d8f-3244-4ece-8d33-791fa6d2a2f3",
1010+
"uuid": "821b4d8f-3244-4ece-8d33-791fa6d2a2f3",
1011+
"_expand": {
1012+
"resultaattype": {
1013+
"url": "http://localhost:8003/catalogi/api/v1/resultaattypen/222-222-222",
1014+
"omschrijving": "Lopend",
1015+
}
1016+
},
1017+
"toelichting": "Testing resultaten",
1018+
"resultaattype": "http://localhost:8003/catalogi/api/v1/resultaattypen/222-222-222",
1019+
}
1020+
},
1021+
)
1022+
1023+
self.client.force_authenticate(user=user)
1024+
response = self.client.get(
1025+
reverse("api:retrieve-internal-resultaattype-choices")
1026+
)
1027+
1028+
self.assertEqual(response.status_code, status.HTTP_200_OK)
1029+
1030+
data = response.json()
1031+
1032+
self.assertEqual(len(data), 2)
1033+
self.assertEqual(
1034+
[
1035+
{
1036+
"value": "http://localhost:8003/catalogi/api/v1/resultaattypen/111-111-111",
1037+
"label": "Afgehandeld",
1038+
},
1039+
{
1040+
"value": "http://localhost:8003/catalogi/api/v1/resultaattypen/222-222-222",
1041+
"label": "Lopend",
1042+
},
1043+
],
1044+
sorted(data, key=lambda choice: choice["value"]),
1045+
)
1046+
1047+
def test_get_choices_with_filters(self):
1048+
user = UserFactory.create()
1049+
1050+
ZaakFactory.create(
1051+
identificatie="ZAAK-01",
1052+
post___expand={
1053+
"resultaat": {
1054+
"url": "http://localhost:8003/zaken/api/v1/resultaten/821b4d8f-3244-4ece-8d33-791fa6d2a2f3",
1055+
"uuid": "821b4d8f-3244-4ece-8d33-791fa6d2a2f3",
1056+
"_expand": {
1057+
"resultaattype": {
1058+
"url": "http://localhost:8003/catalogi/api/v1/resultaattypen/111-111-111",
1059+
"omschrijving": "Afgehandeld",
1060+
}
1061+
},
1062+
"toelichting": "Testing resultaten",
1063+
"resultaattype": "http://localhost:8003/catalogi/api/v1/resultaattypen/111-111-111",
1064+
}
1065+
},
1066+
)
1067+
ZaakFactory.create(
1068+
identificatie="ZAAK-02",
1069+
post___expand={
1070+
"resultaat": {
1071+
"url": "http://localhost:8003/zaken/api/v1/resultaten/821b4d8f-3244-4ece-8d33-791fa6d2a2f3",
1072+
"uuid": "821b4d8f-3244-4ece-8d33-791fa6d2a2f3",
1073+
"_expand": {
1074+
"resultaattype": {
1075+
"url": "http://localhost:8003/catalogi/api/v1/resultaattypen/222-222-222",
1076+
"omschrijving": "Lopend",
1077+
}
1078+
},
1079+
"toelichting": "Testing resultaten",
1080+
"resultaattype": "http://localhost:8003/catalogi/api/v1/resultaattypen/222-222-222",
1081+
}
1082+
},
1083+
)
1084+
1085+
self.client.force_authenticate(user=user)
1086+
endpoint = furl(reverse("api:retrieve-internal-resultaattype-choices"))
1087+
endpoint.args["identificatie"] = "ZAAK-01"
1088+
1089+
response = self.client.get(endpoint.url)
1090+
1091+
self.assertEqual(response.status_code, status.HTTP_200_OK)
1092+
1093+
data = response.json()
1094+
1095+
self.assertEqual(len(data), 1)
1096+
self.assertEqual(
1097+
[
1098+
{
1099+
"value": "http://localhost:8003/catalogi/api/v1/resultaattypen/111-111-111",
1100+
"label": "Afgehandeld",
1101+
},
1102+
],
1103+
data,
1104+
)

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

+35
Original file line numberDiff line numberDiff line change
@@ -122,3 +122,38 @@ def test_filter_zaken_with_removed_zaken(self):
122122

123123
self.assertEqual(response.status_code, status.HTTP_200_OK)
124124
self.assertEqual(data["count"], 10)
125+
126+
def test_filter_zaken_on_resultaat(self):
127+
zaken = ZaakFactory.create_batch(3)
128+
zaken[0]._expand.update(
129+
{
130+
"resultaat": {
131+
"url": "http://localhost:8003/zaken/api/v1/resultaten/c81e0154-7d0f-4f1f-9264-06c45127d6a4",
132+
"uuid": "c81e0154-7d0f-4f1f-9264-06c45127d6a4",
133+
"_expand": {
134+
"resultaattype": {
135+
"url": "http://localhost:8003/catalogi/api/v1/resultaattypen/7759dcb7-de9a-4543-99e3-81472c488f32",
136+
"resultaattypeomschrijving": "Lopend",
137+
}
138+
},
139+
"toelichting": "Testing resultaattype",
140+
"resultaattype": "http://localhost:8003/catalogi/api/v1/resultaattypen/7759dcb7-de9a-4543-99e3-81472c488f32",
141+
}
142+
}
143+
)
144+
zaken[0].identificatie = "ZAAK-WITH-RESULTAAT"
145+
zaken[0].save()
146+
147+
user = UserFactory(username="record_manager", post__can_start_destruction=True)
148+
149+
self.client.force_authenticate(user)
150+
endpoint = furl(reverse("api:zaken-list"))
151+
endpoint.args["resultaat__resultaattype"] = (
152+
"http://localhost:8003/catalogi/api/v1/resultaattypen/7759dcb7-de9a-4543-99e3-81472c488f32"
153+
)
154+
response = self.client.get(endpoint.url)
155+
data = response.json()
156+
157+
self.assertEqual(response.status_code, status.HTTP_200_OK)
158+
self.assertEqual(data["count"], 1)
159+
self.assertEqual(data["results"][0]["identificatie"], "ZAAK-WITH-RESULTAAT")

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

+4-2
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,9 @@ def test_retrieve_all_choices(self):
9898
user = UserFactory.create()
9999

100100
self.client.force_authenticate(user=user)
101-
response = self.client.get(reverse("api:retrieve-resultaattype-choices"))
101+
response = self.client.get(
102+
reverse("api:retrieve-external-resultaattype-choices")
103+
)
102104

103105
self.assertEqual(response.status_code, status.HTTP_200_OK)
104106

@@ -120,7 +122,7 @@ def test_retrieve_all_choices(self):
120122
def test_retrieve_choices_with_filters(self):
121123
user = UserFactory.create()
122124

123-
endpoint = furl(reverse("api:retrieve-resultaattype-choices"))
125+
endpoint = furl(reverse("api:retrieve-external-resultaattype-choices"))
124126
endpoint.args["zaaktype"] = (
125127
"http://localhost:8003/catalogi/api/v1/zaaktypen/be210495-20b6-48ff-8d3d-3e44f74c43a4"
126128
)

backend/src/openarchiefbeheer/zaken/utils.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ def format_zaaktype_choices(zaaktypen: Iterable[dict]) -> list[DropDownChoice]:
144144
for identificatie, urls in zaaktypen_per_version.items():
145145
omschrijving = id_to_omschrijving_map[identificatie]["omschrijving"]
146146
label = f"{omschrijving} ({identificatie or _("no identificatie")})"
147-
value = ",".join(urls)
147+
value = ",".join(sorted(urls))
148148
formatted_zaaktypen.append({"label": label, "value": value})
149149
formatted_zaaktypen = sorted(formatted_zaaktypen, key=lambda x: x["label"])
150150
return formatted_zaaktypen

frontend/src/lib/api/private.ts

+7-3
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,13 @@ export async function listResultaatTypeChoices(zaaktypeUrl?: string) {
5252
return cacheMemo(
5353
"listResultaatTypeChoices",
5454
async () => {
55-
const response = await request("GET", "/_resultaattype-choices/", {
56-
zaaktype: zaaktypeUrl,
57-
});
55+
const response = await request(
56+
"GET",
57+
"/_external-resultaattype-choices/",
58+
{
59+
zaaktype: zaaktypeUrl,
60+
},
61+
);
5862
const promise: Promise<Option[]> = response.json();
5963

6064
return promise;

0 commit comments

Comments
 (0)