Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[#377] Fix review responses #382

Merged
merged 7 commits into from
Sep 26, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion backend/docs/developers/index.rst
Original file line number Diff line number Diff line change
@@ -14,4 +14,5 @@ Content

e2e-tests
gherkin-like-tests
vcr-tests
vcr-tests
logic
14 changes: 14 additions & 0 deletions backend/docs/developers/logic.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
.. _developers_logic:


Processing a review
===================

After a reviewer has reviewed a destruction list, the record manager needs to process the feedback.
For each zaak that has been "rejected" by the reviewer, the record manager can:

- Keep the zaak in the destruction list (go against the suggestion of the reviewer). In this case, no details of the zaak can be updated.
- Remove the zaak from the destruction list. When removing the zaak, the record manager should either:

- Update the selectielijstklasse of the zaak. This should automatically update the archiefactiedatum of the zaak (this is currently done by the frontend). This is for example desirable in the case that the zaak has the wrong selectielijstklasse by accident.
- Update the archiefactiedatum of the zaak. In this case the selectielijstklasse is correct, but for whatever reason the zaak needs to be kept for longer.
44 changes: 44 additions & 0 deletions backend/src/openarchiefbeheer/destruction/api/serializers.py
Original file line number Diff line number Diff line change
@@ -18,11 +18,13 @@
from openarchiefbeheer.zaken.models import Zaak

from ..constants import (
DestructionListItemAction,
InternalStatus,
ListItemStatus,
ListRole,
ListStatus,
ReviewDecisionChoices,
ZaakActionType,
)
from ..models import (
DestructionList,
@@ -502,11 +504,53 @@ class Meta:
"pk",
"review_item",
"action_item",
"action_zaak_type",
"action_zaak",
"created",
"comment",
)

def validate(self, attrs: dict) -> dict:
action_zaak = attrs.get("action_zaak", {})
if attrs["action_item"] == DestructionListItemAction.keep and action_zaak:
raise ValidationError(
{
"action_zaak": _(
"The case cannot be changed if it is kept in the destruction list."
)
}
)

zaak_action_type = attrs.get("action_zaak_type")
selectielijstklasse = action_zaak.get("selectielijstklasse")
if (
attrs["action_item"] == DestructionListItemAction.remove
and zaak_action_type == ZaakActionType.bewaartermijn
and selectielijstklasse
):
raise ValidationError(
{
"action_zaak_type": _(
"The selectielijstklasse cannot be changed if the case action type is 'bewaartermijn'."
)
}
)

if (
attrs["action_item"] == DestructionListItemAction.remove
and zaak_action_type == ZaakActionType.selectielijstklasse_and_bewaartermijn
and not selectielijstklasse
):
raise ValidationError(
{
"selectielijstklasse": _(
"The selectielijstklasse is required for action type is 'selectielijstklasse_and_bewaartermijn'."
)
}
)

return attrs


class ReviewResponseSerializer(serializers.ModelSerializer):
items_responses = ReviewItemResponseSerializer(many=True)
7 changes: 7 additions & 0 deletions backend/src/openarchiefbeheer/destruction/constants.py
Original file line number Diff line number Diff line change
@@ -33,6 +33,13 @@ class DestructionListItemAction(models.TextChoices):
remove = "remove", _("remove")


class ZaakActionType(models.TextChoices):
selectielijstklasse_and_bewaartermijn = "selectielijstklasse_and_bewaartermijn", _(
"selectielijstklasse and bewaartermijn"
)
bewaartermijn = "bewaartermijn", _("bewaartermijn")


class InternalStatus(models.TextChoices):
new = "new", _("new")
queued = "queued", _("queued")
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Generated by Django 4.2.15 on 2024-09-23 13:12

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("destruction", "0021_destructionlist_planned_destruction_date"),
]

