diff --git a/src/openklant/components/klantinteracties/admin/actoren.py b/src/openklant/components/klantinteracties/admin/actoren.py index d7f0bb5b..15b71506 100644 --- a/src/openklant/components/klantinteracties/admin/actoren.py +++ b/src/openklant/components/klantinteracties/admin/actoren.py @@ -8,7 +8,6 @@ Medewerker, OrganisatorischeEenheid, ) -from .internetaken import InterneTaakInlineAdmin class GeautomatiseerdeActorInlineAdmin(admin.StackedInline): @@ -52,7 +51,6 @@ class ActorAdmin(admin.ModelAdmin): GeautomatiseerdeActorInlineAdmin, MedewerkerInlineAdmin, OrganisatorischeEenheidInlineAdmin, - InterneTaakInlineAdmin, ) fieldsets = ( ( diff --git a/src/openklant/components/klantinteracties/admin/internetaken.py b/src/openklant/components/klantinteracties/admin/internetaken.py index 2cb73322..b0b1ae57 100644 --- a/src/openklant/components/klantinteracties/admin/internetaken.py +++ b/src/openklant/components/klantinteracties/admin/internetaken.py @@ -20,7 +20,10 @@ class InterneTaakAdmin(admin.ModelAdmin): "afgehandeld_op", ) list_filter = ( - "actor", + "actoren", "status", ) - readonly_fields = ("toegewezen_op",) + readonly_fields = ( + "uuid", + "toegewezen_op", + ) diff --git a/src/openklant/components/klantinteracties/api/filterset/internetaken.py b/src/openklant/components/klantinteracties/api/filterset/internetaken.py index 35c07cfd..cb99d757 100644 --- a/src/openklant/components/klantinteracties/api/filterset/internetaken.py +++ b/src/openklant/components/klantinteracties/api/filterset/internetaken.py @@ -10,7 +10,7 @@ class InternetaakFilterSet(FilterSet): toegewezen_aan_actor__uuid = filters.UUIDFilter( help_text=_("Zoek internetaak object op basis van het toegewezen actor uuid."), - field_name="actor__uuid", + field_name="actoren__uuid", ) toegewezen_aan_actor__url = filters.CharFilter( help_text=_("Zoek internetaak object op basis van het toegewezen actor url."), @@ -35,7 +35,7 @@ class Meta: "nummer", "status", "toegewezen_op", - "actor__naam", + "actoren__naam", "klantcontact__uuid", "klantcontact__nummer", "toegewezen_aan_actor__uuid", @@ -47,7 +47,7 @@ class Meta: def filter_toegewezen_aan_actor_url(self, queryset, name, value): try: url_uuid = uuid.UUID(value.rstrip("/").split("/")[-1]) - return queryset.filter(actor__uuid=url_uuid) + return queryset.filter(actoren__uuid=url_uuid) except ValueError: return queryset.none() diff --git a/src/openklant/components/klantinteracties/api/serializers/internetaken.py b/src/openklant/components/klantinteracties/api/serializers/internetaken.py index 4c9d6826..b1319bd0 100644 --- a/src/openklant/components/klantinteracties/api/serializers/internetaken.py +++ b/src/openklant/components/klantinteracties/api/serializers/internetaken.py @@ -1,6 +1,8 @@ from django.db import transaction from django.utils.translation import gettext_lazy as _ +from drf_spectacular.utils import extend_schema_serializer +from glom import PathAccessError, glom from rest_framework import serializers from openklant.components.klantinteracties.api.serializers.actoren import ( @@ -33,12 +35,21 @@ class Meta: } +@extend_schema_serializer(deprecate_fields=["toegewezen_aan_actor"]) class InterneTaakSerializer(serializers.HyperlinkedModelSerializer): toegewezen_aan_actor = ActorForeignKeySerializer( - required=True, + required=False, allow_null=False, - help_text=_("Actor die een interne taak toegewezen kreeg."), - source="actor", + help_text=_("Eerste actor die een interne taak toegewezen kreeg."), + source="actoren.first", + many=False, + ) + toegewezen_aan_actoren = ActorForeignKeySerializer( + required=False, + allow_null=False, + help_text=_("Actoren die een interne taak toegewezen kreeg."), + source="actoren", + many=True, ) aanleidinggevend_klantcontact = KlantcontactForeignKeySerializer( required=True, @@ -56,6 +67,7 @@ class Meta: "gevraagde_handeling", "aanleidinggevend_klantcontact", "toegewezen_aan_actor", + "toegewezen_aan_actoren", "toelichting", "status", "toegewezen_op", @@ -70,7 +82,52 @@ class Meta: }, } + def to_representation(self, instance): + response = super().to_representation(instance) + response["toegewezen_aan_actor"] = ActorForeignKeySerializer( + instance.actoren.order_by("internetakenactorenthoughmodel__pk").first(), + context={**self.context}, + ).data + + response["toegewezen_aan_actoren"] = ActorForeignKeySerializer( + instance.actoren.all().order_by("internetakenactorenthoughmodel__pk"), + context={**self.context}, + many=True, + ).data + return response + + # TODO: remove when depricated actoren field gets removed + def _validate_actoren(self): + toegewezen_aan_actor = "toegewezen_aan_actor" in self.initial_data + toegewezen_aan_actoren = "toegewezen_aan_actoren" in self.initial_data + + if toegewezen_aan_actor == toegewezen_aan_actoren: + if toegewezen_aan_actor: + message = _( + "`toegewezen_aan_actor` en `toegewezen_aan_actoren` mag niet beiden gebruikt worden." + ) + else: + message = _( + "`toegewezen_aan_actor` of `toegewezen_aan_actoren` is required (mag niet beiden gebruiken)." + ) + + # If patch don't raise required error + if self.context.get("request").method == "PATCH": + return + + raise serializers.ValidationError(message) + + # TODO: remove when depricated actoren field gets removed + def _get_actoren(self, actoren): + if isinstance(actoren, list): + actor_uuids = [str(actor.get("uuid")) for actor in actoren] + else: + actor_uuids = [glom(actoren, "first.uuid", skip_exc=PathAccessError)] + + return [Actor.objects.get(uuid=uuid) for uuid in actor_uuids] + def validate(self, attrs): + self._validate_actoren() status = attrs.get("status", None) if status is None and self.instance is not None: status = self.instance.status @@ -89,21 +146,27 @@ def validate(self, attrs): @transaction.atomic def create(self, validated_data): - actor_uuid = str(validated_data.pop("actor").get("uuid")) + actoren = validated_data.pop("actoren", None) klantcontact_uuid = str(validated_data.pop("klantcontact").get("uuid")) - validated_data["actor"] = Actor.objects.get(uuid=actor_uuid) validated_data["klantcontact"] = Klantcontact.objects.get( uuid=klantcontact_uuid ) - return super().create(validated_data) + internetaak = super().create(validated_data) + if actoren: + for actor in self._get_actoren(actoren): + internetaak.actoren.add(actor) + + return internetaak @transaction.atomic def update(self, instance, validated_data): - if "actor" in validated_data: - if actor := validated_data.pop("actor", None): - validated_data["actor"] = Actor.objects.get(uuid=str(actor.get("uuid"))) + if "actoren" in validated_data: + actoren = validated_data.pop("actoren") + instance.actoren.clear() + for actor in self._get_actoren(actoren): + instance.actoren.add(actor) if "klantcontact" in validated_data: if klantcontact := validated_data.pop("klantcontact", None): diff --git a/src/openklant/components/klantinteracties/api/tests/test_filters.py b/src/openklant/components/klantinteracties/api/tests/test_filters.py index 134f0d8d..b61ebf66 100644 --- a/src/openklant/components/klantinteracties/api/tests/test_filters.py +++ b/src/openklant/components/klantinteracties/api/tests/test_filters.py @@ -1071,19 +1071,19 @@ def setUp(self): self.klantcontact5, ) = KlantcontactFactory.create_batch(5) self.internetaak = InterneTaakFactory.create( - actor=self.actor, klantcontact=self.klantcontact + actoren=[self.actor], klantcontact=self.klantcontact ) self.internetaak2 = InterneTaakFactory.create( - actor=self.actor2, klantcontact=self.klantcontact2 + actoren=[self.actor2], klantcontact=self.klantcontact2 ) self.internetaak3 = InterneTaakFactory.create( - actor=self.actor3, klantcontact=self.klantcontact3 + actoren=[self.actor3], klantcontact=self.klantcontact3 ) self.internetaak4 = InterneTaakFactory.create( - actor=self.actor4, klantcontact=self.klantcontact4 + actoren=[self.actor4], klantcontact=self.klantcontact4 ) self.internetaak5 = InterneTaakFactory.create( - actor=self.actor5, klantcontact=self.klantcontact5 + actoren=[self.actor5], klantcontact=self.klantcontact5 ) def test_filter_toegewezen_aan_actor_url(self): diff --git a/src/openklant/components/klantinteracties/api/tests/test_internetaken.py b/src/openklant/components/klantinteracties/api/tests/test_internetaken.py index dd75b36d..e76fac98 100644 --- a/src/openklant/components/klantinteracties/api/tests/test_internetaken.py +++ b/src/openklant/components/klantinteracties/api/tests/test_internetaken.py @@ -60,6 +60,11 @@ def test_create_internetaak(self): response_data = response.json() self.assertEqual(response_data["toegewezenAanActor"]["uuid"], str(actor.uuid)) + self.assertEqual(len(response_data["toegewezenAanActoren"]), 1) + self.assertEqual( + response_data["toegewezenAanActoren"][0]["uuid"], + str(actor.uuid), + ) self.assertEqual( response_data["aanleidinggevendKlantcontact"]["uuid"], str(klantcontact.uuid), @@ -126,6 +131,11 @@ def test_create_internetaak_with_status_te_verwerken(self): response_data = response.json() self.assertEqual(response_data["toegewezenAanActor"]["uuid"], str(actor.uuid)) + self.assertEqual(len(response_data["toegewezenAanActoren"]), 1) + self.assertEqual( + response_data["toegewezenAanActoren"][0]["uuid"], + str(actor.uuid), + ) self.assertEqual( response_data["aanleidinggevendKlantcontact"]["uuid"], str(klantcontact.uuid), @@ -172,6 +182,60 @@ def test_create_internetaak_with_afgehandeld_op_date(self): response_data = response.json() self.assertEqual(response_data["toegewezenAanActor"]["uuid"], str(actor.uuid)) + self.assertEqual(len(response_data["toegewezenAanActoren"]), 1) + self.assertEqual( + response_data["toegewezenAanActoren"][0]["uuid"], + str(actor.uuid), + ) + self.assertEqual( + response_data["toegewezenAanActoren"][0]["uuid"], str(actor.uuid) + ) + self.assertEqual( + response_data["aanleidinggevendKlantcontact"]["uuid"], + str(klantcontact.uuid), + ) + self.assertEqual(response_data["nummer"], "1312312312") + self.assertEqual(response_data["gevraagdeHandeling"], "gevraagdeHandeling") + self.assertEqual(response_data["toelichting"], "toelichting") + self.assertEqual(response_data["status"], "verwerkt") + self.assertTrue( + InterneTaak.objects.filter(afgehandeld_op="2024-01-01T01:00:00Z").exists() + ) + + def test_create_internetaak_with_multiple_actoren(self): + actor, actor2 = ActorFactory.create_batch(2) + klantcontact = KlantcontactFactory.create() + + list_url = reverse("klantinteracties:internetaak-list") + + data = { + "toegewezenAanActoren": [ + {"uuid": str(actor2.uuid)}, + {"uuid": str(actor.uuid)}, + ], + "aanleidinggevendKlantcontact": {"uuid": str(klantcontact.uuid)}, + "nummer": "1312312312", + "gevraagdeHandeling": "gevraagdeHandeling", + "toelichting": "toelichting", + "status": "verwerkt", + "afgehandeldOp": "2024-01-01T01:00:00Z", + } + + response = self.client.post(list_url, data) + + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + + response_data = response.json() + + self.assertEqual(response_data["toegewezenAanActor"]["uuid"], str(actor2.uuid)) + self.assertEqual( + response_data["toegewezenAanActoren"][0]["uuid"], + str(actor2.uuid), + ) + self.assertEqual( + response_data["toegewezenAanActoren"][1]["uuid"], + str(actor.uuid), + ) self.assertEqual( response_data["aanleidinggevendKlantcontact"]["uuid"], str(klantcontact.uuid), @@ -189,7 +253,7 @@ def test_update_internetaak(self): actor, actor2 = ActorFactory.create_batch(2) klantcontact, klantcontact2 = KlantcontactFactory.create_batch(2) internetaak = InterneTaakFactory.create( - actor=actor, + actoren=[actor], klantcontact=klantcontact, nummer="1237713712", gevraagde_handeling="gevraagdeHandeling", @@ -204,6 +268,11 @@ def test_update_internetaak(self): data = response.json() self.assertEqual(data["toegewezenAanActor"]["uuid"], str(actor.uuid)) + self.assertEqual(len(data["toegewezenAanActoren"]), 1) + self.assertEqual( + data["toegewezenAanActoren"][0]["uuid"], + str(actor.uuid), + ) self.assertEqual( data["aanleidinggevendKlantcontact"]["uuid"], str(klantcontact.uuid) ) @@ -228,6 +297,7 @@ def test_update_internetaak(self): data = response.json() self.assertEqual(data["toegewezenAanActor"]["uuid"], str(actor2.uuid)) + self.assertEqual(len(data["toegewezenAanActoren"]), 1) self.assertEqual( data["aanleidinggevendKlantcontact"]["uuid"], str(klantcontact2.uuid) ) @@ -242,6 +312,8 @@ def test_update_internetaak(self): with freeze_time("2024-01-01T12:20:00Z") and self.subTest( "updating_on_later_date_does_not_change_afgehandeld_op" ): + # remove other actoren field + del data["toegewezenAanActoren"] response = self.client.put(detail_url, data) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertFalse( @@ -309,11 +381,139 @@ def test_update_internetaak(self): "De Internetaak kan geen afgehandeld op datum bevatten als de status niet op 'verwerkt' staat.", ) + with self.subTest("validate_acoren_field_required_neither_fields"): + # no toegewezen_aan_actoren and toegewezen_aan_actor + data = { + "aanleidinggevendKlantcontact": {"uuid": str(klantcontact2.uuid)}, + "gevraagdeHandeling": "changed", + "status": "te_verwerken", + "afgehandeldOp": "2024-01-01T01:00:00Z", + } + response = self.client.put(detail_url, data) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + data = response.json() + self.assertEqual(data["invalidParams"][0]["name"], "nonFieldErrors") + self.assertEqual( + data["invalidParams"][0]["reason"], + "`toegewezen_aan_actor` of `toegewezen_aan_actoren` is required (mag niet beiden gebruiken).", + ) + + with self.subTest("validate_acoren_field_required_both_fields"): + # no toegewezen_aan_actoren and toegewezen_aan_actor + data = { + "toegewezenAanActor": {"uuid": str(actor2.uuid)}, + "toegewezenAanActoren": [{"uuid": str(actor2.uuid)}], + "aanleidinggevendKlantcontact": {"uuid": str(klantcontact2.uuid)}, + "gevraagdeHandeling": "changed", + "status": "te_verwerken", + "afgehandeldOp": "2024-01-01T01:00:00Z", + } + response = self.client.put(detail_url, data) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + data = response.json() + self.assertEqual(data["invalidParams"][0]["name"], "nonFieldErrors") + self.assertEqual( + data["invalidParams"][0]["reason"], + "`toegewezen_aan_actor` en `toegewezen_aan_actoren` mag niet beiden gebruikt worden.", + ) + + @freeze_time("2024-01-01T12:00:00Z") + def test_update_internetaak_with_multiple_actoren(self): + actor, actor2, actor3 = ActorFactory.create_batch(3) + klantcontact, klantcontact2 = KlantcontactFactory.create_batch(2) + internetaak = InterneTaakFactory.create( + actoren=[actor], + klantcontact=klantcontact, + nummer="1237713712", + gevraagde_handeling="gevraagdeHandeling", + toelichting="toelichting", + status="te_verwerken", + ) + detail_url = reverse( + "klantinteracties:internetaak-detail", + kwargs={"uuid": str(internetaak.uuid)}, + ) + response = self.client.get(detail_url) + data = response.json() + + self.assertEqual(data["toegewezenAanActor"]["uuid"], str(actor.uuid)) + self.assertEqual(len(data["toegewezenAanActoren"]), 1) + self.assertEqual( + data["toegewezenAanActoren"][0]["uuid"], + str(actor.uuid), + ) + self.assertEqual( + data["aanleidinggevendKlantcontact"]["uuid"], str(klantcontact.uuid) + ) + self.assertEqual(data["nummer"], "1237713712") + self.assertEqual(data["gevraagdeHandeling"], "gevraagdeHandeling") + self.assertEqual(data["toelichting"], "toelichting") + self.assertEqual(data["status"], "te_verwerken") + + data = { + "toegewezenAanActoren": [ + {"uuid": str(actor3.uuid)}, + {"uuid": str(actor2.uuid)}, + ], + "aanleidinggevendKlantcontact": {"uuid": str(klantcontact2.uuid)}, + "nummer": "9999999999", + "gevraagdeHandeling": "changed", + "toelichting": "changed", + "status": "verwerkt", + } + + response = self.client.put(detail_url, data) + self.assertEqual(response.status_code, status.HTTP_200_OK) + response_data = response.json() + + self.assertEqual(response_data["toegewezenAanActor"]["uuid"], str(actor3.uuid)) + self.assertEqual(len(response_data["toegewezenAanActoren"]), 2) + self.assertEqual( + response_data["toegewezenAanActoren"][0]["uuid"], + str(actor3.uuid), + ) + self.assertEqual( + response_data["toegewezenAanActoren"][1]["uuid"], + str(actor2.uuid), + ) + self.assertEqual( + response_data["aanleidinggevendKlantcontact"]["uuid"], + str(klantcontact2.uuid), + ) + self.assertEqual(response_data["nummer"], "9999999999") + self.assertEqual(response_data["gevraagdeHandeling"], "changed") + self.assertEqual(response_data["toelichting"], "changed") + self.assertEqual(response_data["status"], "verwerkt") + self.assertTrue( + InterneTaak.objects.filter(afgehandeld_op="2024-01-01T12:00:00Z").exists() + ) + + with self.subTest( + "update_toegewezen_aan_actor_resoltes_in_one_actor_being_set" + ): + # no toegewezen_aan_actoren and toegewezen_aan_actor + del data["toegewezenAanActoren"] + data = { + "toegewezenAanActor": {"uuid": str(actor.uuid)}, + } + response = self.client.patch(detail_url, data) + self.assertEqual(response.status_code, status.HTTP_200_OK) + response_data = response.json() + + self.assertEqual( + response_data["toegewezenAanActor"]["uuid"], str(actor.uuid) + ) + self.assertEqual(len(response_data["toegewezenAanActoren"]), 1) + self.assertEqual( + response_data["toegewezenAanActoren"][0]["uuid"], + str(actor.uuid), + ) + def test_partial_update_internetaak(self): actor = ActorFactory.create() klantcontact = KlantcontactFactory.create() internetaak = InterneTaakFactory.create( - actor=actor, + actoren=[actor], klantcontact=klantcontact, nummer="1237713712", gevraagde_handeling="gevraagdeHandeling", @@ -353,6 +553,14 @@ def test_partial_update_internetaak(self): self.assertEqual(data["toelichting"], "toelichting") self.assertEqual(data["status"], "verwerkt") + with self.subTest("disable_actoren_validation"): + # no toegewezen_aan_actoren and toegewezen_aan_actor + data = { + "gevraagdeHandeling": "changed", + } + response = self.client.patch(detail_url, data) + self.assertEqual(response.status_code, status.HTTP_200_OK) + def test_destroy_internetaak(self): internetaak = InterneTaakFactory.create() detail_url = reverse( diff --git a/src/openklant/components/klantinteracties/api/viewsets/internetaken.py b/src/openklant/components/klantinteracties/api/viewsets/internetaken.py index b6c778ab..68bda792 100644 --- a/src/openklant/components/klantinteracties/api/viewsets/internetaken.py +++ b/src/openklant/components/klantinteracties/api/viewsets/internetaken.py @@ -43,9 +43,10 @@ class InterneTaakViewSet(viewsets.ModelViewSet): """Iets dat door een actor moet worden gedaan om opvolging te geven aan een klantcontact.""" - queryset = InterneTaak.objects.order_by("-pk").select_related( - "actor", - "klantcontact", + queryset = ( + InterneTaak.objects.order_by("-pk") + .prefetch_related("actoren") + .select_related("klantcontact") ) serializer_class = InterneTaakSerializer lookup_field = "uuid" diff --git a/src/openklant/components/klantinteracties/migrations/0016_internetaak_actoren.py b/src/openklant/components/klantinteracties/migrations/0016_internetaak_actoren.py new file mode 100644 index 00000000..2ca5725d --- /dev/null +++ b/src/openklant/components/klantinteracties/migrations/0016_internetaak_actoren.py @@ -0,0 +1,52 @@ +# Generated by Django 4.2.11 on 2024-08-08 10:38 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + dependencies = [ + ("klantinteracties", "0015_internetaak_afgehandeld_op"), + ] + + operations = [ + migrations.CreateModel( + name="InterneTakenActorenThoughModel", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "actor", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="klantinteracties.actor", + ), + ), + ( + "internetaak", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="klantinteracties.internetaak", + ), + ), + ], + options={"ordering": ("pk",)}, + ), + migrations.AddField( + model_name="internetaak", + name="actoren", + field=models.ManyToManyField( + help_text="De actoren aan wie de interne taak werd toegewezen.", + through="klantinteracties.InterneTakenActorenThoughModel", + to="klantinteracties.actor", + verbose_name="actoren", + ), + ), + ] diff --git a/src/openklant/components/klantinteracties/migrations/0017_auto_20240815_2234.py b/src/openklant/components/klantinteracties/migrations/0017_auto_20240815_2234.py new file mode 100644 index 00000000..d8773020 --- /dev/null +++ b/src/openklant/components/klantinteracties/migrations/0017_auto_20240815_2234.py @@ -0,0 +1,26 @@ +# Generated by Django 4.2.11 on 2024-08-15 22:34 + +from django.db import migrations + + +class Migration(migrations.Migration): + def sync_actor_and_actoren(apps, schema_editor): + Internetaak = apps.get_model("klantinteracties", "internetaak") + for internetaak in Internetaak.objects.all(): + internetaak.actoren.add(internetaak.actor) + + def reverse_sync_actor_and_actoren(apps, schema_editor): + Internetaak = apps.get_model("klantinteracties", "internetaak") + for internetaak in Internetaak.objects.all(): + internetaak.actor = internetaak.actoren.first() + internetaak.save() + + dependencies = [ + ("klantinteracties", "0016_internetaak_actoren"), + ] + + operations = [ + migrations.RunPython( + sync_actor_and_actoren, reverse_code=reverse_sync_actor_and_actoren + ), + ] diff --git a/src/openklant/components/klantinteracties/migrations/0018_remove_internetaak_actor.py b/src/openklant/components/klantinteracties/migrations/0018_remove_internetaak_actor.py new file mode 100644 index 00000000..3ca4ff3f --- /dev/null +++ b/src/openklant/components/klantinteracties/migrations/0018_remove_internetaak_actor.py @@ -0,0 +1,29 @@ +# Generated by Django 4.2.11 on 2024-08-15 22:35 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ("klantinteracties", "0017_auto_20240815_2234"), + ] + + operations = [ + migrations.AlterField( + model_name="internetaak", + name="actor", + field=models.ForeignKey( + help_text="De actor aan wie de interne taak werd toegewezen.", + on_delete=django.db.models.deletion.CASCADE, + to="klantinteracties.actor", + verbose_name="actor", + default=1, + ), + ), + migrations.RemoveField( + model_name="internetaak", + name="actor", + ), + ] diff --git a/src/openklant/components/klantinteracties/migrations/0016_alter_partij_indicatie_geheimhouding.py b/src/openklant/components/klantinteracties/migrations/0019_alter_partij_indicatie_geheimhouding.py similarity index 91% rename from src/openklant/components/klantinteracties/migrations/0016_alter_partij_indicatie_geheimhouding.py rename to src/openklant/components/klantinteracties/migrations/0019_alter_partij_indicatie_geheimhouding.py index 78023860..e872952c 100644 --- a/src/openklant/components/klantinteracties/migrations/0016_alter_partij_indicatie_geheimhouding.py +++ b/src/openklant/components/klantinteracties/migrations/0019_alter_partij_indicatie_geheimhouding.py @@ -6,7 +6,7 @@ class Migration(migrations.Migration): dependencies = [ - ("klantinteracties", "0015_internetaak_afgehandeld_op"), + ("klantinteracties", "0018_remove_internetaak_actor"), ] operations = [ diff --git a/src/openklant/components/klantinteracties/models/internetaken.py b/src/openklant/components/klantinteracties/models/internetaken.py index 1f216f86..c6bb5dbf 100644 --- a/src/openklant/components/klantinteracties/models/internetaken.py +++ b/src/openklant/components/klantinteracties/models/internetaken.py @@ -7,7 +7,6 @@ from openklant.components.utils.number_generator import number_generator -from .actoren import Actor from .constants import Taakstatus from .klantcontacten import Klantcontact @@ -18,11 +17,11 @@ class InterneTaak(models.Model): default=uuid.uuid4, help_text=_("Unieke (technische) identificatiecode van de interne taak."), ) - actor = models.ForeignKey( - Actor, - on_delete=models.CASCADE, - verbose_name=_("actor"), - help_text=_("De actor aan wie de interne taak werd toegewezen."), + actoren = models.ManyToManyField( + "klantinteracties.Actor", + verbose_name=_("actoren"), + help_text=_("De actoren aan wie de interne taak werd toegewezen."), + through="klantinteracties.InterneTakenActorenThoughModel", ) klantcontact = models.ForeignKey( Klantcontact, @@ -98,3 +97,13 @@ def save(self, *args, **kwargs): def __str__(self): return f"{self.klantcontact} - ({self.nummer})" + + +class InterneTakenActorenThoughModel(models.Model): + actor = models.ForeignKey("klantinteracties.Actor", on_delete=models.CASCADE) + internetaak = models.ForeignKey( + "klantinteracties.InterneTaak", on_delete=models.CASCADE + ) + + class Meta: + ordering = ("pk",) diff --git a/src/openklant/components/klantinteracties/models/tests/factories/internetaken.py b/src/openklant/components/klantinteracties/models/tests/factories/internetaken.py index 46618b30..3c984d0e 100644 --- a/src/openklant/components/klantinteracties/models/tests/factories/internetaken.py +++ b/src/openklant/components/klantinteracties/models/tests/factories/internetaken.py @@ -2,9 +2,6 @@ from openklant.components.klantinteracties.models.constants import Taakstatus from openklant.components.klantinteracties.models.internetaken import InterneTaak -from openklant.components.klantinteracties.models.tests.factories.actoren import ( - ActorFactory, -) from openklant.components.klantinteracties.models.tests.factories.klantcontacten import ( KlantcontactFactory, ) @@ -12,7 +9,6 @@ class InterneTaakFactory(factory.django.DjangoModelFactory): uuid = factory.Faker("uuid4") - actor = factory.SubFactory(ActorFactory) klantcontact = factory.SubFactory(KlantcontactFactory) nummer = factory.Sequence(lambda n: str(n)) gevraagde_handeling = factory.Faker("word") @@ -22,3 +18,12 @@ class InterneTaakFactory(factory.django.DjangoModelFactory): class Meta: model = InterneTaak + + @factory.post_generation + def actoren(self, create, extracted, **kwargs): + if not create: + return + + if extracted: + for actor in extracted: + self.actoren.add(actor) diff --git a/src/openklant/components/klantinteracties/openapi.yaml b/src/openklant/components/klantinteracties/openapi.yaml index 05f1053a..7c9a53a7 100644 --- a/src/openklant/components/klantinteracties/openapi.yaml +++ b/src/openklant/components/klantinteracties/openapi.yaml @@ -1319,7 +1319,7 @@ paths: description: Zoek internetaak object op basis van het aanleidingevende klantcontact uuid. - in: query - name: actor__naam + name: actoren__naam schema: type: string - in: query @@ -3413,7 +3413,13 @@ components: toegewezenAanActor: allOf: - $ref: '#/components/schemas/ActorForeignKey' - description: Actor die een interne taak toegewezen kreeg. + description: Eerste actor die een interne taak toegewezen kreeg. + deprecated: true + toegewezenAanActoren: + type: array + items: + $ref: '#/components/schemas/ActorForeignKey' + description: Actoren die een interne taak toegewezen kreeg. toelichting: type: string description: Toelichting die, aanvullend bij de inhoud van het klantcontact @@ -3441,7 +3447,6 @@ components: - aanleidinggevendKlantcontact - gevraagdeHandeling - status - - toegewezenAanActor - toegewezenOp - url - uuid @@ -4658,7 +4663,13 @@ components: toegewezenAanActor: allOf: - $ref: '#/components/schemas/ActorForeignKey' - description: Actor die een interne taak toegewezen kreeg. + description: Eerste actor die een interne taak toegewezen kreeg. + deprecated: true + toegewezenAanActoren: + type: array + items: + $ref: '#/components/schemas/ActorForeignKey' + description: Actoren die een interne taak toegewezen kreeg. toelichting: type: string description: Toelichting die, aanvullend bij de inhoud van het klantcontact diff --git a/src/openklant/fixtures/klantinteracties.json b/src/openklant/fixtures/klantinteracties.json index 020d5799..9decafbd 100644 --- a/src/openklant/fixtures/klantinteracties.json +++ b/src/openklant/fixtures/klantinteracties.json @@ -363,7 +363,7 @@ "pk": 1, "fields": { "uuid": "58d26043-0cdc-4a46-9110-6acca6e200f2", - "actor": 1, + "actoren": [1], "klantcontact": 1, "nummer": "0000000001", "gevraagde_handeling": "Doorzetten naar juiste afdeling en terubellend.", @@ -377,7 +377,7 @@ "pk": 2, "fields": { "uuid": "2ef831a3-9c4a-42d2-9b55-fc885e9292dc", - "actor": 2, + "actoren": [2], "klantcontact": 2, "nummer": "0000000002", "gevraagde_handeling": "Terugbellen",