Skip to content

Commit 2863590

Browse files
authored
Merge pull request #274 from maykinmedia/issue/#234-unrelated-items
🐛 - #234 - fix unrelated zaaktypen being shown in select on vario…
2 parents a6e1562 + 5a1bb2b commit 2863590

File tree

14 files changed

+536
-16
lines changed

14 files changed

+536
-16
lines changed

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

+11-2
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,9 @@ async def handle(route):
224224
await page.route("**/*/api/v1/_selectielijstklasse-choices/?zaak=*", handle)
225225

226226
async def zaken_are_indexed(self, amount, **kwargs):
227-
return await self._get_or_create_batch(ZaakFactory, amount, **kwargs)
227+
return await self._get_or_create_batch(
228+
ZaakFactory, amount, with_expand=True, **kwargs
229+
)
228230

229231
@sync_to_async
230232
def _orm_get(self, model, **kwargs):
@@ -254,7 +256,14 @@ async def _get_or_create(self, factory, **kwargs):
254256
return await self._factory_create(factory, **kwargs)
255257

256258
async def _get_or_create_batch(self, factory, amount, **kwargs):
257-
queryset = await self._orm_filter(factory._meta.model, **kwargs)
259+
# Remove any traits of the factory
260+
orm_params = {
261+
key: value
262+
for key, value in kwargs.items()
263+
if key not in factory._meta.parameters
264+
}
265+
266+
queryset = await self._orm_filter(factory._meta.model, **orm_params)
258267
if queryset:
259268
return queryset
260269
return await self._factory_create_batch(factory, amount, **kwargs)

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

+17-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
from rest_framework import serializers
44
from rest_framework_gis.fields import GeometryField
55

6-
from openarchiefbeheer.zaken.models import Zaak
6+
from openarchiefbeheer.destruction.models import DestructionList, DestructionListReview
7+
8+
from ..models import Zaak
79

810

911
class ZaakSerializer(serializers.ModelSerializer):
@@ -86,3 +88,17 @@ class SelectielijstklasseChoicesQueryParamSerializer(serializers.Serializer):
8688
"The URL of the zaak for which the selectielijstklasse choices are needed."
8789
)
8890
)
91+
92+
93+
class ZaakTypeChoicesQueryParamSerializer(serializers.Serializer):
94+
destruction_list = serializers.SlugRelatedField(
95+
slug_field="uuid",
96+
required=False,
97+
queryset=DestructionList.objects.all().prefetch_related("items__zaak"),
98+
)
99+
review = serializers.PrimaryKeyRelatedField(
100+
required=False,
101+
queryset=DestructionListReview.objects.all().prefetch_related(
102+
"item_reviews__destruction_list_item__zaak",
103+
),
104+
)

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

+20-2
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,17 @@
1515

1616
from ..models import Zaak
1717
from ..tasks import retrieve_and_cache_zaken_from_openzaak
18-
from ..utils import retrieve_selectielijstklasse_choices, retrieve_zaaktypen_choices
18+
from ..utils import (
19+
get_zaaktypen_choices_from_list,
20+
get_zaaktypen_choices_from_review,
21+
retrieve_selectielijstklasse_choices,
22+
retrieve_zaaktypen_choices,
23+
)
1924
from .serializers import (
2025
SelectielijstklasseChoicesQueryParamSerializer,
2126
SelectielijstklasseChoicesSerializer,
2227
ZaaktypeChoiceSerializer,
28+
ZaakTypeChoicesQueryParamSerializer,
2329
)
2430

2531

@@ -51,13 +57,25 @@ class ZaaktypenChoicesView(APIView):
5157
"The response is cached for 15 minutes."
5258
),
5359
tags=["private"],
60+
parameters=[ZaakTypeChoicesQueryParamSerializer],
5461
responses={
5562
200: ZaaktypeChoiceSerializer,
5663
},
5764
)
5865
@method_decorator(cache_page(60 * 15))
5966
def get(self, request, *args, **kwargs):
60-
zaaktypen_choices = retrieve_zaaktypen_choices()
67+
param_serializer = ZaakTypeChoicesQueryParamSerializer(
68+
data=request.query_params
69+
)
70+
param_serializer.is_valid(raise_exception=True)
71+
72+
if destruction_list := param_serializer.validated_data.get("destruction_list"):
73+
zaaktypen_choices = get_zaaktypen_choices_from_list(destruction_list)
74+
elif review := param_serializer.validated_data.get("review"):
75+
zaaktypen_choices = get_zaaktypen_choices_from_review(review)
76+
else:
77+
zaaktypen_choices = retrieve_zaaktypen_choices()
78+
6179
serializer = ZaaktypeChoiceSerializer(data=zaaktypen_choices, many=True)
6280
serializer.is_valid(raise_exception=True)
6381
return Response(serializer.data, status=status.HTTP_200_OK)

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