operations = [
migrations.AddField(
model_name="reviewitemresponse",
name="action_zaak_type",
field=models.CharField(
blank=True,
choices=[
(
"selectielijstklasse_and_bewaartermijn",
"selectielijstklasse and bewaartermijn",
),
("bewaartermijn", "bewaartermijn"),
],
help_text="What type of change to do on the case. ",
max_length=80,
verbose_name="action zaak type",
),
),
]
11 changes: 9 additions & 2 deletions backend/src/openarchiefbeheer/destruction/models.py
Original file line number Diff line number Diff line change
@@ -24,6 +24,7 @@
ListRole,
ListStatus,
ReviewDecisionChoices,
ZaakActionType,
)
from .exceptions import ZaakArchiefactiedatumInFuture, ZaakNotFound

@@ -468,6 +469,13 @@ class ReviewItemResponse(models.Model):
choices=DestructionListItemAction.choices,
max_length=80,
)
action_zaak_type = models.CharField(
_("action zaak type"),
choices=ZaakActionType.choices,
max_length=80,
help_text=_("What type of change to do on the case. "),
blank=True,
)
action_zaak = models.JSONField(
_("action case"),
blank=True,
@@ -501,7 +509,7 @@ class Meta:
def __str__(self):
return f"Response to {self.review_item}"

def process(self):
def process(self) -> None:
if self.processing_status == InternalStatus.succeeded:
return

@@ -514,7 +522,6 @@ def process(self):
destruction_list_item.status = ListItemStatus.removed
destruction_list_item.save()

if self.action_zaak:
destruction_list_item.zaak.update_data(self.action_zaak)

self.processing_status = InternalStatus.succeeded
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
from django.utils.translation import gettext_lazy as _

import freezegun
from furl import furl
from rest_framework import status
from rest_framework.reverse import reverse
from rest_framework.test import APITestCase

from openarchiefbeheer.accounts.tests.factories import UserFactory

from ...constants import DestructionListItemAction, ListStatus, ZaakActionType
from ...models import ReviewItemResponse, ReviewResponse
from ..factories import (
DestructionListAssigneeFactory,
DestructionListFactory,
DestructionListItemReviewFactory,
DestructionListReviewFactory,
ReviewItemResponseFactory,
ReviewResponseFactory,
)


class ReviewResponsesViewSetTests(APITestCase):
def test_no_auth(self):
endpoint = reverse("api:review-responses-list")

response = self.client.get(endpoint)

self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)

def test_filter_on_review(self):
user = UserFactory.create()

review = DestructionListReviewFactory.create(destruction_list__name="List 1")
review_response = ReviewResponseFactory.create(review=review)
ReviewItemResponseFactory.create_batch(2, review_item__review=review)
another_response = ReviewResponseFactory.create()
ReviewItemResponseFactory.create(review_item__review=another_response.review)

endpoint = furl(reverse("api:review-responses-list"))
endpoint.args["review"] = review.pk

self.client.force_authenticate(user=user)
response = self.client.get(endpoint.url)

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

data = response.json()

self.assertEqual(len(data), 1)
self.assertEqual(data[0]["pk"], review_response.pk)
self.assertEqual(len(data[0]["itemsResponses"]), 2)

def test_create_review_response(self):
record_manager = UserFactory.create(role__can_start_destruction=True)
review = DestructionListReviewFactory.create(
destruction_list__author=record_manager,
destruction_list__status=ListStatus.changes_requested,
destruction_list__assignee=record_manager,
)
items_reviews = DestructionListItemReviewFactory.create_batch(
3,
destruction_list_item__destruction_list=review.destruction_list,
review=review,
)

endpoint = reverse("api:review-responses-list")
self.client.force_authenticate(user=record_manager)

response = self.client.post(
endpoint,
data={
"review": review.pk,
"comment": "A comment about the review.",
"itemsResponses": [
{
"reviewItem": items_reviews[0].pk,
"actionItem": DestructionListItemAction.keep,
"comment": "This zaak needs to stay in the list.",
},
{
"reviewItem": items_reviews[1].pk,
"actionItem": DestructionListItemAction.remove,
"actionZaak": {
"action_type": ZaakActionType.selectielijstklasse_and_bewaartermijn,
"selectielijstklasse": "http://some-url.nl",
"archiefactiedatum": "2030-01-01",
},
"comment": "Changed the selectielijstklasse and removed from the list.",
},
{
"reviewItem": items_reviews[2].pk,
"actionItem": DestructionListItemAction.remove,
"actionZaak": {
"action_type": ZaakActionType.bewaartermijn,
"archiefactiedatum": "2030-01-01",
},
"comment": "Changed the archiefactiedatum and removed from the list.",
},
],
},
format="json",
)

