From cb7a966ab7e18fe9b07ae57e9e4a3e8e197ff25f Mon Sep 17 00:00:00 2001 From: Nino Date: Thu, 30 Jan 2025 12:39:19 +0100 Subject: [PATCH 1/6] add reviewer to bpmn --- .../process_vve_ok/1.9.0/process_vve_ok.bpmn | 1029 +++++++++++++++++ .../migrations/0008_caseusertask_author.py | 26 + app/apps/workflow/models.py | 19 +- app/apps/workflow/serializers.py | 6 + app/apps/workflow/views.py | 2 +- app/config/settings.py | 1 + 6 files changed, 1081 insertions(+), 2 deletions(-) create mode 100644 app/apps/workflow/bpmn_files/default/process_vve_ok/1.9.0/process_vve_ok.bpmn create mode 100644 app/apps/workflow/migrations/0008_caseusertask_author.py diff --git a/app/apps/workflow/bpmn_files/default/process_vve_ok/1.9.0/process_vve_ok.bpmn b/app/apps/workflow/bpmn_files/default/process_vve_ok/1.9.0/process_vve_ok.bpmn new file mode 100644 index 0000000..a11ad27 --- /dev/null +++ b/app/apps/workflow/bpmn_files/default/process_vve_ok/1.9.0/process_vve_ok.bpmn @@ -0,0 +1,1029 @@ + + + + + + + + + Gateway_1tmui51 + task_ingediend_vve + task_vve_prio_buurt + Gateway_1ym227t + Gateway_19yon7e + task_upload_aanvraag_formulier + Gateway_194que8 + task_upload_grootaandeelhouder_formulier + Gateway_1pdq2pf + task_mail_gebruiker_controle_informatie + Gateway_16fvnma + Activity_0xumr2i + task_controle_informatie + task_versturen_adviseur + Gateway_0lck87v + task_informeer_projectleider_adviseur + Gateway_1a71jts + task_relevante_info_pj + Gateway_12v4ugz + Gateway_0dwvorx + task_bestuur_geinformeerd + Gateway_07u1q44 + Event_01ko21h + task_informeer_aanvrager + Gateway_1qlbwp9 + Gateway_0y8v8h9 + Gateway_0dkaqv2 + task_eerder_ingediend + Activity_1w1swgw + task_email_vve + task_verwerken_intake_formulier + Activity_0nh6ajz + Gateway_1c85mt4 + Event_1tc1avn + task_klanttevredenheidsonderzoek + Gateway_17bw8ea + Activity_16jdr6g + Gateway_0p6zwwq + Gateway_0du1x64 + task_controle_gebouw + Gateway_0zq4w38 + task_controle_bouwjaar + Gateway_1ke9dsy + task_aanvragen_informatie_bestuur + Gateway_13kgcno + task_monitoren_extra_informatie + task_monitor_mail_bestuur + Event_049hit6 + Activity_1so3vlp + task_prestatieverklaring + Event_09lu92e + task_afwijzing_sturen + task_controle_afwijzing + timer_extra_info + timer_bestuur + + + + Flow_0tsiwpv + Flow_08urjmx + Flow_0hd6fjl + + + + + + + + + + + + + + + + Flow_1gby6d6 + Flow_0tsiwpv + + + + + + + + + + + Flow_08urjmx + Flow_0ab55hc + + + Flow_0ab55hc + Flow_12k1d1t + Flow_0jf4ejy + Flow_1w5rash + + + Flow_0szubqz + Flow_12k1d1t + Flow_0hd6fjl + Flow_1qt3h6y + + + + + + + + Flow_00876s7 + Flow_0ajxfee + + + Flow_0ajxfee + Flow_02yy2x9 + Flow_0isu3fo + + + + + + + + Flow_02yy2x9 + Flow_1rveqob + + + Flow_0isu3fo + Flow_1rveqob + Flow_086fr37 + + + Flow_0u8z0vo + Flow_1y54pnc + + + Flow_0ee0ab2 + Flow_0u8z0vo + Flow_0js6nja + + + Flow_086fr37 + Flow_09objfh + set_status("Volledigheid") + + + + + + + + + + + + + + + + + + + + + Flow_086fr37 + Flow_0ee0ab2 + + + Flow_12r31n3 + Flow_11ing8x + + + Flow_152efhd + Flow_1mil6xn + Flow_0mo2q8p + + + Flow_0km6gii + Flow_152efhd + + + Flow_1dk88vx + Flow_0km6gii + Flow_1mil6xn + + + + + + + + + + + + + + + + Flow_1rg4agt + Flow_1dk88vx + + + Flow_0u9qye8 + Flow_160da0w + Flow_1gpnf5k + + + Flow_1gpnf5k + Flow_1xurlff + Flow_1rg4agt + + + + + + + + + + + Flow_13qqg46 + Flow_0u9qye8 + + + Flow_10pzthf + Flow_0jf4ejy + Flow_13qqg46 + + + Flow_0rcrplq + + + Flow_18jp7ne + Flow_0rcrplq + + + Flow_1va2jdn + Flow_0xl61sw + Flow_1paiaux + + + Flow_0js6nja + Flow_00ula1u + Flow_1dfae84 + + + Flow_06ysp0h + Flow_1gby6d6 + Flow_0szubqz + + + + + + + + + + + + + + + + Flow_1v8pztr + Flow_06ysp0h + + + Flow_1dfae84 + Flow_1v8pztr + set_status("Beoordelen") + + + Flow_0mo2q8p + Flow_1i8l9e8 + + + Flow_1i8l9e8 + Flow_08ufbro + + + Flow_08ufbro + Flow_12r31n3 + set_status("Toegekend aan adviseur") + + + Flow_0g1fd8z + Flow_0sz4gcs + Flow_1t2na2c + + + Flow_1dga19z + + + Flow_0sz4gcs + Flow_0sjd48h + + + Flow_0mcs4a3 + Flow_0sjd48h + Flow_1dga19z + + + Flow_11ing8x + Flow_0g1fd8z + set_status("Evaluatie ") + + + Flow_1cr0yoe + Flow_0ul0oq9 + Flow_10pzthf + + + Flow_0xl61sw + Flow_0ul0oq9 + Flow_18jp7ne + + + + + + + + + + + + Flow_0uckmdm + Flow_1cr0yoe + + + Flow_0p9p84s + Flow_1paiaux + Flow_0uckmdm + + + + + + + + + + + Flow_05g4jsk + Flow_1va2jdn + + + Flow_1w5rash + Flow_0p9p84s + Flow_05g4jsk + + + Flow_160da0w + Flow_08fmt8b + + + Flow_1qt3h6y + Flow_0dk0tas + Flow_00f0qoe + Flow_0sp23oy + + + + + + + + Flow_1y54pnc + Flow_00ula1u + + + Flow_08fmt8b + Flow_1xurlff + + + Flow_1etqrm3 + + + Flow_1etqrm3 + Flow_00876s7 + set_status("Aanvraag invoeren") + + + Flow_1t2na2c + Flow_0mcs4a3 + + + Flow_1p1i2ve + + + + Flow_01qyetz + Flow_1p1i2ve + + + + + + + + Flow_0sp23oy + Flow_01qyetz + + + Flow_0dk0tas + + parse_duration("P14D") + + + + Flow_00f0qoe + + parse_duration("P14D") + + + + + form_aanvraag_via_vve.get("value") == "no" + + + + + + form_vve_prio_buurt.get("value") == "no" + + + + hoa_is_small.get("value") == True + + + form_aanvraag_ingediend.get("value") == "yes" + + + + + + + has_major_shareholder.get("value") == True + + + + + form_controle_informatie.get("value") == "no" or form_controle_informatie_formulier.get("value") == "no" + + + + + + + + + + + + form_relevante_info_pj.get("value") == "yes" + + + + + + + form_bestuur_geinformeerd.get("value") == "no" + + + + + + + + + + form_controle_bouwjaar.get("value") == "no" + + + + + + + + + + + + + + + + form_controle_gebouw.get("value") == "yes" + + + + + (build_year.get("value") >= 1995 and advice_type.get("value") == "Haalbaarheidsonderzoek") or (build_year.get("value") < 1995 and advice_type.get("value") == "Energieadvies") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/apps/workflow/migrations/0008_caseusertask_author.py b/app/apps/workflow/migrations/0008_caseusertask_author.py new file mode 100644 index 0000000..1abc06b --- /dev/null +++ b/app/apps/workflow/migrations/0008_caseusertask_author.py @@ -0,0 +1,26 @@ +# Generated by Django 5.0.8 on 2025-01-30 09:17 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("workflow", "0007_workflowoption"), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.AddField( + model_name="caseusertask", + name="requires_review", + field=models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.PROTECT, + related_name="case_user_tasks", + to=settings.AUTH_USER_MODEL, + ), + ), + ] diff --git a/app/apps/workflow/models.py b/app/apps/workflow/models.py index 95df212..86f513b 100644 --- a/app/apps/workflow/models.py +++ b/app/apps/workflow/models.py @@ -2,6 +2,7 @@ import os +from apps.users import auth from apps.events.models import CaseEvent, TaskModelEventEmitter from apps.cases.models import Case, CaseStateType from django.conf import settings @@ -199,6 +200,13 @@ def _create_user_tasks(self, wf): due_date=make_aware(datetime.datetime.today()), case=self.case, workflow=self, + initiated_by=( + auth.get_user_model().objects.get(id=self.data.get("author")) + if self.data.get("author") + else None + ), + requires_review=task.task_spec.extensions.get("requires_review", False) + or False, ) for task in ready_tasks if not CaseUserTask.objects.filter( @@ -392,8 +400,17 @@ class CaseUserTask(models.Model): related_name="tasks", on_delete=models.CASCADE, ) - + initiated_by = models.ForeignKey( + to=settings.AUTH_USER_MODEL, + related_name="case_user_tasks", + on_delete=models.PROTECT, + null=True, + ) objects = BulkCreateSignalsManager() + requires_review = models.BooleanField( + default=False, + help_text="Indicates whether this task requires review by another user.", + ) @property def get_form_variables(self): diff --git a/app/apps/workflow/serializers.py b/app/apps/workflow/serializers.py index 41cde37..e0d4d35 100644 --- a/app/apps/workflow/serializers.py +++ b/app/apps/workflow/serializers.py @@ -11,6 +11,10 @@ class CaseUserTaskSerializer(serializers.ModelSerializer): case = serializers.PrimaryKeyRelatedField(queryset=Case.objects.all()) homeowner_association = serializers.SerializerMethodField() + initiated_by = serializers.SerializerMethodField() + + def get_initiated_by(self, obj): + return obj.initiated_by.email if obj.initiated_by else None def get_homeowner_association(self, obj): return ( @@ -35,6 +39,8 @@ class Meta: "completed", "case", "homeowner_association", + "initiated_by", + "requires_review", ) diff --git a/app/apps/workflow/views.py b/app/apps/workflow/views.py index 841dedf..6c407eb 100644 --- a/app/apps/workflow/views.py +++ b/app/apps/workflow/views.py @@ -96,7 +96,7 @@ def _complete_task_common(self, serializer, save_document=False): "value": serializer.validated_data.get("name"), } serializer.save() - + variables["author"] = author.id task_data = { "case_user_task_id": case_user_task_id, "description": task.name, diff --git a/app/config/settings.py b/app/config/settings.py index 572f732..2b7730a 100644 --- a/app/config/settings.py +++ b/app/config/settings.py @@ -249,6 +249,7 @@ def get_redis_url(): "1.6.0": {}, "1.7.0": {}, "1.8.0": {}, + "1.9.0": {}, }, }, "sub_workflow": { From b56db923864ea4da485dc5bc00c42c0c718c956c Mon Sep 17 00:00:00 2001 From: Nino Date: Thu, 30 Jan 2025 12:50:38 +0100 Subject: [PATCH 2/6] fix migration --- .../workflow/migrations/0008_caseusertask_author.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/app/apps/workflow/migrations/0008_caseusertask_author.py b/app/apps/workflow/migrations/0008_caseusertask_author.py index 1abc06b..1c9d870 100644 --- a/app/apps/workflow/migrations/0008_caseusertask_author.py +++ b/app/apps/workflow/migrations/0008_caseusertask_author.py @@ -15,7 +15,7 @@ class Migration(migrations.Migration): operations = [ migrations.AddField( model_name="caseusertask", - name="requires_review", + name="initiated_by", field=models.ForeignKey( null=True, on_delete=django.db.models.deletion.PROTECT, @@ -23,4 +23,12 @@ class Migration(migrations.Migration): to=settings.AUTH_USER_MODEL, ), ), + migrations.AddField( + model_name="caseusertask", + name="requires_review", + field=models.BooleanField( + default=False, + help_text="Indicates whether this task requires review by another user.", + ), + ), ] From 3f614b95850959de1cc9a008b268c55643dfe57c Mon Sep 17 00:00:00 2001 From: Remy van der Wereld Date: Fri, 31 Jan 2025 16:21:33 +0100 Subject: [PATCH 3/6] Updated labels --- .../default/process_vve_ok/1.9.0/process_vve_ok.bpmn | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/apps/workflow/bpmn_files/default/process_vve_ok/1.9.0/process_vve_ok.bpmn b/app/apps/workflow/bpmn_files/default/process_vve_ok/1.9.0/process_vve_ok.bpmn index a11ad27..e46055f 100644 --- a/app/apps/workflow/bpmn_files/default/process_vve_ok/1.9.0/process_vve_ok.bpmn +++ b/app/apps/workflow/bpmn_files/default/process_vve_ok/1.9.0/process_vve_ok.bpmn @@ -260,11 +260,11 @@ - + - + From ebc4a54ad410fe9000b761679654f5412ef324e3 Mon Sep 17 00:00:00 2001 From: Nino Date: Fri, 31 Jan 2025 16:27:41 +0100 Subject: [PATCH 4/6] fix missing initiated_by in create case and sub wf --- app/apps/cases/views.py | 9 ++++++--- app/apps/workflow/models.py | 4 ++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/app/apps/cases/views.py b/app/apps/cases/views.py index 98a8094..6533bca 100644 --- a/app/apps/cases/views.py +++ b/app/apps/cases/views.py @@ -63,11 +63,13 @@ def create(self, request): contacts_data = validated_data.pop("contacts", []) case = Case.objects.create(**validated_data) Contact.process_contacts(case, contacts_data) - self.start_workflow(case) + self.start_workflow(case, request.user.id) return Response(CaseSerializer(case).data, status=201) - def start_workflow(self, case): - task = task_create_main_worflow_for_case.delay(case_id=case.id) + def start_workflow(self, case, user_id): + task = task_create_main_worflow_for_case.delay( + case_id=case.id, data={"initiated_by": user_id} + ) task.wait(timeout=None, interval=0.5) start_workflow_task = task_start_worflow.delay( CaseWorkflow.objects.get(case=case).id @@ -136,6 +138,7 @@ def start_process(self, request, pk): case=case, workflow_type=workflow_type, workflow_message_name=instance.message_name, + data={"initiated_by": request.user.id}, ) task = task_start_worflow.delay(case_workflow.id) task.wait(timeout=None, interval=0.5) diff --git a/app/apps/workflow/models.py b/app/apps/workflow/models.py index 86f513b..cdb63e5 100644 --- a/app/apps/workflow/models.py +++ b/app/apps/workflow/models.py @@ -201,8 +201,8 @@ def _create_user_tasks(self, wf): case=self.case, workflow=self, initiated_by=( - auth.get_user_model().objects.get(id=self.data.get("author")) - if self.data.get("author") + auth.get_user_model().objects.get(id=self.data.get("initiated_by")) + if self.data.get("initiated_by") else None ), requires_review=task.task_spec.extensions.get("requires_review", False) From 324340312d4cc2e98ae1587aefa13d5e687c93e9 Mon Sep 17 00:00:00 2001 From: Nino Date: Fri, 31 Jan 2025 16:32:25 +0100 Subject: [PATCH 5/6] missing one --- app/apps/workflow/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/apps/workflow/views.py b/app/apps/workflow/views.py index 6c407eb..c2d262a 100644 --- a/app/apps/workflow/views.py +++ b/app/apps/workflow/views.py @@ -96,7 +96,7 @@ def _complete_task_common(self, serializer, save_document=False): "value": serializer.validated_data.get("name"), } serializer.save() - variables["author"] = author.id + variables["initiated_by"] = author.id task_data = { "case_user_task_id": case_user_task_id, "description": task.name, From 4cd8150f8382a1dca007edf0c46fafae5b6148f7 Mon Sep 17 00:00:00 2001 From: Nino Date: Mon, 3 Feb 2025 10:19:22 +0100 Subject: [PATCH 6/6] add verification for review tasks --- app/apps/workflow/tests/tests_api.py | 42 ++++++++++++++++++++++++++-- app/apps/workflow/views.py | 7 +++++ 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/app/apps/workflow/tests/tests_api.py b/app/apps/workflow/tests/tests_api.py index 01e7849..e141e65 100644 --- a/app/apps/workflow/tests/tests_api.py +++ b/app/apps/workflow/tests/tests_api.py @@ -6,7 +6,11 @@ from apps.homeownerassociation.models import HomeownerAssociation from apps.cases.models import Case, CaseDocument from apps.workflow.models import CaseUserTask, CaseWorkflow, GenericCompletedTask -from utils.test_utils import get_authenticated_client, get_unauthenticated_client +from utils.test_utils import ( + get_authenticated_client, + get_test_user, + get_unauthenticated_client, +) import uuid from django.core.files.uploadedfile import SimpleUploadedFile @@ -85,7 +89,7 @@ def test_complete_task(self, complete_generic_user_task_and_create_new_user_task ) def test_get_case_user_tasks(self): - case, case_user_task = self._create_case_and_task() + _, _ = self._create_case_and_task() url = reverse("tasks-list") response = self.client.get(url) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -108,6 +112,38 @@ def test_complete_task_invalid_id(self): response = self.client.post(url, data, format="json") self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + @patch("apps.workflow.views.complete_generic_user_task_and_create_new_user_tasks") + def test_complete_task_with_requires_review( + self, complete_generic_user_task_and_create_new_user_tasks + ): + complete_generic_user_task_and_create_new_user_tasks.return_value = ( + "task completed" + ) + case = self._create_case() + case = Case.objects.get(id=case) + case_wf = CaseWorkflow.objects.create( + case=case, completed=False, workflow_type="process_vve_ok" + ) + user = get_test_user() + case_user_task = CaseUserTask.objects.create( + task_name="task1", + completed=False, + case=case, + task_id=uuid.uuid4(), + due_date="2021-01-01", + workflow_id=case_wf.id, + initiated_by=user, + requires_review=True, + ) + url = reverse("generictasks-complete-task") + data = { + "case_user_task_id": case_user_task.id, + "case": case.id, + "variables": {"test": "test"}, + } + response = self.client.post(url, data, format="json") + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + def test_complete_file_task_missing_file(self): case, case_user_task = self._create_case_and_task() url = reverse("generictasks-complete-file-task") @@ -132,6 +168,8 @@ def _create_case_and_task(self): task_id=uuid.uuid4(), due_date="2021-01-01", workflow_id=case_wf.id, + initiated_by=get_test_user(), + requires_review=False, ) return case, case_user_task diff --git a/app/apps/workflow/views.py b/app/apps/workflow/views.py index c2d262a..b933c26 100644 --- a/app/apps/workflow/views.py +++ b/app/apps/workflow/views.py @@ -77,6 +77,13 @@ def _complete_task_common(self, serializer, save_document=False): author = data.pop("author") variables = data.get("variables", {}) task = CaseUserTask.objects.get(id=case_user_task_id, completed=False) + + if task.requires_review and author.id == task.initiated_by.id: + return Response( + {"detail": "You are not authorized to complete this task"}, + status=status.HTTP_403_FORBIDDEN, + ) + from apps.workflow.user_tasks import get_task_by_name user_task_type = get_task_by_name(task.task_name)