+1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ class Params:
3131
"url": "https://selectielijst.nl/api/v1/procestypen/7ff2b005-4d84-47fe-983a-732bfa958ff5",
3232
},
3333
"omschrijving": "Aangifte behandelen",
34+
"identificatie": "ZAAKTYPE-01",
3435
},
3536
"resultaat": {
3637
"resultaattype": "http://catalogue-api.nl/catalogi/api/v1/resultaattypen/111-111-111",

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

+188
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,13 @@
1111
from zgw_consumers.test.factories import ServiceFactory
1212

1313
from openarchiefbeheer.accounts.tests.factories import UserFactory
14+
from openarchiefbeheer.destruction.constants import ListItemStatus
15+
from openarchiefbeheer.destruction.tests.factories import (
16+
DestructionListFactory,
17+
DestructionListItemFactory,
18+
DestructionListItemReviewFactory,
19+
DestructionListReviewFactory,
20+
)
1421

1522
from ..tasks import retrieve_and_cache_zaken_from_openzaak
1623
from .factories import ZaakFactory
@@ -172,6 +179,187 @@ def test_response_cached(self, m):
172179
history = m.request_history
173180
self.assertEqual(len(history), 1)
174181

182+
def test_retrieve_zaaktypen_choices_for_destruction_list(self):
183+
user = UserFactory.create(role__can_start_destruction=True)
184+
185+
destruction_list = DestructionListFactory.create()
186+
# The zaaktypen of these items should be returned,
187+
# because they are in the destruction list with status 'suggested'
188+
items_in_list = DestructionListItemFactory.create_batch(
189+
3,
190+
with_zaak=True,
191+
zaak__with_expand=True,
192+
destruction_list=destruction_list,
193+
status=ListItemStatus.suggested,
194+
)
195+
# We simulate 2 items having different versions of the same zaaktype
196+
items_in_list[0].zaak._expand["zaaktype"]["identificatie"] = "ZAAKTYPE-1"
197+
items_in_list[0].zaak.save()
198+
items_in_list[1].zaak._expand["zaaktype"]["identificatie"] = "ZAAKTYPE-1"
199+
items_in_list[1].zaak.save()
200+
items_in_list[2].zaak._expand["zaaktype"]["identificatie"] = "ZAAKTYPE-2"
201+
items_in_list[2].zaak.save()
202+
203+
# This zaaktype should NOT be returned because it has status 'removed'
204+
DestructionListItemFactory.create(
205+
with_zaak=True,
206+
destruction_list=destruction_list,
207+
status=ListItemStatus.removed,
208+
)
209+
# These zaaktypen should NOT be returned because it is not related to the list
210+
DestructionListItemFactory.create_batch(2, with_zaak=True)
211+
212+
self.client.force_authenticate(user=user)
213+
endpoint = furl(reverse("api:retrieve-zaaktypen-choices"))
214+
endpoint.args["destruction_list"] = destruction_list.uuid
215+
216+
response = self.client.get(endpoint.url)
217+
218+
self.assertEqual(response.status_code, status.HTTP_200_OK)
219+
220+
choices = sorted(response.json(), key=lambda choice: choice["label"])
221+
222+
self.assertEqual(len(choices), 2)
223+
self.assertEqual(choices[0]["label"], "ZAAKTYPE-1")
224+
self.assertEqual(choices[1]["label"], "ZAAKTYPE-2")
225+
226+
values = choices[0]["value"].split(",")
227+
228+
self.assertEqual(len(values), 2)
229+
self.assertIn(items_in_list[0].zaak.zaaktype, values)
230+
self.assertIn(items_in_list[1].zaak.zaaktype, values)
231+
232+
self.assertEqual(choices[1]["value"], items_in_list[2].zaak.zaaktype)
233+
234+
@Mocker()
235+
def test_not_cached_if_query_param_chages(self, m):
236+
user = UserFactory.create(role__can_start_destruction=True)
237+
238+
destruction_list = DestructionListFactory.create()
239+
240+
items_in_list = DestructionListItemFactory.create_batch(
241+
2,
242+
with_zaak=True,
243+
zaak__with_expand=True,
244+
destruction_list=destruction_list,
245+
status=ListItemStatus.suggested,
246+
)
247+
items_in_list[0].zaak._expand["zaaktype"]["identificatie"] = "ZAAKTYPE-1"
248+
items_in_list[0].zaak.save()
249+
items_in_list[1].zaak._expand["zaaktype"]["identificatie"] = "ZAAKTYPE-2"
250+
items_in_list[1].zaak.save()
251+
252+
ServiceFactory.create(
253+
api_type=APITypes.ztc,
254+
api_root="http://catalogi-api.nl/catalogi/api/v1",
255+
)
256+
ZaakFactory.create(
257+
zaaktype="http://catalogi-api.nl/catalogi/api/v1/zaakypen/111-111-111",
258+
_expand={"zaaktype": {"identificatie": "ZAAKTYPE-3"}},
259+
)
260+
ZaakFactory.create(
261+
zaaktype="http://catalogi-api.nl/catalogi/api/v1/zaakypen/222-222-222",
262+
_expand={"zaaktype": {"identificatie": "ZAAKTYPE-4"}},
263+
)
264+
ZaakFactory.create(
265+
zaaktype="http://catalogi-api.nl/catalogi/api/v1/zaakypen/333-333-333",
266+
_expand={"zaaktype": {"identificatie": "ZAAKTYPE-5"}},
267+
)
268+
269+
m.get(
270+
"http://catalogi-api.nl/catalogi/api/v1/zaaktypen",
271+
json={
272+
"count": 2,
273+
"results": [
274+
{
275+
"url": "http://catalogi-api.nl/catalogi/api/v1/zaakypen/111-111-111",
276+
"identificatie": "ZAAKTYPE-3",
277+
},
278+
{
279+
"url": "http://catalogi-api.nl/catalogi/api/v1/zaakypen/222-222-222",
280+
"identificatie": "ZAAKTYPE-4",
281+
},
282+
{
283+
"url": "http://catalogi-api.nl/catalogi/api/v1/zaakypen/333-333-333",
284+
"identificatie": "ZAAKTYPE-5",
285+
},
286+
],
287+
},
288+
)
289+
290+
self.client.force_authenticate(user=user)
291+
endpoint = furl(reverse("api:retrieve-zaaktypen-choices"))
292+
endpoint.args["destruction_list"] = destruction_list.uuid
293+
294+
response = self.client.get(endpoint.url)
295+
296+
self.assertEqual(response.status_code, status.HTTP_200_OK)
297+
self.assertEqual(len(response.json()), 2)
298+
299+
del endpoint.args["destruction_list"]
300+
response = self.client.get(endpoint.url)
301+
302+
self.assertEqual(response.status_code, status.HTTP_200_OK)
303+
self.assertEqual(len(response.json()), 3)
304+
305+
def test_retrieve_zaaktypen_choices_for_review(self):
306+
user = UserFactory.create(role__can_start_destruction=True)
307+
308+
review = DestructionListReviewFactory.create()
309+
# The zaaktypen of these items should be returned,
310+
# because they are in the review
311+
review_items = DestructionListItemReviewFactory.create_batch(
312+
3,
313+
destruction_list_item__with_zaak=True,
314+
destruction_list_item__zaak__with_expand=True,
315+
destruction_list=review.destruction_list,
316+
review=review,
317+
)
318+
# We simulate 2 items having different versions of the same zaaktype
319+
review_items[0].destruction_list_item.zaak._expand["zaaktype"][
320+
"identificatie"
321+
] = "ZAAKTYPE-1"
322+
review_items[0].destruction_list_item.zaak.save()
323+
review_items[1].destruction_list_item.zaak._expand["zaaktype"][
324+
"identificatie"
325+
] = "ZAAKTYPE-1"
326+
review_items[1].destruction_list_item.zaak.save()
327+
review_items[2].destruction_list_item.zaak._expand["zaaktype"][
328+
"identificatie"
329+
] = "ZAAKTYPE-2"
330+
review_items[2].destruction_list_item.zaak.save()
331+
332+
# These zaaktypen should NOT be returned because they are not in the review
333+
DestructionListItemReviewFactory.create_batch(
334+
3,
335+
destruction_list_item__with_zaak=True,
336+
destruction_list_item__zaak__with_expand=True,
337+
)
338+
339+
self.client.force_authenticate(user=user)
340+
endpoint = furl(reverse("api:retrieve-zaaktypen-choices"))
341+
endpoint.args["review"] = review.pk
342+
343+
response = self.client.get(endpoint.url)
344+
345+
self.assertEqual(response.status_code, status.HTTP_200_OK)
346+
347+
choices = sorted(response.json(), key=lambda choice: choice["label"])
348+
349+
self.assertEqual(len(choices), 2)
350+
self.assertEqual(choices[0]["label"], "ZAAKTYPE-1")
351+
self.assertEqual(choices[1]["label"], "ZAAKTYPE-2")
352+
353+
values = choices[0]["value"].split(",")
354+
355+
self.assertEqual(len(values), 2)
356+
self.assertIn(review_items[0].destruction_list_item.zaak.zaaktype, values)
357+
self.assertIn(review_items[1].destruction_list_item.zaak.zaaktype, values)
358+
359+
self.assertEqual(
360+
choices[1]["value"], review_items[2].destruction_list_item.zaak.zaaktype
361+
)
362+
175363

176364
class SelectielijstklasseChoicesViewTests(APITestCase):
177365
def setUp(self):

backend/src/openarchiefbeheer/zaken/utils.py

+46-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import traceback
22
from collections import defaultdict
33
from functools import lru_cache, partial
4-
from typing import Callable, Generator, Literal
4+
from typing import TYPE_CHECKING, Callable, Generator, Literal
55

66
from django.conf import settings
77

@@ -18,11 +18,18 @@
1818
from zgw_consumers.models import Service
1919
from zgw_consumers.utils import PaginatedResponseData
2020

21+
from openarchiefbeheer.destruction.constants import ListItemStatus
2122
from openarchiefbeheer.utils.results_store import ResultStore
2223

2324
from .models import Zaak
2425
from .types import DropDownChoice
2526

27+
if TYPE_CHECKING:
28+
from openarchiefbeheer.destruction.models import (
29+
DestructionList,
30+
DestructionListReview,
31+
)
32+
2633

2734
def pagination_helper(
2835
client: APIClient, paginated_response: PaginatedResponseData, **kwargs
@@ -108,6 +115,44 @@ def retrieve_zaaktypen_choices() -> list[DropDownChoice]:
108115
return sorted(zaaktypen_choices, key=lambda zaaktype: zaaktype["label"])
109116

110117

118+
def _get_zaaktype_choices(zaaktypen_to_include: dict[list]) -> list[DropDownChoice]:
119+
"""Return formatted zaaktype choices
120+
121+
Takes a dictionary where the key is the identificatie of a zaaktype and
122+
the value is a list of all the URLs for the different versions of that zaaktype."""
123+
zaaktypen = defaultdict(list)
124+
for zaaktype_url, zaaktype_identificatie in zaaktypen_to_include:
125+
zaaktypen[zaaktype_identificatie].append(zaaktype_url)
126+
127+
zaaktypen_choices = [
128+
{"label": key, "value": ",".join(value)} for key, value in zaaktypen.items()
129+
]
130+
return sorted(zaaktypen_choices, key=lambda zaaktype: zaaktype["label"])
131+
132+
133+
def get_zaaktypen_choices_from_list(
134+
destruction_list: "DestructionList",
135+
) -> list[DropDownChoice]:
136+
zaaktypen_to_include = (
137+
destruction_list.items.filter(status=ListItemStatus.suggested)
138+
.distinct("zaak__zaaktype")
139+
.values_list("zaak__zaaktype", "zaak___expand__zaaktype__identificatie")
140+
)
141+
return _get_zaaktype_choices(zaaktypen_to_include)
142+
143+
144+
def get_zaaktypen_choices_from_review(
145+
review: "DestructionListReview",
146+
) -> list[DropDownChoice]:
147+
zaaktypen_to_include = review.item_reviews.distinct(
148+
"destruction_list_item__zaak__zaaktype"
149+
).values_list(
150+
"destruction_list_item__zaak__zaaktype",
151+
"destruction_list_item__zaak___expand__zaaktype__identificatie",
152+
)
153+
return _get_zaaktype_choices(zaaktypen_to_include)
154+
155+
111156
def format_selectielijstklasse_choice(resultaat: Resultaat) -> DropDownChoice:
112157
description = f"{resultaat.get('volledig_nummer', resultaat['nummer'])} - {resultaat['naam']} - {resultaat['waardering']}"
113158
if resultaat.get("bewaartermijn"):

0 commit comments

Comments
 (0)