self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(ReviewResponse.objects.filter(review=review).count(), 1)
self.assertEqual(
ReviewItemResponse.objects.filter(review_item__review=review).count(), 3
)

item_response1 = ReviewItemResponse.objects.get(review_item=items_reviews[0].pk)

self.assertEqual(item_response1.action_item, DestructionListItemAction.keep)
self.assertEqual(item_response1.comment, "This zaak needs to stay in the list.")

item_response2 = ReviewItemResponse.objects.get(review_item=items_reviews[1].pk)

self.assertEqual(
item_response2.action_zaak["selectielijstklasse"], "http://some-url.nl"
)
self.assertEqual(item_response2.action_zaak["archiefactiedatum"], "2030-01-01")

item_response3 = ReviewItemResponse.objects.get(review_item=items_reviews[2].pk)

self.assertEqual(item_response3.action_zaak["archiefactiedatum"], "2030-01-01")

def test_cannot_create_response_if_not_author(self):
record_manager1 = UserFactory.create(role__can_start_destruction=True)
record_manager2 = UserFactory.create(role__can_start_destruction=True)

review = DestructionListReviewFactory.create(
destruction_list__author=record_manager1,
destruction_list__status=ListStatus.changes_requested,
destruction_list__assignee=record_manager1,
)

endpoint = reverse("api:review-responses-list")
self.client.force_authenticate(user=record_manager2)

response = self.client.post(
endpoint,
data={
"review": review.pk,
"comment": "A comment about the review.",
"itemsResponses": [],
},
format="json",
)

self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEqual(
response.json()["nonFieldErrors"][0],
_(
"This user is either not allowed to update the destruction list or "
"the destruction list cannot currently be updated."
),
)

def test_cannot_create_response_if_not_changes_requested(self):
record_manager = UserFactory.create(role__can_start_destruction=True)

review = DestructionListReviewFactory.create(
destruction_list__author=record_manager,
destruction_list__status=ListStatus.ready_to_review,
destruction_list__assignee=record_manager,
)

endpoint = reverse("api:review-responses-list")
self.client.force_authenticate(user=record_manager)

response = self.client.post(
endpoint,
data={
"review": review.pk,
"comment": "A comment about the review.",
"itemsResponses": [],
},
format="json",
)

self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEqual(
response.json()["nonFieldErrors"][0],
_(
"This user is either not allowed to update the destruction list or "
"the destruction list cannot currently be updated."
),
)

@freezegun.freeze_time("2023-09-15T21:36:00+02:00")
def test_audit_log(self):
# Reassign
record_manager = UserFactory.create(role__can_start_destruction=True)
destruction_list = DestructionListFactory.create(
name="Test audittrail",
status=ListStatus.ready_to_review,
author=record_manager,
)
DestructionListAssigneeFactory.create(destruction_list=destruction_list)
other_reviewer = UserFactory.create(role__can_review_destruction=True)

self.client.force_authenticate(user=record_manager)
endpoint_reassign = reverse(
"api:destructionlist-reassign", kwargs={"uuid": destruction_list.uuid}
)
response = self.client.post(
endpoint_reassign,
data={
"assignee": {"user": other_reviewer.pk},
"comment": "Lorem ipsum...",
},
format="json",
)
self.assertEqual(response.status_code, status.HTTP_200_OK)

endpoint_audittrail = reverse(
"api:destructionlist-auditlog", kwargs={"uuid": destruction_list.uuid}
)
response_audittrail = self.client.get(endpoint_audittrail)

self.assertEqual(response_audittrail.status_code, status.HTTP_200_OK)

data = response_audittrail.data

self.assertEqual(data[0]["user"]["pk"], record_manager.pk)
self.assertEqual(
data[0]["timestamp"],
"2023-09-15T21:36:00+02:00",
)
self.assertEqual(
data[0]["message"],
_('Destruction list "%(list_name)s" was reassigned.')
% {"list_name": "Test audittrail"},
)
self.assertEqual(
data[0]["extra_data"]["assignee"]["user"],
{
"email": other_reviewer.email,
"pk": other_reviewer.pk,
"username": other_reviewer.username,
},
)
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
from django.test import TestCase
from django.utils.translation import gettext_lazy as _

from openarchiefbeheer.accounts.tests.factories import UserFactory

from ...api.serializers import ReviewResponseSerializer
from ...constants import DestructionListItemAction, ListStatus, ZaakActionType
from ..factories import DestructionListItemReviewFactory, DestructionListReviewFactory


class ReviewResponseSerializerTests(TestCase):
def test_rejecting_suggestion_cannot_have_zaak_action(self):
record_manager = UserFactory.create(role__can_start_destruction=True)
review = DestructionListReviewFactory.create(
destruction_list__author=record_manager,
destruction_list__status=ListStatus.changes_requested,
destruction_list__assignee=record_manager,
)
items_review = DestructionListItemReviewFactory.create(
destruction_list_item__destruction_list=review.destruction_list,
review=review,
)

data = {
"review": review.pk,
"comment": "A response about the review.",
"items_responses": [
{
"review_item": items_review.pk,
"action_item": DestructionListItemAction.keep,
"action_zaak_type": ZaakActionType.bewaartermijn,
"action_zaak": {
"archiefactiedatum": "2030-01-01",
},
"comment": "This zaak needs to stay in the list.",
},
],
}

serializer = ReviewResponseSerializer(data=data)

self.assertFalse(serializer.is_valid())
self.assertEqual(
serializer.errors["items_responses"][0]["action_zaak"][0],
_("The case cannot be changed if it is kept in the destruction list."),
)

def test_bewaartermijn_zaak_action_cannot_change_selectielijst(self):
record_manager = UserFactory.create(role__can_start_destruction=True)
review = DestructionListReviewFactory.create(
destruction_list__author=record_manager,
destruction_list__status=ListStatus.changes_requested,
destruction_list__assignee=record_manager,
)
items_review = DestructionListItemReviewFactory.create(
destruction_list_item__destruction_list=review.destruction_list,
review=review,
)

data = {
"review": review.pk,
"comment": "A response about the review.",
"items_responses": [
{
"review_item": items_review.pk,
"action_item": DestructionListItemAction.remove,
"action_zaak_type": ZaakActionType.bewaartermijn,
"action_zaak": {
"selectielijstklasse": "http://some-url.nl",
"archiefactiedatum": "2030-01-01",
},
"comment": "This zaak needs to kept longer.",
},
],
}

serializer = ReviewResponseSerializer(data=data)

self.assertFalse(serializer.is_valid())
self.assertEqual(
serializer.errors["items_responses"][0]["action_zaak_type"][0],
_(
"The selectielijstklasse cannot be changed if the case action type is 'bewaartermijn'."
),
)

def test_selectielijst_zaak_action_requires_selectielijst(self):
record_manager = UserFactory.create(role__can_start_destruction=True)
review = DestructionListReviewFactory.create(
destruction_list__author=record_manager,
destruction_list__status=ListStatus.changes_requested,
destruction_list__assignee=record_manager,
)
items_review = DestructionListItemReviewFactory.create(
destruction_list_item__destruction_list=review.destruction_list,
review=review,
)

data = {
"review": review.pk,
"comment": "A response about the review.",
"items_responses": [
{
"review_item": items_review.pk,
"action_item": DestructionListItemAction.remove,
"action_zaak_type": ZaakActionType.selectielijstklasse_and_bewaartermijn,
"action_zaak": { # No selectielijstklasse
"archiefactiedatum": "2030-01-01",
},
"comment": "This zaak needs to kept longer.",
},
],
}

serializer = ReviewResponseSerializer(data=data)

self.assertFalse(serializer.is_valid())
self.assertEqual(
serializer.errors["items_responses"][0]["selectielijstklasse"][0],
_(
"The selectielijstklasse is required for action type is 'selectielijstklasse_and_bewaartermijn'."
),
)
220 changes: 0 additions & 220 deletions backend/src/openarchiefbeheer/destruction/tests/test_endpoints.py
Original file line number Diff line number Diff line change
@@ -3,7 +3,6 @@
from django.core import mail
from django.utils.translation import gettext_lazy as _

import freezegun
from furl import furl
from rest_framework import status
from rest_framework.reverse import reverse
@@ -16,7 +15,6 @@
from openarchiefbeheer.zaken.tests.factories import ZaakFactory

from ..constants import (
DestructionListItemAction,
InternalStatus,
ListItemStatus,
ListRole,
@@ -28,17 +26,13 @@
DestructionListAssignee,
DestructionListItemReview,
DestructionListReview,
ReviewItemResponse,
ReviewResponse,
)
from .factories import (
DestructionListAssigneeFactory,
DestructionListFactory,
DestructionListItemFactory,
DestructionListItemReviewFactory,
DestructionListReviewFactory,
ReviewItemResponseFactory,
ReviewResponseFactory,
)


@@ -1344,217 +1338,3 @@ def test_with_deleted_zaken(self):
data = response.json()

self.assertIsNone(data[0]["zaak"])


class ReviewResponsesViewSetTests(APITestCase):
def test_no_auth(self):
endpoint = reverse("api:review-responses-list")

response = self.client.get(endpoint)

self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)

def test_filter_on_review(self):
user = UserFactory.create()

review = DestructionListReviewFactory.create(destruction_list__name="List 1")
review_response = ReviewResponseFactory.create(review=review)
ReviewItemResponseFactory.create_batch(2, review_item__review=review)
another_response = ReviewResponseFactory.create()
ReviewItemResponseFactory.create(review_item__review=another_response.review)

endpoint = furl(reverse("api:review-responses-list"))
endpoint.args["review"] = review.pk

self.client.force_authenticate(user=user)
response = self.client.get(endpoint.url)

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

data = response.json()

self.assertEqual(len(data), 1)
self.assertEqual(data[0]["pk"], review_response.pk)
self.assertEqual(len(data[0]["itemsResponses"]), 2)

def test_create_review_response(self):
record_manager = UserFactory.create(role__can_start_destruction=True)
review = DestructionListReviewFactory.create(
destruction_list__author=record_manager,
destruction_list__status=ListStatus.changes_requested,
destruction_list__assignee=record_manager,
)
items_reviews = DestructionListItemReviewFactory.create_batch(
3,
destruction_list_item__destruction_list=review.destruction_list,
review=review,
)

endpoint = reverse("api:review-responses-list")
self.client.force_authenticate(user=record_manager)

response = self.client.post(
endpoint,
data={
"review": review.pk,
"comment": "A comment about the review.",
"itemsResponses": [
{
"reviewItem": items_reviews[0].pk,
"actionItem": DestructionListItemAction.keep,
"comment": "This zaak needs to stay in the list.",
},
{
"reviewItem": items_reviews[1].pk,
"actionItem": DestructionListItemAction.remove,
"actionZaak": {"selectielijstklasse": "http://some-url.nl"},
"comment": "Changed the selectielijstklasse and removed from the list.",
},
{
"reviewItem": items_reviews[2].pk,
"actionItem": DestructionListItemAction.remove,
"actionZaak": {"archiefactiedatum": "2030-01-01"},
"comment": "Changed the archiefactiedatum and removed from the list.",
},
],
},
format="json",
)

self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(ReviewResponse.objects.filter(review=review).count(), 1)
self.assertEqual(
ReviewItemResponse.objects.filter(review_item__review=review).count(), 3
)

item_response1 = ReviewItemResponse.objects.get(review_item=items_reviews[0].pk)

self.assertEqual(item_response1.action_item, DestructionListItemAction.keep)
self.assertEqual(item_response1.comment, "This zaak needs to stay in the list.")

item_response2 = ReviewItemResponse.objects.get(review_item=items_reviews[1].pk)

self.assertEqual(
item_response2.action_zaak["selectielijstklasse"], "http://some-url.nl"
)

item_response3 = ReviewItemResponse.objects.get(review_item=items_reviews[2].pk)

self.assertEqual(item_response3.action_zaak["archiefactiedatum"], "2030-01-01")

def test_cannot_create_response_if_not_author(self):
record_manager1 = UserFactory.create(role__can_start_destruction=True)
record_manager2 = UserFactory.create(role__can_start_destruction=True)

review = DestructionListReviewFactory.create(
destruction_list__author=record_manager1,
destruction_list__status=ListStatus.changes_requested,
destruction_list__assignee=record_manager1,
)

endpoint = reverse("api:review-responses-list")
self.client.force_authenticate(user=record_manager2)

response = self.client.post(
endpoint,
data={
"review": review.pk,
"comment": "A comment about the review.",
"itemsResponses": [],
},
format="json",
)

self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEqual(
response.json()["nonFieldErrors"][0],
_(
"This user is either not allowed to update the destruction list or "
"the destruction list cannot currently be updated."
),
)

def test_cannot_create_response_if_not_changes_requested(self):
record_manager = UserFactory.create(role__can_start_destruction=True)

review = DestructionListReviewFactory.create(
destruction_list__author=record_manager,
destruction_list__status=ListStatus.ready_to_review,
destruction_list__assignee=record_manager,
)

endpoint = reverse("api:review-responses-list")
self.client.force_authenticate(user=record_manager)

response = self.client.post(
endpoint,
data={
"review": review.pk,
"comment": "A comment about the review.",
"itemsResponses": [],
},
format="json",
)

self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEqual(
response.json()["nonFieldErrors"][0],
_(
"This user is either not allowed to update the destruction list or "
"the destruction list cannot currently be updated."
),
)

@freezegun.freeze_time("2023-09-15T21:36:00+02:00")
def test_audit_log(self):
# Reassign
record_manager = UserFactory.create(role__can_start_destruction=True)
destruction_list = DestructionListFactory.create(
name="Test audittrail",
status=ListStatus.ready_to_review,
author=record_manager,
)
DestructionListAssigneeFactory.create(destruction_list=destruction_list)
other_reviewer = UserFactory.create(role__can_review_destruction=True)

self.client.force_authenticate(user=record_manager)
endpoint_reassign = reverse(
"api:destructionlist-reassign", kwargs={"uuid": destruction_list.uuid}
)
response = self.client.post(
endpoint_reassign,
data={
"assignee": {"user": other_reviewer.pk},
"comment": "Lorem ipsum...",
},
format="json",
)
self.assertEqual(response.status_code, status.HTTP_200_OK)

endpoint_audittrail = reverse(
"api:destructionlist-auditlog", kwargs={"uuid": destruction_list.uuid}
)
response_audittrail = self.client.get(endpoint_audittrail)

self.assertEqual(response_audittrail.status_code, status.HTTP_200_OK)

data = response_audittrail.data

self.assertEqual(data[0]["user"]["pk"], record_manager.pk)
self.assertEqual(
data[0]["timestamp"],
"2023-09-15T21:36:00+02:00",
)
self.assertEqual(
data[0]["message"],
_('Destruction list "%(list_name)s" was reassigned.')
% {"list_name": "Test audittrail"},
)
self.assertEqual(
data[0]["extra_data"]["assignee"]["user"],
{
"email": other_reviewer.email,
"pk": other_reviewer.pk,
"username": other_reviewer.username,
},
)
60 changes: 58 additions & 2 deletions backend/src/openarchiefbeheer/destruction/tests/test_tasks.py
Original file line number Diff line number Diff line change
@@ -22,6 +22,7 @@
ListItemStatus,
ListRole,
ListStatus,
ZaakActionType,
)
from ..tasks import (
complete_and_notify,
@@ -64,7 +65,10 @@ def test_client_error_during_zaak_update(self, m):
review_item__destruction_list_item__zaak=zaak,
review_item__review=review_response.review,
action_item=DestructionListItemAction.remove,
action_zaak={"archiefactiedatum": "2026-01-01"},
action_zaak_type=ZaakActionType.bewaartermijn,
action_zaak={
"archiefactiedatum": "2026-01-01",
},
)

m.patch(zaak.url, status_code=400)
@@ -92,7 +96,10 @@ def test_changes_to_both_zaak_and_destruction_list_item(self, m):
review_item__destruction_list_item__zaak=zaak,
review_item__review=review_response.review,
action_item=DestructionListItemAction.remove,
action_zaak={"archiefactiedatum": "2026-01-01"},
action_zaak_type=ZaakActionType.bewaartermijn,
action_zaak={
"archiefactiedatum": "2026-01-01",
},
)
review_response.review.destruction_list.assignees.all().delete()
first_reviwer = DestructionListAssigneeFactory.create(
@@ -141,6 +148,55 @@ def test_changes_to_both_zaak_and_destruction_list_item(self, m):
self.assertEqual(mail.outbox[0].subject, "Destruction list review request")
self.assertEqual(mail.outbox[0].recipients(), ["reviewer1@oab.nl"])

def test_reject_suggestion_does_not_change_zaak(self, m):
review_response = ReviewResponseFactory.create(
review__destruction_list__status=ListStatus.changes_requested,
review__author__email="reviewer1@oab.nl",
)
zaak = ZaakFactory.create(archiefactiedatum="2025-01-01")
review_item_response = ReviewItemResponseFactory.create(
review_item__destruction_list_item__zaak=zaak,
review_item__review=review_response.review,
action_item=DestructionListItemAction.keep,
action_zaak_type=ZaakActionType.bewaartermijn,
action_zaak={
"archiefactiedatum": "2026-01-01",
},
)
review_response.review.destruction_list.assignees.all().delete()
DestructionListAssigneeFactory.create(
user=review_response.review.author,
destruction_list=review_response.review.destruction_list,
role=ListRole.reviewer,
)

with (
patch(
"openarchiefbeheer.destruction.utils.EmailConfig.get_solo",
return_value=EmailConfig(
subject_review_required="Destruction list review request",
body_review_required="Please review the list",
),
),
):
process_review_response(review_response.pk)

review_response.refresh_from_db()
review_item_response.refresh_from_db()
zaak.refresh_from_db()

self.assertEqual(review_response.processing_status, InternalStatus.succeeded)
self.assertEqual(
review_item_response.processing_status, InternalStatus.succeeded
)
self.assertEqual(
review_item_response.review_item.destruction_list_item.status,
ListItemStatus.suggested,
)
self.assertEqual(
zaak.archiefactiedatum.isoformat(), "2025-01-01"
) # NOT changed!!


class ProcessDeletingZakenTests(TestCase):
@log_capture(level=logging.INFO)
7 changes: 4 additions & 3 deletions frontend/src/lib/api/reviewResponse.ts
Original file line number Diff line number Diff line change
@@ -12,15 +12,16 @@ export type ReviewResponse = {
export type ReviewItemResponse = {
reviewItem: ReviewItem["pk"];
actionItem: "keep" | "remove";
actionZaak: ActionZaak;
actionZaakType?: "selectielijstklasse_and_bewaartermijn" | "bewaartermijn";
actionZaak?: ActionZaak;
comment: string;
pk?: number;
created?: string;
};

export type ActionZaak = {
selectielijstklasse: string;
archiefactiedatum: string;
selectielijstklasse?: string;
archiefactiedatum?: string;
};

/**
Original file line number Diff line number Diff line change
@@ -258,11 +258,23 @@ export function DestructionListDetailPage() {
return {
reviewItem: ri.pk,
actionItem: detail.action === "keep" ? "keep" : "remove",
actionZaak: {
selectielijstklasse: detail.selectielijstklasse,
archiefactiedatum: detail.archiefactiedatum,
},
comment: detail.comment,
actionZaakType:
detail.action === "keep"
? undefined
: detail.action === "change_selectielijstklasse"
? "selectielijstklasse_and_bewaartermijn"
: "bewaartermijn",
actionZaak:
detail.action !== "keep"
? {
selectielijstklasse:
detail.action === "change_selectielijstklasse"
? detail.selectielijstklasse
: undefined,
archiefactiedatum: detail.archiefactiedatum,
}
: undefined,
};
}) || [],
},