From 809efb775b24ed7908d0c11d60abd7a05c72d0f0 Mon Sep 17 00:00:00 2001 From: Kelvin Chan Date: Tue, 13 Feb 2024 14:12:11 +0100 Subject: [PATCH 1/7] 2320 - kownsl FE fix --- .../kownsl/src/lib/advice/advice.component.html | 10 +++++----- .../kownsl/src/lib/advice/advice.component.ts | 13 ++++++------- .../kownsl/src/lib/advice/advice.service.ts | 4 ++-- .../kownsl/src/lib/approval/approval.component.html | 12 ++++++------ .../kownsl/src/lib/approval/approval.component.ts | 6 +++--- .../kownsl/src/lib/approval/approval.service.ts | 4 ++-- .../libs/features/kownsl/src/models/advice-form.ts | 2 +- .../features/kownsl/src/models/review-request.ts | 9 +++++---- 8 files changed, 30 insertions(+), 30 deletions(-) diff --git a/frontend/zac-ui/libs/features/kownsl/src/lib/advice/advice.component.html b/frontend/zac-ui/libs/features/kownsl/src/lib/advice/advice.component.html index 00b92ac09..22a1ccc52 100644 --- a/frontend/zac-ui/libs/features/kownsl/src/lib/advice/advice.component.html +++ b/frontend/zac-ui/libs/features/kownsl/src/lib/advice/advice.component.html @@ -79,10 +79,10 @@

{{zaakData.zaaktype.omschrijving}}

- -
+ +
-

Vorige adviezen

+

Vorige adviezen

@@ -95,7 +95,7 @@

Vorige adviezen

-

Documenten

+

Documenten

@@ -105,7 +105,7 @@

Documenten

- (maximaal 1000 karakters) + (maximaal 1000 karakters) { - const docName = `${document.name} (${document.title})`; + const docName = document.bestandsnaam; const rowData: RowData = { cellData: { lezen: { @@ -249,7 +248,7 @@ export class AdviceComponent implements OnInit { .pipe( switchMap( (closedDocs: CloseDocument[]) => { if (closedDocs.length > 0) { - this.adviceFormData.documents = closedDocs.map( (doc, i) => { + this.adviceFormData.adviceDocuments = closedDocs.map( (doc, i) => { return { document: this.deleteUrls[i].drcUrl, editedDocument: doc.versionedUrl diff --git a/frontend/zac-ui/libs/features/kownsl/src/lib/advice/advice.service.ts b/frontend/zac-ui/libs/features/kownsl/src/lib/advice/advice.service.ts index 58a87a88b..2ff6407ea 100644 --- a/frontend/zac-ui/libs/features/kownsl/src/lib/advice/advice.service.ts +++ b/frontend/zac-ui/libs/features/kownsl/src/lib/advice/advice.service.ts @@ -5,6 +5,7 @@ import { AdviceForm } from '../../models/advice-form'; import {HttpParams, HttpResponse} from '@angular/common/http'; import { CloseDocument } from '../../models/close-document'; import {DocumentUrls, ReadWriteDocument} from "@gu/models"; +import { ReviewRequest } from '@gu/kownsl'; @Injectable({ providedIn: 'root' @@ -13,10 +14,9 @@ export class AdviceService { constructor(private http: ApplicationHttpClient) { } - getAdvice(uuid: string, assignee: string): Observable> { + getAdvice(uuid: string, assignee: string): Observable { const endpoint = encodeURI(`/api/kownsl/review-requests/${uuid}/advice`); const options = { - observe: 'response' as 'response', params: new HttpParams().set('assignee', assignee), } return this.http.Get(endpoint, options); diff --git a/frontend/zac-ui/libs/features/kownsl/src/lib/approval/approval.component.html b/frontend/zac-ui/libs/features/kownsl/src/lib/approval/approval.component.html index 99d8f19f7..12b4ee91a 100644 --- a/frontend/zac-ui/libs/features/kownsl/src/lib/approval/approval.component.html +++ b/frontend/zac-ui/libs/features/kownsl/src/lib/approval/approval.component.html @@ -79,10 +79,10 @@

{{zaakData.zaaktype.omschrijving}}

- -
+ +
-

Vorige accorderingen

+

Vorige accorderingen

@@ -93,10 +93,10 @@

Vorige accorderingen

-

Documenten

+

Documenten

@@ -107,7 +107,7 @@

Documenten

- +
diff --git a/frontend/zac-ui/libs/features/kownsl/src/lib/approval/approval.component.ts b/frontend/zac-ui/libs/features/kownsl/src/lib/approval/approval.component.ts index a1b4a4353..6b8f2ab0f 100644 --- a/frontend/zac-ui/libs/features/kownsl/src/lib/approval/approval.component.ts +++ b/frontend/zac-ui/libs/features/kownsl/src/lib/approval/approval.component.ts @@ -76,9 +76,9 @@ export class ApprovalComponent implements OnInit { return of(null) }), switchMap(res => { - const {zaak} = res; + const {zaak} = res return this.getZaakDetails(zaak.bronorganisatie, zaak.identificatie) - }) + }), ) .subscribe(() => { this.isLoading = false; @@ -131,7 +131,7 @@ export class ApprovalComponent implements OnInit { const tableData: Table = new Table(['Accordeur', 'Gedaan op', 'Akkoord'], []); // Add table body data - tableData.bodyData = approvalData.reviews.map(review => { + tableData.bodyData = approvalData.approvals.map(review => { const author = `${review.author.firstName} ${review.author.lastName}`; const approved = review.approved ? 'Akkoord' : 'Niet Akkoord'; const rowData: RowData = { diff --git a/frontend/zac-ui/libs/features/kownsl/src/lib/approval/approval.service.ts b/frontend/zac-ui/libs/features/kownsl/src/lib/approval/approval.service.ts index a4c297cce..4100a3e9c 100644 --- a/frontend/zac-ui/libs/features/kownsl/src/lib/approval/approval.service.ts +++ b/frontend/zac-ui/libs/features/kownsl/src/lib/approval/approval.service.ts @@ -3,6 +3,7 @@ import { ApplicationHttpClient } from '@gu/services'; import { Observable } from 'rxjs'; import { ApprovalForm } from '../../models/approval-form'; import {HttpParams, HttpResponse} from '@angular/common/http'; +import { ReviewRequest } from '@gu/kownsl'; @Injectable({ providedIn: 'root' @@ -11,10 +12,9 @@ export class ApprovalService { constructor(private http: ApplicationHttpClient) { } - getApproval(uuid: string, assignee: string): Observable> { + getApproval(uuid: string, assignee: string): Observable { const endpoint = encodeURI(`/api/kownsl/review-requests/${uuid}/approval`); const options = { - observe: 'response' as 'response', params: new HttpParams().set('assignee', assignee) } return this.http.Get(endpoint, options); diff --git a/frontend/zac-ui/libs/features/kownsl/src/models/advice-form.ts b/frontend/zac-ui/libs/features/kownsl/src/models/advice-form.ts index abd689756..736a78b0f 100644 --- a/frontend/zac-ui/libs/features/kownsl/src/models/advice-form.ts +++ b/frontend/zac-ui/libs/features/kownsl/src/models/advice-form.ts @@ -1,6 +1,6 @@ export interface AdviceForm { advice: string; - documents: AdviceDocument[] | [] + adviceDocuments: AdviceDocument[] | []; } export interface AdviceDocument { diff --git a/frontend/zac-ui/libs/features/kownsl/src/models/review-request.ts b/frontend/zac-ui/libs/features/kownsl/src/models/review-request.ts index 909527d0f..7893ec2b9 100644 --- a/frontend/zac-ui/libs/features/kownsl/src/models/review-request.ts +++ b/frontend/zac-ui/libs/features/kownsl/src/models/review-request.ts @@ -1,9 +1,8 @@ import { Review } from './review'; -import { ZaakDocument } from './zaak-document'; import { Zaak } from './zaak'; import {Approval} from "./approval"; import {Advice} from "./advice"; -import {User, UserGroupDetail} from '@gu/models'; +import {Document, User, UserGroupDetail} from '@gu/models'; export interface Metadata { taskDefinitionId: string; @@ -27,9 +26,11 @@ export interface ReviewRequest { toelichting: string; requester: Requester; metadata: Metadata; - zaakDocuments: ZaakDocument[]; - reviews: Review[]; zaak: Zaak; + zaakDocuments: Document[]; + zaakeigenschappen: any[]; + approvals?: Review[]; + advices?: Review[]; } export interface ReviewRequestSummary { From 26ef76279cb8ffc555fcbb3dcaf3766c65fc0f2b Mon Sep 17 00:00:00 2001 From: Kelvin Chan Date: Wed, 14 Feb 2024 17:00:37 +0100 Subject: [PATCH 2/7] 2321 - zaakeigenschappen in kownsl fe --- .../adviseren-accorderen.component.html | 15 ++++++++++ .../adviseren-accorderen.component.ts | 28 ++++++++++++++++++- .../configuration-components.scss | 4 +++ .../informatie/informatie.component.html | 2 +- .../zaak-detail/src/models/task-context.ts | 3 +- 5 files changed, 49 insertions(+), 3 deletions(-) diff --git a/frontend/zac-ui/libs/features/zaak-detail/src/lib/actions/keten-processen/configuration-components/adviseren-accorderen/adviseren-accorderen.component.html b/frontend/zac-ui/libs/features/zaak-detail/src/lib/actions/keten-processen/configuration-components/adviseren-accorderen/adviseren-accorderen.component.html index 82f0d7b85..fa9e17385 100644 --- a/frontend/zac-ui/libs/features/zaak-detail/src/lib/actions/keten-processen/configuration-components/adviseren-accorderen/adviseren-accorderen.component.html +++ b/frontend/zac-ui/libs/features/zaak-detail/src/lib/actions/keten-processen/configuration-components/adviseren-accorderen/adviseren-accorderen.component.html @@ -35,6 +35,21 @@

{{taskContextData.task.name}} voor {{taskContextData.context.zaakInformatie.

+
+ +

Over welke zaakinformatie gaat het?

+
    +
  • + + {{eigenschap.eigenschap.naam}}: {{eigenschap.waarde}} + +
  • +
+

Van wie wil je advies? Selecteer hier onder één of meerdere collega's. Je kunt ook gebruikersgroepen selecteren. Geef daarbij ook aan wat de einddatum is van de vraag.

diff --git a/frontend/zac-ui/libs/features/zaak-detail/src/lib/actions/keten-processen/configuration-components/adviseren-accorderen/adviseren-accorderen.component.ts b/frontend/zac-ui/libs/features/zaak-detail/src/lib/actions/keten-processen/configuration-components/adviseren-accorderen/adviseren-accorderen.component.ts index 23989e28e..cac21540c 100644 --- a/frontend/zac-ui/libs/features/zaak-detail/src/lib/actions/keten-processen/configuration-components/adviseren-accorderen/adviseren-accorderen.component.ts +++ b/frontend/zac-ui/libs/features/zaak-detail/src/lib/actions/keten-processen/configuration-components/adviseren-accorderen/adviseren-accorderen.component.ts @@ -11,10 +11,10 @@ import { Table, UserGroupDetail, UserSearchResult, - Zaak } from '@gu/models'; import { ModalService, PaginatorComponent, SnackbarService } from '@gu/components'; import { KownslSummaryComponent } from '../../../adviseren-accorderen/kownsl-summary.component'; +import { MatCheckboxChange } from '@angular/material/checkbox'; /** * @@ -68,6 +68,7 @@ export class AdviserenAccorderenComponent implements OnChanges { submitSuccess: boolean; errorMessage: string; error: any; + selectedProperties: string[] = []; constructor( private datePipe: DatePipe, @@ -317,6 +318,15 @@ export class AdviserenAccorderenComponent implements OnChanges { } } + /** + * Check if user exists in current selected properties array. + * @param {UserSearchResult} user + * @returns {boolean} + */ + isInSelectedProperties(property) { + return this.selectedProperties.some(prop => prop === property); + } + // // Events. // @@ -350,6 +360,21 @@ export class AdviserenAccorderenComponent implements OnChanges { this.selectedDocuments = event; } + /** + * Update selected users array. + * @param {MatCheckboxChange} event + */ + updateSelectedUsers(event: MatCheckboxChange) { + const selectedValue = event.source.value; + const isInSelectedProperties = this.isInSelectedProperties(selectedValue); + if (event.checked && !isInSelectedProperties) { + this.selectedProperties.push(selectedValue) + } else if (!event.checked && isInSelectedProperties) { + const i = this.selectedProperties.findIndex(property => property === selectedValue); + this.selectedProperties.splice(i, 1); + } + } + /** * Adds a step to the form. * @param i @@ -429,6 +454,7 @@ export class AdviserenAccorderenComponent implements OnChanges { form: this.taskContextData.form, assignedUsers: assignedUsers, documents: this.selectedDocuments, + zaakeigenschappen: this.selectedProperties, toelichting: toelichting, id: this.taskContextData.context.previouslyAssignedUsers.length > 0 ? this.taskContextData.context.id : null }; diff --git a/frontend/zac-ui/libs/features/zaak-detail/src/lib/actions/keten-processen/configuration-components/configuration-components.scss b/frontend/zac-ui/libs/features/zaak-detail/src/lib/actions/keten-processen/configuration-components/configuration-components.scss index 8838c769b..718c7e42a 100644 --- a/frontend/zac-ui/libs/features/zaak-detail/src/lib/actions/keten-processen/configuration-components/configuration-components.scss +++ b/frontend/zac-ui/libs/features/zaak-detail/src/lib/actions/keten-processen/configuration-components/configuration-components.scss @@ -19,6 +19,10 @@ .configuration-details { margin-bottom: 0.875rem; } + .configuration-properties { + list-style: none; + display: flex; + } .step-label { display: inline-block; font-size: 0.875rem; diff --git a/frontend/zac-ui/libs/features/zaak-detail/src/lib/overview/informatie/informatie.component.html b/frontend/zac-ui/libs/features/zaak-detail/src/lib/overview/informatie/informatie.component.html index a03cc95c2..1c0aebbee 100644 --- a/frontend/zac-ui/libs/features/zaak-detail/src/lib/overview/informatie/informatie.component.html +++ b/frontend/zac-ui/libs/features/zaak-detail/src/lib/overview/informatie/informatie.component.html @@ -10,7 +10,7 @@
- Date: Wed, 14 Feb 2024 22:08:48 +0100 Subject: [PATCH 3/7] :bug: Check if zaaktype is str or ZaakType when fetching zaakeigenschappen --- backend/src/zac/core/services.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/backend/src/zac/core/services.py b/backend/src/zac/core/services.py index 6fc2a5973..2778ba1c0 100644 --- a/backend/src/zac/core/services.py +++ b/backend/src/zac/core/services.py @@ -646,6 +646,11 @@ def fetch_zaakeigenschappen(zaak: Zaak) -> List[ZaakEigenschap]: def get_zaakeigenschappen(zaak: Zaak) -> List[ZaakEigenschap]: perf_logger.info(" Fetching eigenschappen for zaak %s", zaak.identificatie) + zaak.zaaktype = ( + zaak.zaaktype + if isinstance(zaak.zaaktype, ZaakType) + else get_zaaktype(zaak.zaaktype) + ) eigenschappen = { eigenschap.url: eigenschap for eigenschap in get_eigenschappen(zaak.zaaktype) } From 14a07c0659baf564800b5257c38dc1d51c0d8719 Mon Sep 17 00:00:00 2001 From: damm89 Date: Fri, 16 Feb 2024 10:57:00 +0100 Subject: [PATCH 4/7] :sparkles: Add review documents to all reviews, fix zaakeigenschappen, update kownsl test utils, update tests --- .../zac/camunda/tests/test_user_task_view.py | 9 +- .../contrib/objects/kownsl/api/serializers.py | 446 ++++++++--- .../zac/contrib/objects/kownsl/api/views.py | 93 ++- .../src/zac/contrib/objects/kownsl/camunda.py | 20 +- .../src/zac/contrib/objects/kownsl/data.py | 60 +- .../objects/kownsl/tests/test_camunda.py | 32 +- .../kownsl/tests/test_kownsl_services.py | 7 +- .../kownsl/tests/test_review_request_views.py | 15 +- .../objects/kownsl/tests/test_review_views.py | 755 ++++++++++++++++++ .../test_zaak_review_request_reminder_view.py | 10 +- .../tests/test_zaak_review_requests_views.py | 83 +- .../zac/contrib/objects/kownsl/tests/utils.py | 91 ++- backend/src/zac/contrib/objects/services.py | 12 +- .../src/zac/core/tests/test_zet_resultaat.py | 5 +- .../tests/test_review_requests_endpoint.py | 7 +- 15 files changed, 1398 insertions(+), 247 deletions(-) create mode 100644 backend/src/zac/contrib/objects/kownsl/tests/test_review_views.py diff --git a/backend/src/zac/camunda/tests/test_user_task_view.py b/backend/src/zac/camunda/tests/test_user_task_view.py index 8da00b559..5be862cf6 100644 --- a/backend/src/zac/camunda/tests/test_user_task_view.py +++ b/backend/src/zac/camunda/tests/test_user_task_view.py @@ -1,5 +1,4 @@ import uuid -from copy import deepcopy from pathlib import Path from unittest.mock import patch @@ -28,10 +27,7 @@ from zac.camunda.data import Task from zac.contrib.dowc.data import OpenDowc from zac.contrib.objects.kownsl.constants import KownslTypes -from zac.contrib.objects.kownsl.tests.utils import ( - ReviewRequestFactory, - ReviewsAdviceFactory, -) +from zac.contrib.objects.kownsl.tests.utils import ReviewRequestFactory, ReviewsFactory from zac.contrib.objects.services import factory_review_request, factory_reviews from zac.core.models import CoreConfig from zac.core.permissions import zaakproces_usertasks @@ -594,8 +590,7 @@ def test_get_zet_resultaat_context(self, m, *mocks): json=paginated_response([self.zaaktype]), ) tasks = [_get_task(**{"formKey": "zac:zetResultaat"})] - review_request = factory_review_request(ReviewRequestFactory()) - reviews = factory_reviews(ReviewsAdviceFactory()) + reviews = factory_reviews(ReviewsFactory()) mock_resource_get(m, self.zaaktype) resultaattype = generate_oas_component( "ztc", diff --git a/backend/src/zac/contrib/objects/kownsl/api/serializers.py b/backend/src/zac/contrib/objects/kownsl/api/serializers.py index a8438e248..cb1c839e8 100644 --- a/backend/src/zac/contrib/objects/kownsl/api/serializers.py +++ b/backend/src/zac/contrib/objects/kownsl/api/serializers.py @@ -19,10 +19,17 @@ from zac.elasticsearch.drf_api.serializers import ESListZaakDocumentSerializer from ..constants import KownslTypes -from ..data import Advice, AdviceDocument, Approval, OpenReview, ReviewRequest, Reviews +from ..data import ( + Advice, + Approval, + KownslZaakEigenschap, + OpenReview, + ReviewDocument, + ReviewRequest, +) ################################################### -# Reviews # +# Kownsl Users # ################################################### @@ -76,76 +83,82 @@ class KownslGroupSerializerSlugRelatedField(SerializerSlugRelatedField): response_serializer = KownslGroupSerializer -class ApprovalSerializer(APIModelSerializer): - author = KownslUserSerializerSlugRelatedField( - slug_field="username", - queryset=User.objects.all(), - required=True, - help_text=_("`username` of the author that approved."), - ) - group = KownslGroupSerializerSlugRelatedField( - slug_field="name", - queryset=Group.objects.all(), - required=False, - help_text=_("`name` of the group that author answered for."), - allow_null=True, - ) - created = serializers.DateTimeField( - help_text=_("Datetime review request was created."), - default=datetime.now(), - ) +################################################### +# Reviews - read # +################################################### + +class KownslZaakEigenschapSerializer(APIModelSerializer): class Meta: - model = Approval - fields = ("author", "created", "group", "approved", "toelichting") + model = KownslZaakEigenschap + fields = ("url", "naam", "waarde") -class AdviceDocumentSerializer(APIModelSerializer): - advice_url = serializers.SerializerMethodField( - help_text=_("URL-reference to the advice version of the document on the DoWC.") +class ReviewDocumentSerializer(APIModelSerializer): + review_url = serializers.SerializerMethodField( + help_text=_("URL to read the review version of the document on the DoWC.") + ) + bestandsnaam = serializers.CharField( + source="document.bestandsnaam", + help_text=_("The filename of the document."), + read_only=True, + ) + document = serializers.URLField( + help_text=_( + "URL-reference to document in DRC API, including `?versie=` parameter" + ), + source="document.url", ) - download_advice_url = serializers.SerializerMethodField( - help_text=_("URL-reference to download the advice version of the document.") + download_review_url = serializers.SerializerMethodField( + help_text=_("URL-reference to download the review version of the document.") ) download_source_url = serializers.SerializerMethodField( help_text=_("URL-reference to download the source version of the document.") ) source_url = serializers.SerializerMethodField( - help_text=_("URL-reference to the source version of the document on the DoWC.") + help_text=_("URL to read the source version of the document on the DoWC.") ) - bestandsnaam = serializers.CharField( - source="document.bestandsnaam", - help_text=_("The filename of the document."), - read_only=True, + edited_document = serializers.URLField( + write_only=True, + help_text=_( + "URL-reference of document to DRC API, including `?versie=` parameter." + ), + required=False, ) class Meta: - model = AdviceDocument + model = ReviewDocument fields = ( - "advice_url", - "advice_version", - "download_advice_url", + "review_url", + "review_version", + "bestandsnaam", + "document", + "download_review_url", "download_source_url", + "edited_document", "source_url", "source_version", - "bestandsnaam", ) extra_kwargs = { "source_version": { - "help_text": _("The version of the document before advice is given"), + "help_text": _("The version of the document before review is given"), + "read_only": True, }, - "advice_version": { - "help_text": _("The version of the document after advice is given.") + "review_version": { + "help_text": _("The version of the document after review is given."), + "read_only": True, }, } def build_url(self, url: str, kwargs: Dict) -> str: + if not url: + return "" return furl(url).set(kwargs).url - def get_advice_url(self, obj) -> str: + def get_review_url(self, obj) -> str: return self.build_url( get_dowc_url_from_obj(obj.document, purpose=DocFileTypes.read), - {"versie": obj.advice_version}, + {"versie": obj.review_version}, ) def get_source_url(self, obj) -> str: @@ -154,7 +167,7 @@ def get_source_url(self, obj) -> str: {"versie": obj.source_version}, ) - def get_download_advice_url(self, obj) -> str: + def get_download_review_url(self, obj) -> str: return self.build_url( reverse_lazy( "core:download-document", @@ -163,7 +176,7 @@ def get_download_advice_url(self, obj) -> str: "identificatie": obj.document.identificatie, }, ), - {"versie": obj.advice_version} if obj.advice_version else {}, + {"versie": obj.review_version} if obj.review_version else {}, ) def get_download_source_url(self, obj) -> str: @@ -179,12 +192,51 @@ def get_download_source_url(self, obj) -> str: ) -class AdviceSerializer(APIModelSerializer): - advice_documents = AdviceDocumentSerializer( - label=_("advice documents"), - help_text=_("URL-references of documents relevant to the advice."), +class ApprovalSerializer(APIModelSerializer): + author = KownslUserSerializerSlugRelatedField( + slug_field="username", + queryset=User.objects.all(), + required=True, + help_text=_( + "`username` of the author that approved. Automatically derived from request." + ), + ) + created = serializers.DateTimeField( + help_text=_("Datetime review request was created."), + default=lambda: datetime.now(), + ) + group = KownslGroupSerializerSlugRelatedField( + slug_field="name", + queryset=Group.objects.all(), + required=False, + help_text=_("`name` of the group that author answered for."), + allow_null=True, + ) + review_documents = ReviewDocumentSerializer( + label=_("review documents"), + help_text=_("Documents relevant to the review."), many=True, ) + zaakeigenschappen = KownslZaakEigenschapSerializer(many=True) + + class Meta: + model = Approval + fields = ( + "author", + "created", + "group", + "approved", + "review_documents", + "toelichting", + "zaakeigenschappen", + ) + extra_kwargs = { + "approved": {"required": True}, + "toelichting": {"required": False}, + } + + +class AdviceSerializer(APIModelSerializer): author = KownslUserSerializerSlugRelatedField( slug_field="username", queryset=User.objects.all(), @@ -193,7 +245,7 @@ class AdviceSerializer(APIModelSerializer): ) created = serializers.DateTimeField( help_text=_("Datetime review request was created."), - default=datetime.now(), + default=lambda: datetime.now(), ) group = KownslGroupSerializerSlugRelatedField( slug_field="name", @@ -203,15 +255,22 @@ class AdviceSerializer(APIModelSerializer): allow_null=True, allow_empty=True, ) + review_documents = ReviewDocumentSerializer( + label=_("review documents"), + help_text=_("Documents relevant to the review."), + many=True, + ) + zaakeigenschappen = KownslZaakEigenschapSerializer(many=True) class Meta: model = Advice fields = ( "advice", - "advice_documents", "author", "created", "group", + "review_documents", + "zaakeigenschappen", ) extra_kwargs = { "advice": {"help_text": _("Advice given for review request.")}, @@ -226,14 +285,6 @@ class ApprovalReviewsSerializer(serializers.Serializer): approvals = ApprovalSerializer(many=True, source="get_reviews") -class SubmitReviewSerializer(PolymorphicSerializer): - serializer_mapping = { - KownslTypes.advice: AdviceSerializer, - KownslTypes.approval: ApprovalSerializer, - } - discriminator_field = "review_type" - - class OpenReviewSerializer(APIModelSerializer): deadline = serializers.DateField( help_text=_("Deadline date of open review request."), required=True @@ -263,7 +314,213 @@ class Meta: ################################################### -# ReviewRequests # +# Reviews - write # +################################################### + + +class SubmitReviewDocumentSerializer(APIModelSerializer): + document = serializers.URLField( + help_text=_( + "URL-reference to document in DRC API, including `?versie=` parameter" + ) + ) + edited_document = serializers.URLField( + write_only=True, + help_text=_( + "URL-reference of document to DRC API, including `?versie=` parameter." + ), + required=False, + ) + source_version = serializers.SerializerMethodField( + help_text=_("The version of the document before review is given") + ) + review_version = serializers.SerializerMethodField( + help_text=_("The version of the document after review is given") + ) + + class Meta: + model = ReviewDocument + fields = ( + "document", + "edited_document", + "review_version", + "source_version", + ) + + def get_source_version(self, obj: Dict) -> int: + return int(furl(obj["document"]).args.get("versie")) + + def get_review_version(self, obj: Dict) -> int: + url = obj.get("edited_document", obj["document"]) + return int(furl(url).args.get("versie")) + + def validate(self, attrs: dict): + validated_data = super().validate(attrs) + + edited_document_url = validated_data.get("edited_document") + if edited_document_url is None: + edited_document_url = validated_data["document"] + + source_url = furl(validated_data["document"]) + review_url = furl(edited_document_url) + # compare URLs without query + if source_url.copy().remove("versie") != review_url.copy().remove("versie"): + raise serializers.ValidationError( + _("Source and review version must be versions of the same document") + ) + + source_version = source_url.args.get("versie") + review_version = review_url.args.get("versie") + + errs = {} + err_no_version = _("The URL is missing the '?versie=' querystring parameter.") + err_invalid_version = _("The 'versie' parameter must be a positive integer.") + for field, version in [ + ("document", source_version), + ("edited_document", review_version), + ]: + # check if the version parameter is present + if not version: + errs[field] = err_no_version + continue + + # check for valid ranges + try: + value = int(version) + except (ValueError, TypeError): + errs[field] = err_invalid_version + else: + if value <= 0: + errs[field] = err_invalid_version + + if errs: + raise serializers.ValidationError(errs) + + return validated_data + + def to_representation(self, data): + data = super().to_representation(data) + if "edited_document" in data: + del data["edited_document"] + return data + + +class SubmitApprovalSerializer(APIModelSerializer): + author = KownslUserSerializerSlugRelatedField( + slug_field="username", + queryset=User.objects.all(), + required=True, + help_text=_( + "`username` of the author that approved. Automatically derived from request." + ), + ) + created = serializers.DateTimeField( + help_text=_("Datetime review request was created."), + default=lambda: datetime.now(), + ) + group = KownslGroupSerializerSlugRelatedField( + slug_field="name", + queryset=Group.objects.all(), + help_text=_("`name` of the group that author answered for."), + allow_null=True, + ) + review_documents = SubmitReviewDocumentSerializer( + label=_("review documents"), + help_text=_("Documents relevant to the review."), + many=True, + ) + zaakeigenschappen = KownslZaakEigenschapSerializer(many=True) + + class Meta: + model = Approval + fields = ( + "author", + "created", + "group", + "approved", + "toelichting", + "review_documents", + "zaakeigenschappen", + ) + extra_kwargs = { + "approved": {"required": True}, + "toelichting": {"required": False}, + } + + def validate_group(self, group): + if not group: + return "" + return group + + def validate(self, data): + if not data.get("zaakeigenschappen") and not data.get("review_documents"): + raise serializers.ValidationError( + _( + "At least one of: [`review_documents`, `zaakeigenschappen`] is required." + ) + ) + + return super().validate(data) + + +class SubmitAdviceSerializer(APIModelSerializer): + author = KownslUserSerializerSlugRelatedField( + slug_field="username", + queryset=User.objects.all(), + required=True, + help_text=_("`username` of the author that adviced."), + ) + created = serializers.DateTimeField( + help_text=_("Datetime review request was created."), + default=lambda: datetime.now(), + ) + group = KownslGroupSerializerSlugRelatedField( + slug_field="name", + queryset=Group.objects.all(), + required=False, + help_text=_("`name` of the group that author answered for."), + allow_null=True, + ) + review_documents = SubmitReviewDocumentSerializer( + label=_("review documents"), + help_text=_("Documents relevant to the review."), + many=True, + required=False, + ) + zaakeigenschappen = KownslZaakEigenschapSerializer(many=True, required=False) + + class Meta: + model = Advice + fields = ( + "advice", + "author", + "created", + "group", + "review_documents", + "zaakeigenschappen", + ) + extra_kwargs = { + "advice": {"help_text": _("Advice given for review request.")}, + } + + def validate_group(self, group): + if not group: + return "" + return group + + def validate(self, data): + if not data.get("zaakeigenschappen") and not data.get("review_documents"): + raise serializers.ValidationError( + _( + "At least one of: [`review_documents`, `zaakeigenschappen`] is required." + ) + ) + + return super().validate(data) + + +################################################### +# ReviewRequests - read # ################################################### @@ -321,39 +578,6 @@ class ZaakRevReqDetailSerializer(PolymorphicSerializer): ) -class UpdateZaakReviewRequestSerializer(APIModelSerializer): - update_users = serializers.BooleanField( - required=False, - help_text=_( - "A boolean flag to indicate whether a change of users is requested in the review request. If review request is/will be locked - this will fail." - ), - ) - - class Meta: - model = ReviewRequest - fields = ("lock_reason", "update_users") - extra_kwargs = { - "lock_reason": { - "allow_blank": False, - "required": False, - "help_text": _( - "If the review request will be locked the users can not be updated." - ), - } - } - - def validate(self, data): - validated_data = super().validate(data) - if validated_data.get("update_users") and validated_data.get("lock_reason"): - raise serializers.ValidationError( - _("A locked review request can not be updated.") - ) - if validated_data.get("update_users"): - # Set is_being_reconfigured field - validated_data["is_being_reconfigured"] = True - return validated_data - - class ZaakRevReqSummarySerializer(APIModelSerializer): can_lock = serializers.SerializerMethodField( label=_("can lock request"), help_text=_("User can lock the review request.") @@ -386,3 +610,41 @@ def get_can_lock(self, obj) -> bool: def get_completed(self, obj) -> int: return len(obj.reviews) + + +################################################### +# ReviewRequests - write # +################################################### + + +class UpdateZaakReviewRequestSerializer(APIModelSerializer): + update_users = serializers.BooleanField( + required=False, + help_text=_( + "A boolean flag to indicate whether a change of users is requested in the review request. If review request is/will be locked - this will fail." + ), + ) + + class Meta: + model = ReviewRequest + fields = ("lock_reason", "update_users") + extra_kwargs = { + "lock_reason": { + "allow_blank": False, + "required": False, + "help_text": _( + "If the review request will be locked the users can not be updated." + ), + } + } + + def validate(self, data): + validated_data = super().validate(data) + if validated_data.get("update_users") and validated_data.get("lock_reason"): + raise serializers.ValidationError( + _("A locked review request can not be updated.") + ) + if validated_data.get("update_users"): + # Set is_being_reconfigured field + validated_data["is_being_reconfigured"] = True + return validated_data diff --git a/backend/src/zac/contrib/objects/kownsl/api/views.py b/backend/src/zac/contrib/objects/kownsl/api/views.py index 786144953..1e68e0b4a 100644 --- a/backend/src/zac/contrib/objects/kownsl/api/views.py +++ b/backend/src/zac/contrib/objects/kownsl/api/views.py @@ -1,4 +1,6 @@ import logging +from copy import deepcopy +from typing import Optional from django.contrib.auth.models import Group from django.utils.translation import gettext_lazy as _ @@ -34,7 +36,8 @@ ReviewIsUnlocked, ) from .serializers import ( - SubmitReviewSerializer, + SubmitAdviceSerializer, + SubmitApprovalSerializer, UpdateZaakReviewRequestSerializer, ZaakRevReqDetailSerializer, ZaakRevReqSummarySerializer, @@ -72,7 +75,12 @@ class SubmitReviewView(GetReviewRequestMixin, APIView): HasNotReviewed, ReviewIsUnlocked, ) - serializer_class = ZaakRevReqDetailSerializer + serializer_class = None + + def get_assignee_from_query_param(self) -> Optional[str]: + if not (assignee := self.request.query_params.get("assignee", None)): + raise exceptions.ValidationError("`assignee` query parameter is required.") + return assignee @extend_schema( summary=_("Retrieve review request."), @@ -88,19 +96,51 @@ class SubmitReviewView(GetReviewRequestMixin, APIView): responses={200: ZaakRevReqDetailSerializer}, ) def get(self, request, request_uuid): - if not (request.query_params.get("assignee")): - raise exceptions.ValidationError("'assignee' query parameter is required.") + # check filter + self.get_assignee_from_query_param() + # check permissions review_request = self.get_object() review_request.zaak = get_zaak(zaak_url=review_request.zaak) - serializer = self.serializer_class( + serializer = ZaakRevReqDetailSerializer( instance=review_request, context={"request": request, "view": self}, ) return Response(serializer.data) + def post(self, request, request_uuid): + # check filter + assignee = self.get_assignee_from_query_param() + # check permissions + rr = self.get_object() + + # resolve assignee + data = deepcopy(request.data) + assignee = resolve_assignee(assignee) + if isinstance(assignee, Group): + data["group"] = f"{assignee}" + data["author"] = request.user.username + data["requester"] = rr.requester + + # pass to serializer + serializer = self.serializer_class(data=data) + serializer.is_valid(raise_exception=True) + + # submit review + submit_review(serializer.data, review_request=rr) + + # invalidate the review request cache + invalidate_review_cache(rr) + return Response( + status=status.HTTP_204_NO_CONTENT, + ) + + +class SubmitAdviceView(SubmitReviewView): + serializer_class = SubmitAdviceSerializer + @extend_schema( - summary=_("Create review for review request."), + summary=_("Create advice for review request."), parameters=[ OpenApiParameter( name="assignee", @@ -110,33 +150,32 @@ def get(self, request, request_uuid): location=OpenApiParameter.QUERY, ) ], - request=SubmitReviewSerializer, - responses={201: SubmitReviewSerializer(many=True)}, + request=SubmitAdviceSerializer, + responses={204: None}, ) def post(self, request, request_uuid): - if not (assignee := request.query_params.get("assignee")): - raise exceptions.ValidationError("'assignee' query parameter is required.") - - data = {**request.data} - assignee = resolve_assignee(assignee) - if isinstance(assignee, Group): - data["group"] = f"{assignee}" - - rr = self.get_object() # check permissions - data["review_type"] = rr.review_type - serializer = SubmitReviewSerializer(data=data) - serializer.is_valid(raise_exception=True) - reviews = submit_review(serializer.data, review_request=rr) - invalidate_review_cache(rr) - return Response(SubmitReviewSerializer(reviews.reviews, many=True).data) + return super().post(request, request_uuid) class SubmitApprovalView(SubmitReviewView): - pass + serializer_class = SubmitApprovalSerializer - -class SubmitAdviceView(SubmitReviewView): - pass + @extend_schema( + summary=_("Create approval for review request."), + parameters=[ + OpenApiParameter( + name="assignee", + required=True, + type=OpenApiTypes.STR, + description=_("Assignee of the user task in camunda."), + location=OpenApiParameter.QUERY, + ) + ], + request=SubmitApprovalSerializer, + responses={204: None}, + ) + def post(self, request, request_uuid): + return super().post(request, request_uuid) class ZaakReviewRequestSummaryView(GetZaakMixin, APIView): diff --git a/backend/src/zac/contrib/objects/kownsl/camunda.py b/backend/src/zac/contrib/objects/kownsl/camunda.py index 4b6da7fd5..6fef7fa1b 100644 --- a/backend/src/zac/contrib/objects/kownsl/camunda.py +++ b/backend/src/zac/contrib/objects/kownsl/camunda.py @@ -29,7 +29,7 @@ ) from .cache import invalidate_review_requests_cache from .constants import FORM_KEY_REVIEW_TYPE_MAPPING, KownslTypes -from .data import AdviceApprovalContext, AssignedUsers, ReviewRequest +from .data import AssignedUsers, ReviewContext, ReviewRequest from .fields import SelectZaakEigenschappenKownslField @@ -132,7 +132,7 @@ def validate(self, attrs): @usertask_context_serializer -class AdviceApprovalContextSerializer(APIModelSerializer): +class ReviewContextSerializer(APIModelSerializer): camunda_assigned_users = CamundaAssignedUsersSerializer( help_text=_("Users or groups assigned from within the camunda process.") ) @@ -163,7 +163,7 @@ class AdviceApprovalContextSerializer(APIModelSerializer): zaak_informatie = ZaakInformatieTaskSerializer() class Meta: - model = AdviceApprovalContext + model = ReviewContext fields = ( "camunda_assigned_users", "documents_link", @@ -506,7 +506,7 @@ def get_review_request_from_task(task: Task) -> Optional[ReviewRequest]: return review_request -def get_review_context(task: Task) -> AdviceApprovalContext: +def get_review_context(task: Task) -> ReviewContext: rr = get_review_request_from_task(task) zaak_context = get_zaak_context(task, require_zaaktype=True) zaak = zaak_context.zaak @@ -535,21 +535,21 @@ def get_review_context(task: Task) -> AdviceApprovalContext: @register( "zac:configureAdviceRequest", - AdviceApprovalContextSerializer, + ReviewContextSerializer, ConfigureReviewRequestSerializer, ) -def get_advice_context(task: Task) -> AdviceApprovalContext: +def get_advice_context(task: Task) -> ReviewContext: context = get_review_context(task) context["review_type"] = KownslTypes.advice - return AdviceApprovalContext(**context) + return ReviewContext(**context) @register( "zac:configureApprovalRequest", - AdviceApprovalContextSerializer, + ReviewContextSerializer, ConfigureReviewRequestSerializer, ) -def get_approval_context(task: Task) -> AdviceApprovalContext: +def get_approval_context(task: Task) -> ReviewContext: context = get_review_context(task) context["review_type"] = KownslTypes.approval - return AdviceApprovalContext(**context) + return ReviewContext(**context) diff --git a/backend/src/zac/contrib/objects/kownsl/data.py b/backend/src/zac/contrib/objects/kownsl/data.py index 0d730c5c6..4a5538c14 100644 --- a/backend/src/zac/contrib/objects/kownsl/data.py +++ b/backend/src/zac/contrib/objects/kownsl/data.py @@ -35,17 +35,26 @@ class OpenReview(Model): @dataclass -class AdviceDocument(Model): - advice_version: int +class ReviewDocument(Model): + review_version: int source_version: int document: str +@dataclass +class KownslZaakEigenschap(Model): + url: str + naam: str + waarde: str + + @dataclass class Advice(Model): created: datetime advice: str - advice_documents: List[AdviceDocument] + review_documents: List[ReviewDocument] + zaakeigenschappen: List[KownslZaakEigenschap] + author: dict = field(default_factory=dict) group: dict = field(default_factory=dict) @@ -57,10 +66,16 @@ class Advice(Model): class Approval(Model): created: datetime approved: bool + review_documents: List[ReviewDocument] + zaakeigenschappen: List[KownslZaakEigenschap] + author: dict = field(default_factory=dict) group: dict = field(default_factory=dict) toelichting: str = "" + # for internal use only + documents: list = field(default_factory=list) + @dataclass class AssignedUsers(Model): @@ -147,26 +162,30 @@ def num_assigned_users(self, obj): def get_completed(self) -> int: return len(self.get_reviews()) - def _resolve_advice_documents_for_advices( - self, advices: List[Advice] + def _resolve_review_documents_for_reviews( + self, reviews: Union[List[Advice], List[Approval]] ) -> List[Advice]: - documents = set( - advice_document.document - for advice in advices - for advice_document in advice.advice_documents + documents = list( + set( + furl(review_document.document).remove(args=True).url + for review in reviews + for review_document in review.review_documents + ) ) if documents: - _documents = search_informatieobjects(urls=list(documents)) + _documents = search_informatieobjects(urls=documents, size=len(documents)) documents = {doc.url: doc for doc in _documents} - for advice in advices: - advice_documents = [] - for advice_document in advice.advice_documents: - advice_document.document = documents[advice_document.document] - advice_documents.append(advice_document) + for review in reviews: + review_documents = [] + for review_document in review.review_documents: + review_document.document = documents[ + furl(review_document.document).remove(args=True).url + ] + review_documents.append(review_document) - advice.documents = advice_documents + review.documents = review_documents - return advices + return reviews def get_reviews(self) -> List[Union[Advice, Approval]]: if not self.fetched_reviews: @@ -174,10 +193,7 @@ def get_reviews(self) -> List[Union[Advice, Approval]]: if reviews := get_reviews_for_review_request(self): self.reviews = reviews.reviews - if self.review_type == KownslTypes.advice: - self.reviews = self._resolve_advice_documents_for_advices( - self.reviews - ) + self.reviews = self._resolve_review_documents_for_reviews(self.reviews) else: self.reviews = [] @@ -279,7 +295,7 @@ def get_zaakeigenschappen(self) -> List[ZaakEigenschap]: @dataclass -class AdviceApprovalContext(Context): +class ReviewContext(Context): camunda_assigned_users: AssignedUsers documents_link: str review_type: str diff --git a/backend/src/zac/contrib/objects/kownsl/tests/test_camunda.py b/backend/src/zac/contrib/objects/kownsl/tests/test_camunda.py index 54806a822..c25386c22 100644 --- a/backend/src/zac/contrib/objects/kownsl/tests/test_camunda.py +++ b/backend/src/zac/contrib/objects/kownsl/tests/test_camunda.py @@ -26,13 +26,13 @@ from zac.contrib.objects.kownsl.data import KownslTypes, ReviewRequest, Reviews from zac.core.tests.utils import ClearCachesMixin from zac.elasticsearch.api import create_informatieobject_document -from zac.tests.utils import mock_resource_get, paginated_response +from zac.tests.utils import paginated_response from zgw.models.zrc import Zaak from ..camunda import ( - AdviceApprovalContextSerializer, AssignedUsersSerializer, ConfigureReviewRequestSerializer, + ReviewContextSerializer, ZaakInformatieTaskSerializer, ) from .utils import ( @@ -44,7 +44,7 @@ AdviceFactory, AssignedUsersFactory, ReviewRequestFactory, - ReviewsAdviceFactory, + ReviewsFactory, UserAssigneeFactory, ) @@ -212,7 +212,7 @@ def test_advice_context_serializer(self, m): return_value=None, ): task_data = UserTaskData(task=task, context=_get_context(task)) - serializer = AdviceApprovalContextSerializer(instance=task_data) + serializer = ReviewContextSerializer(instance=task_data) self.assertEqual( { "camunda_assigned_users": { @@ -275,7 +275,7 @@ def test_approval_context_serializer(self, m): return_value=None, ): task_data = UserTaskData(task=task, context=_get_context(task)) - serializer = AdviceApprovalContextSerializer(instance=task_data) + serializer = ReviewContextSerializer(instance=task_data) self.assertEqual( serializer.data["context"], { @@ -332,7 +332,7 @@ def test_approval_context_serializer_with_user(self, m): return_value=None, ): task_data = UserTaskData(task=task, context=_get_context(task)) - serializer = AdviceApprovalContextSerializer(instance=task_data) + serializer = ReviewContextSerializer(instance=task_data) self.assertEqual( serializer.data["context"], { @@ -381,6 +381,7 @@ def test_advice_context_serializer_previously_assigned_users(self, m): user_assignees = UserAssigneeFactory( **{ + "email": "some-other-author@email.zac", "username": "some-other-author", "first_name": "Some Other First", "last_name": "Some Last", @@ -410,10 +411,10 @@ def test_advice_context_serializer_previously_assigned_users(self, m): # Let resolve_assignee get the right users and groups UserFactory.create( - username=review_request["assignedUsers"][0]["userAssignees"][0] + username=review_request["assignedUsers"][0]["userAssignees"][0]["username"] ) UserFactory.create( - username=review_request["assignedUsers"][1]["userAssignees"][0] + username=review_request["assignedUsers"][1]["userAssignees"][0]["username"] ) rr = factory(ReviewRequest, review_request) @@ -432,7 +433,7 @@ def test_advice_context_serializer_previously_assigned_users(self, m): return_value=rr, ): task_data = UserTaskData(task=task, context=_get_context(task)) - serializer = AdviceApprovalContextSerializer(instance=task_data) + serializer = ReviewContextSerializer(instance=task_data) self.assertEqual( { "camunda_assigned_users": { @@ -452,6 +453,7 @@ def test_advice_context_serializer_previously_assigned_users(self, m): { "user_assignees": [ { + "email": "some-author@email.zac", "first_name": "Some First", "full_name": "Some First Some Last", "last_name": "Some Last", @@ -465,6 +467,7 @@ def test_advice_context_serializer_previously_assigned_users(self, m): { "user_assignees": [ { + "email": "some-other-author@email.zac", "first_name": "Some Other First", "full_name": "Some Other First Some Last", "last_name": "Some Last", @@ -556,6 +559,7 @@ def setUpTestData(cls): user_assignees = UserAssigneeFactory( **{ + "email": "some-other-author@email.zac", "username": "some-other-author", "first_name": "Some Other First", "last_name": "Some Last", @@ -575,8 +579,12 @@ def setUpTestData(cls): rr["assignedUsers"].append(assigned_users2) # Let resolve_assignee get the right users and groups - UserFactory.create(username=rr["assignedUsers"][0]["userAssignees"][0]) - UserFactory.create(username=rr["assignedUsers"][1]["userAssignees"][0]) + UserFactory.create( + username=rr["assignedUsers"][0]["userAssignees"][0]["username"] + ) + UserFactory.create( + username=rr["assignedUsers"][1]["userAssignees"][0]["username"] + ) cls.review_request = factory(ReviewRequest, rr) cls.patch_create_review_request = patch( "zac.contrib.objects.kownsl.camunda.create_review_request", @@ -925,7 +933,7 @@ def test_configure_review_request_serializer_fail_get_process_variables(self): def test_reconfigure_review_request_serializer_user_already_reviewed(self, m): advice = AdviceFactory() review_request = ReviewRequestFactory() - reviews_advice = ReviewsAdviceFactory() + reviews_advice = ReviewsFactory() user = UserFactory.create(username=advice["author"]["username"]) assigned_users = [ { diff --git a/backend/src/zac/contrib/objects/kownsl/tests/test_kownsl_services.py b/backend/src/zac/contrib/objects/kownsl/tests/test_kownsl_services.py index 586b1f587..c791e915c 100644 --- a/backend/src/zac/contrib/objects/kownsl/tests/test_kownsl_services.py +++ b/backend/src/zac/contrib/objects/kownsl/tests/test_kownsl_services.py @@ -28,7 +28,7 @@ AdviceFactory, AssignedUsersFactory, ReviewRequestFactory, - ReviewsAdviceFactory, + ReviewsFactory, UserAssigneeFactory, ) from zac.core.models import CoreConfig, MetaObjectTypesConfig @@ -78,6 +78,7 @@ def setUpTestData(cls): user_assignees = UserAssigneeFactory( **{ + "email": "some-other-author@email.zac", "username": "some-other-author", "first_name": "Some Other First", "last_name": "Some Last", @@ -95,7 +96,7 @@ def setUpTestData(cls): cls.review_request = ReviewRequestFactory() cls.review_request["assignedUsers"].append(assigned_users2) cls.advice = AdviceFactory() - cls.reviews_advice = ReviewsAdviceFactory() + cls.reviews_advice = ReviewsFactory() cls.reviews_advice["reviews"] = [cls.advice] cls.review_object = deepcopy(REVIEW_OBJECT) @@ -257,7 +258,7 @@ def test_update_assigned_users_review_request(self, m, *mocks): rr = factory_review_request(self.review_request) # Avoid patching fetch_reviews and everything - reviews_advice = ReviewsAdviceFactory() + reviews_advice = ReviewsFactory() rr.reviews = factory_reviews(reviews_advice).reviews rr.fetched_reviews = True diff --git a/backend/src/zac/contrib/objects/kownsl/tests/test_review_request_views.py b/backend/src/zac/contrib/objects/kownsl/tests/test_review_request_views.py index 3ec720ab4..5704213ce 100644 --- a/backend/src/zac/contrib/objects/kownsl/tests/test_review_request_views.py +++ b/backend/src/zac/contrib/objects/kownsl/tests/test_review_request_views.py @@ -22,7 +22,7 @@ AdviceFactory, AssignedUsersFactory, ReviewRequestFactory, - ReviewsAdviceFactory, + ReviewsFactory, UserAssigneeFactory, ) @@ -50,6 +50,7 @@ def setUpTestData(cls): user_assignees = UserAssigneeFactory( **{ + "email": "some-other-author@email.zac", "username": "some-other-author", "first_name": "Some Other First", "last_name": "Some Last", @@ -67,14 +68,10 @@ def setUpTestData(cls): cls.review_request["assignedUsers"].append(assigned_users2) cls.advice = AdviceFactory() - cls.reviews_advice = ReviewsAdviceFactory() + cls.reviews_advice = ReviewsFactory() cls.group = GroupFactory.create(name="some-group") - def setUp(self): - super().setUp() - def test_fail_create_review_query_param(self, m): - self.client.force_authenticate(user=self.user) url = reverse( "kownsl:reviewrequest-advice", @@ -84,7 +81,7 @@ def test_fail_create_review_query_param(self, m): response = self.client.post(url, body) self.assertEqual(response.status_code, 400) - self.assertEqual(response.json(), ["'assignee' query parameter is required."]) + self.assertEqual(response.json(), ["`assignee` query parameter is required."]) def test_fail_get_review_request_query_param(self, m): self.client.force_authenticate(user=self.user) @@ -95,7 +92,7 @@ def test_fail_get_review_request_query_param(self, m): response = self.client.get(url) self.assertEqual(response.status_code, 400) - self.assertEqual(response.json(), ["'assignee' query parameter is required."]) + self.assertEqual(response.json(), ["`assignee` query parameter is required."]) def test_success_get_review_request(self, m): mock_service_oas_get(m, ZAKEN_ROOT, "zrc") @@ -185,7 +182,7 @@ def test_success_get_review_already_exists_for_group_but_not_user(self, m): rr = factory_review_request(rev_req) advice = deepcopy(self.advice) - advice["adviceDocuments"] = list() + advice["reviewDocuments"] = list() advice["group"] = {"name": "some-other-group", "fullName": "groeop some group"} reviews_advice = deepcopy(self.reviews_advice) reviews_advice["reviews"] = [advice] diff --git a/backend/src/zac/contrib/objects/kownsl/tests/test_review_views.py b/backend/src/zac/contrib/objects/kownsl/tests/test_review_views.py new file mode 100644 index 000000000..88b8bd255 --- /dev/null +++ b/backend/src/zac/contrib/objects/kownsl/tests/test_review_views.py @@ -0,0 +1,755 @@ +from copy import deepcopy +from os import path +from unittest.mock import patch + +from django.urls import reverse_lazy + +import requests_mock +from django_camunda.utils import underscoreize +from freezegun import freeze_time +from rest_framework.test import APITestCase +from zgw_consumers.api_models.base import factory +from zgw_consumers.api_models.catalogi import Eigenschap, InformatieObjectType, ZaakType +from zgw_consumers.api_models.constants import VertrouwelijkheidsAanduidingen +from zgw_consumers.api_models.documenten import Document +from zgw_consumers.api_models.zaken import ZaakEigenschap +from zgw_consumers.constants import APITypes +from zgw_consumers.models import Service +from zgw_consumers.test import generate_oas_component, mock_service_oas_get + +from zac.accounts.models import User +from zac.accounts.tests.factories import UserFactory +from zac.camunda.constants import AssigneeTypeChoices +from zac.contrib.objects.kownsl.constants import KownslTypes +from zac.contrib.objects.kownsl.tests.utils import ( + CATALOGI_ROOT, + DOCUMENT_URL, + DOCUMENTS_ROOT, + OBJECTS_ROOT, + OBJECTTYPES_ROOT, + REVIEW_OBJECT, + REVIEW_OBJECTTYPE, + REVIEW_REQUEST_OBJECTTYPE, + ZAAK_URL, + ZAKEN_ROOT, + AdviceFactory, + ApprovalFactory, + AssignedUsersFactory, + ReviewRequestFactory, + ReviewsFactory, + UserAssigneeFactory, +) +from zac.core.models import CoreConfig, MetaObjectTypesConfig +from zac.core.tests.utils import ClearCachesMixin +from zac.elasticsearch.api import create_informatieobject_document +from zac.tests.utils import mock_resource_get, paginated_response +from zgw.models.zrc import Zaak + +from ...services import factory_review_request, factory_reviews + +CATALOGUS_URL = f"{CATALOGI_ROOT}/catalogussen/e13e72de-56ba-42b6-be36-5c280e9b30cd" + + +@freeze_time("2022-04-14T15:51:09.830235") +@requests_mock.Mocker() +class KownslReviewsTests(ClearCachesMixin, APITestCase): + @classmethod + def setUpTestData(cls): + super().setUpTestData() + Service.objects.create(api_type=APITypes.ztc, api_root=CATALOGI_ROOT) + Service.objects.create(api_type=APITypes.zrc, api_root=ZAKEN_ROOT) + Service.objects.create(api_type=APITypes.drc, api_root=DOCUMENTS_ROOT) + objects_service = Service.objects.create( + api_type=APITypes.orc, api_root=OBJECTS_ROOT + ) + objecttypes_service = Service.objects.create( + api_type=APITypes.orc, api_root=OBJECTTYPES_ROOT + ) + config = CoreConfig.get_solo() + config.primary_objects_api = objects_service + config.primary_objecttypes_api = objecttypes_service + config.save() + + meta_config = MetaObjectTypesConfig.get_solo() + meta_config.review_request_objecttype = REVIEW_REQUEST_OBJECTTYPE["url"] + meta_config.review_objecttype = REVIEW_OBJECTTYPE["url"] + meta_config.save() + + cls.zaaktype = generate_oas_component( + "ztc", + "schemas/ZaakType", + url=f"{CATALOGI_ROOT}zaaktypen/3e2a1218-e598-4bbe-b520-cb56b0584d60", + identificatie="ZT1", + catalogus=CATALOGUS_URL, + vertrouwelijkheidaanduiding=VertrouwelijkheidsAanduidingen.openbaar, + ) + cls.eigenschap = generate_oas_component( + "ztc", + "schemas/Eigenschap", + zaaktype=cls.zaaktype["url"], + naam="some-property", + specificatie={ + "groep": "dummy", + "formaat": "tekst", + "lengte": "3", + "kardinaliteit": "1", + "waardenverzameling": [], + }, + url=f"{CATALOGI_ROOT}eigenschappen/68b5b40c-c479-4008-a57b-a268b280df99", + ) + cls.zaak_json = generate_oas_component( + "zrc", + "schemas/Zaak", + url=ZAAK_URL, + identificatie="ZAAK-2020-0010", + bronorganisatie="123456782", + zaaktype=cls.zaaktype["url"], + vertrouwelijkheidaanduiding=VertrouwelijkheidsAanduidingen.openbaar, + startdatum="2020-12-25", + uiterlijkeEinddatumAfdoening="2021-01-04", + ) + cls.zaakeigenschap = generate_oas_component( + "zrc", + "schemas/ZaakEigenschap", + zaak=cls.zaak_json["url"], + eigenschap=cls.eigenschap["url"], + naam=cls.eigenschap["naam"], + waarde="bar", + ) + cls.informatieobjecttype = generate_oas_component( + "ztc", + "schemas/InformatieObjectType", + url=f"{CATALOGI_ROOT}informatieobjecttypen/d5d7285d-ce95-4f9e-a36f-181f1c642aa6", + omschrijving="bijlage", + catalogus=CATALOGUS_URL, + vertrouwelijkheidaanduiding=VertrouwelijkheidsAanduidingen.openbaar, + ) + cls.document = generate_oas_component( + "drc", + "schemas/EnkelvoudigInformatieObject", + url=DOCUMENT_URL, + identificatie="DOC-2020-007", + bronorganisatie="123456782", + informatieobjecttype=cls.informatieobjecttype["url"], + vertrouwelijkheidaanduiding=VertrouwelijkheidsAanduidingen.openbaar, + bestandsomvang=10, + ) + + # Dict to models + cls.zaak = factory(Zaak, cls.zaak_json) + cls.zaak.zaaktype = factory(ZaakType, cls.zaaktype) + cls.zaakeigenschap = factory(ZaakEigenschap, cls.zaakeigenschap) + cls.zaakeigenschap.eigenschap = factory(Eigenschap, cls.eigenschap) + + # Mock DRC components + cls.document = factory(Document, cls.document) + cls.document.informatieobjecttype = factory( + InformatieObjectType, cls.informatieobjecttype + ) + cls.document.last_edited_date = None # avoid patching fetching audit trail + + # Create elasticsearch document + cls.es_document = create_informatieobject_document(cls.document) + + cls.zaak = factory(Zaak, cls.zaak_json) + + user_assignees = UserAssigneeFactory( + **{ + "email": "some-other-author@email.zac", + "username": "some-other-author", + "first_name": "Some Other First", + "last_name": "Some Last", + "full_name": "Some Other First Some Last", + } + ) + assigned_users2 = AssignedUsersFactory( + **{ + "deadline": "2022-04-15", + "user_assignees": [user_assignees], + "group_assignees": [], + "email_notification": False, + } + ) + cls.review_request = ReviewRequestFactory() + cls.review_request["assignedUsers"].append(assigned_users2) + cls.advice = AdviceFactory() + cls.reviews_advice = ReviewsFactory() + cls.reviews_advice["reviews"] = [cls.advice] + + cls.review_object = deepcopy(REVIEW_OBJECT) + cls.review_object["record"]["data"] = cls.reviews_advice + + cls.get_zaakeigenschappen_patcher = patch( + "zac.contrib.objects.kownsl.data.get_zaakeigenschappen", + return_value=[cls.zaakeigenschap], + ) + cls.get_zaak_patcher = patch( + "zac.contrib.objects.kownsl.api.views.get_zaak", return_value=cls.zaak + ) + cls.search_informatieobjects_patcher = patch( + "zac.contrib.objects.kownsl.data.search_informatieobjects", + return_value=[cls.es_document], + ) + cls.get_supported_extensions_patcher = patch( + "zac.contrib.dowc.utils.get_supported_extensions", + return_value=[path.splitext(cls.es_document.bestandsnaam)], + ) + cls.fetch_reviews_patcher = patch( + "zac.contrib.objects.services.fetch_reviews", + return_value=cls.review_object, + ) + + # Make sure all users associated to the REVIEW REQUEST exist + users = deepcopy( + [ + cls.review_request["assignedUsers"][0]["userAssignees"][0], + cls.review_request["assignedUsers"][1]["userAssignees"][0], + cls.review_request["requester"], + ] + ) + # del full_name + for user in users: + del user["fullName"] + UserFactory.create(**underscoreize(user)) + + cls.user = User.objects.get( + username=cls.review_request["requester"]["username"] + ) + + cls.approval_url = reverse_lazy( + "kownsl:reviewrequest-approval", + kwargs={"request_uuid": cls.review_request["id"]}, + ) + cls.review_url = reverse_lazy( + "kownsl:reviewrequest-advice", + kwargs={"request_uuid": cls.review_request["id"]}, + ) + + def test_fail_get_approval_view_no_assignee(self, m): + self.client.force_authenticate(user=self.user) + response = self.client.get(self.approval_url) + self.assertEqual(response.status_code, 400) + self.assertEqual(response.json(), ["`assignee` query parameter is required."]) + + def test_fail_get_approval_review_request_not_found(self, m): + mock_service_oas_get(m, OBJECTS_ROOT, "objects") + mock_service_oas_get(m, OBJECTTYPES_ROOT, "objecttypes") + + m.get(f"{OBJECTTYPES_ROOT}objecttypes", json=[REVIEW_REQUEST_OBJECTTYPE]) + m.post( + f"{OBJECTS_ROOT}objects/search?pageSize=100", + json=paginated_response([]), + ) + + self.client.force_authenticate(user=self.user) + url = self.approval_url + f"?assignee={AssigneeTypeChoices.user}:{self.user}" + response = self.client.get(url) + self.assertEqual(response.status_code, 404) + + def test_fail_get_approval_zaak_not_found(self, m): + mock_service_oas_get(m, OBJECTS_ROOT, "objects") + mock_service_oas_get(m, OBJECTTYPES_ROOT, "objecttypes") + mock_service_oas_get(m, ZAKEN_ROOT, "zrc") + + m.get(self.zaak.url, status_code=404) + + m.get(f"{OBJECTTYPES_ROOT}objecttypes", json=[REVIEW_REQUEST_OBJECTTYPE]) + m.post( + f"{OBJECTS_ROOT}objects/search?pageSize=100", + json=paginated_response([]), + ) + + self.client.force_authenticate(user=self.user) + url = self.approval_url + f"?assignee={AssigneeTypeChoices.user}:{self.user}" + response = self.client.get(url) + self.assertEqual(response.status_code, 404) + + def test_success_get_approval_review_request(self, m): + mock_service_oas_get(m, ZAKEN_ROOT, "zrc") + + mock_resource_get(m, self.zaak_json) + rr = factory_review_request(self.review_request) + rr.fetched_reviews = True + rr.reviews = [] + rr.review_type = KownslTypes.approval + + self.client.force_authenticate(user=self.user) + url = self.approval_url + f"?assignee={AssigneeTypeChoices.user}:{self.user}" + + rr.zaakeigenschappen = [self.zaakeigenschap.url] + with patch( + "zac.contrib.objects.kownsl.api.views.get_review_request", + return_value=rr, + ): + with self.fetch_reviews_patcher: + with self.get_zaak_patcher: + with self.search_informatieobjects_patcher: + with self.get_supported_extensions_patcher: + with self.get_zaakeigenschappen_patcher: + response = self.client.get(url) + + self.assertEqual(response.status_code, 200) + self.assertEqual( + response.json(), + { + "created": self.review_request["created"], + "documents": [], + "id": self.review_request["id"], + "isBeingReconfigured": self.review_request["isBeingReconfigured"], + "locked": self.review_request["locked"], + "lockReason": self.review_request["lockReason"], + "openReviews": [ + { + "deadline": "2022-04-14", + "groups": [], + "users": ["Some First Some Last"], + }, + { + "deadline": "2022-04-15", + "groups": [], + "users": ["Some Other First Some Last"], + }, + ], + "requester": self.review_request["requester"], + "reviewType": KownslTypes.approval, + "zaak": { + "identificatie": self.zaak.identificatie, + "bronorganisatie": self.zaak.bronorganisatie, + "url": self.zaak.url, + }, + "zaakeigenschappen": [ + { + "url": self.zaakeigenschap.url, + "formaat": self.eigenschap["specificatie"]["formaat"], + "waarde": self.zaakeigenschap.waarde, + "eigenschap": { + "url": self.eigenschap["url"], + "naam": self.eigenschap["naam"], + "toelichting": self.eigenschap["toelichting"], + "specificatie": self.eigenschap["specificatie"], + }, + } + ], + "zaakDocuments": [], + "approvals": [], + }, + ) + + def test_success_get_advice_review_request(self, m): + mock_service_oas_get(m, ZAKEN_ROOT, "zrc") + + mock_resource_get(m, self.zaak_json) + rr = factory_review_request(self.review_request) + rr.fetched_reviews = True + rr.reviews = [] + + self.client.force_authenticate(user=self.user) + url = self.approval_url + f"?assignee={AssigneeTypeChoices.user}:{self.user}" + + with patch( + "zac.contrib.objects.kownsl.api.views.get_review_request", return_value=rr + ): + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + self.assertEqual( + response.json(), + { + "created": self.review_request["created"], + "documents": [], + "id": self.review_request["id"], + "isBeingReconfigured": self.review_request["isBeingReconfigured"], + "locked": self.review_request["locked"], + "lockReason": self.review_request["lockReason"], + "openReviews": [ + { + "deadline": "2022-04-14", + "groups": [], + "users": ["Some First Some Last"], + }, + { + "deadline": "2022-04-15", + "groups": [], + "users": ["Some Other First Some Last"], + }, + ], + "requester": self.review_request["requester"], + "reviewType": KownslTypes.advice, + "zaak": { + "identificatie": self.zaak.identificatie, + "bronorganisatie": self.zaak.bronorganisatie, + "url": self.zaak.url, + }, + "zaakeigenschappen": [], + "zaakDocuments": [], + "advices": [], + }, + ) + + def test_success_create_approval_review_request(self, m): + mock_service_oas_get(m, OBJECTS_ROOT, "objects") + mock_service_oas_get(m, OBJECTTYPES_ROOT, "objecttypes") + mock_service_oas_get(m, ZAKEN_ROOT, "zrc") + + mock_resource_get(m, self.zaak_json) + rr = factory_review_request(self.review_request) + rr.fetched_reviews = True + rr.reviews = [] + rr.review_type = KownslTypes.approval + + self.client.force_authenticate(user=self.user) + url = self.approval_url + f"?assignee={AssigneeTypeChoices.user}:{self.user}" + + payload = { + "approved": True, + "toelichting": "some toelichting here", + "group": "", + "reviewDocuments": [ + { + "document": self.document.url + "?versie=1", + } + ], + "zaakeigenschappen": [ + { + "url": self.zaakeigenschap.url, + "naam": self.eigenschap["naam"], + "waarde": self.zaakeigenschap.waarde, + } + ], + } + approval = ApprovalFactory() + reviews = ReviewsFactory(reviews=[approval], review_type=KownslTypes.approval) + reviews_object = deepcopy(REVIEW_OBJECT) + reviews_object["record"]["data"] = reviews + + with patch( + "zac.contrib.objects.kownsl.api.views.get_review_request", return_value=rr + ): + with patch("zac.contrib.objects.services.fetch_reviews", return_value=None): + with patch( + "zac.contrib.objects.services._create_unique_uuid_for_object", + return_value=reviews["id"], + ): + with patch( + "zac.contrib.objects.services.create_meta_object_and_relate_to_zaak", + return_value=reviews_object, + ) as mock_create_object: + response = self.client.post(url, payload) + + self.assertEqual(response.status_code, 204) + mock_create_object.assert_called_once_with( + "review", + { + "zaak": rr.zaak, + "review_request": str(rr.id), + "review_type": reviews["reviewType"], + "reviews": [ + { + "author": { + "email": self.user.email, + "first_name": self.user.first_name, + "full_name": f"{self.user.first_name} {self.user.last_name}", + "last_name": self.user.last_name, + "username": self.user.username, + }, + "created": "2022-04-14T15:51:09.830235Z", + "group": dict(), + "approved": payload["approved"], + "review_documents": [ + { + "document": self.document.url + "?versie=1", + "review_version": 1, + "source_version": 1, + } + ], + "toelichting": payload["toelichting"], + "zaakeigenschappen": [ + { + "url": self.zaakeigenschap.url, + "naam": self.eigenschap["naam"], + "waarde": self.zaakeigenschap.waarde, + } + ], + } + ], + "id": reviews["id"], + "requester": rr.requester, + }, + rr.zaak, + ) + + def test_success_create_advice_review_request(self, m): + mock_service_oas_get(m, OBJECTS_ROOT, "objects") + mock_service_oas_get(m, OBJECTTYPES_ROOT, "objecttypes") + mock_service_oas_get(m, ZAKEN_ROOT, "zrc") + + mock_resource_get(m, self.zaak_json) + rr = factory_review_request(self.review_request) + rr.fetched_reviews = True + rr.reviews = [] + rr.review_type = KownslTypes.advice + + self.client.force_authenticate(user=self.user) + url = self.review_url + f"?assignee={AssigneeTypeChoices.user}:{self.user}" + + payload = { + "advice": "some advice", + "group": "", + "reviewDocuments": [ + { + "document": self.document.url + "?versie=1", + "editedDocument": self.document.url + "?versie=2", + } + ], + "zaakeigenschappen": [ + { + "url": self.zaakeigenschap.url, + "naam": self.eigenschap["naam"], + "waarde": self.zaakeigenschap.waarde, + } + ], + } + reviews_object = deepcopy(REVIEW_OBJECT) + reviews_object["record"]["data"] = self.reviews_advice + + with patch( + "zac.contrib.objects.kownsl.api.views.get_review_request", return_value=rr + ): + with patch("zac.contrib.objects.services.fetch_reviews", return_value=None): + with patch( + "zac.contrib.objects.services._create_unique_uuid_for_object", + return_value=self.reviews_advice["id"], + ): + with patch( + "zac.contrib.objects.services.create_meta_object_and_relate_to_zaak", + return_value=reviews_object, + ) as mock_create_object: + response = self.client.post(url, payload) + + self.assertEqual(response.status_code, 204) + mock_create_object.assert_called_once_with( + "review", + { + "zaak": rr.zaak, + "review_request": str(rr.id), + "review_type": self.reviews_advice["reviewType"], + "reviews": [ + { + "advice": payload["advice"], + "author": { + "email": self.user.email, + "first_name": self.user.first_name, + "full_name": f"{self.user.first_name} {self.user.last_name}", + "last_name": self.user.last_name, + "username": self.user.username, + }, + "created": "2022-04-14T15:51:09.830235Z", + "group": dict(), + "review_documents": [ + { + "document": self.document.url + "?versie=1", + "review_version": 2, + "source_version": 1, + } + ], + "zaakeigenschappen": [ + { + "url": self.zaakeigenschap.url, + "naam": self.eigenschap["naam"], + "waarde": self.zaakeigenschap.waarde, + } + ], + } + ], + "id": self.reviews_advice["id"], + "requester": rr.requester, + }, + rr.zaak, + ) + + def test_success_create_successive_advice_review_request(self, m): + mock_service_oas_get(m, OBJECTS_ROOT, "objects") + mock_service_oas_get(m, OBJECTTYPES_ROOT, "objecttypes") + mock_service_oas_get(m, ZAKEN_ROOT, "zrc") + + mock_resource_get(m, self.zaak_json) + rr = factory_review_request(self.review_request) + rr.fetched_reviews = True + rr.reviews = factory_reviews(self.reviews_advice).reviews + rr.review_type = KownslTypes.advice + + user = User.objects.get( + username=rr.assigned_users[1].user_assignees[0]["username"] + ) + + self.client.force_authenticate(user=user) + url = self.review_url + f"?assignee={AssigneeTypeChoices.user}:{user}" + + payload = { + "advice": "some more advice", + "group": "", + "reviewDocuments": [ + { + "document": self.document.url + "?versie=2", + "editedDocument": self.document.url + "?versie=3", + } + ], + "zaakeigenschappen": [ + { + "url": self.zaakeigenschap.url, + "naam": self.eigenschap["naam"], + "waarde": self.zaakeigenschap.waarde, + } + ], + } + reviews_object = deepcopy(REVIEW_OBJECT) + reviews_object["record"]["data"] = self.reviews_advice + + with patch( + "zac.contrib.objects.kownsl.api.views.get_review_request", return_value=rr + ): + with patch( + "zac.contrib.objects.services.fetch_reviews", + return_value=[reviews_object], + ): + with patch( + "zac.contrib.objects.services._create_unique_uuid_for_object", + return_value="8fc50840-3450-4497-9d29-791113417023", + ): + with patch( + "zac.contrib.objects.services.update_object_record_data", + return_value=reviews_object, + ) as mock_update_object: + response = self.client.post(url, payload) + + self.assertEqual(response.status_code, 204) + mock_update_object.assert_called_once_with( + { + "url": reviews_object["url"], + "uuid": reviews_object["uuid"], + "type": reviews_object["type"], + "record": { + "index": reviews_object["record"]["index"], + "typeVersion": reviews_object["record"]["typeVersion"], + "data": { + "id": self.reviews_advice["id"], + "reviews": [ + { + "advice": self.reviews_advice["reviews"][0]["advice"], + "author": self.reviews_advice["reviews"][0]["author"], + "created": self.reviews_advice["reviews"][0]["created"], + "group": self.reviews_advice["reviews"][0]["group"], + "reviewDocuments": [ + { + "document": self.document.url + "?versie=1", + "reviewVersion": 2, + "sourceVersion": 1, + } + ], + "zaakeigenschappen": self.reviews_advice["reviews"][0][ + "zaakeigenschappen" + ], + }, + { + "advice": payload["advice"], + "author": { + "email": user.email, + "firstName": user.first_name, + "fullName": f"{user.first_name} {user.last_name}", + "lastName": user.last_name, + "username": user.username, + }, + "created": "2022-04-14T15:51:09.830235Z", + "group": dict(), + "reviewDocuments": [ + { + "document": self.document.url + "?versie=2", + "reviewVersion": 3, + "sourceVersion": 2, + } + ], + "zaakeigenschappen": [ + { + "url": self.zaakeigenschap.url, + "naam": self.eigenschap["naam"], + "waarde": self.zaakeigenschap.waarde, + } + ], + }, + ], + "requester": { + "email": self.user.email, + "username": self.user.username, + "firstName": self.user.first_name, + "lastName": self.user.last_name, + "fullName": f"{self.user.first_name} {self.user.last_name}", + }, + "reviewRequest": str(rr.id), + "reviewType": rr.review_type, + "zaak": rr.zaak, + }, + "geometry": reviews_object["record"]["geometry"], + "startAt": reviews_object["record"]["startAt"], + "endAt": reviews_object["record"]["endAt"], + "registrationAt": reviews_object["record"]["registrationAt"], + "correctionFor": reviews_object["record"]["correctionFor"], + "correctedBy": reviews_object["record"]["correctedBy"], + }, + }, + { + "id": self.reviews_advice["id"], + "reviews": [ + { + "advice": self.reviews_advice["reviews"][0]["advice"], + "author": self.reviews_advice["reviews"][0]["author"], + "created": self.reviews_advice["reviews"][0]["created"], + "group": self.reviews_advice["reviews"][0]["group"], + "reviewDocuments": [ + { + "document": self.document.url + "?versie=1", + "reviewVersion": 2, + "sourceVersion": 1, + } + ], + "zaakeigenschappen": self.reviews_advice["reviews"][0][ + "zaakeigenschappen" + ], + }, + { + "advice": payload["advice"], + "author": { + "email": user.email, + "firstName": user.first_name, + "fullName": f"{user.first_name} {user.last_name}", + "lastName": user.last_name, + "username": user.username, + }, + "created": "2022-04-14T15:51:09.830235Z", + "group": dict(), + "reviewDocuments": [ + { + "document": self.document.url + "?versie=2", + "reviewVersion": 3, + "sourceVersion": 2, + } + ], + "zaakeigenschappen": [ + { + "url": self.zaakeigenschap.url, + "naam": self.eigenschap["naam"], + "waarde": self.zaakeigenschap.waarde, + } + ], + }, + ], + "reviewRequest": str(rr.id), + "reviewType": rr.review_type, + "requester": { + "email": self.user.email, + "username": self.user.username, + "firstName": self.user.first_name, + "lastName": self.user.last_name, + "fullName": f"{self.user.first_name} {self.user.last_name}", + }, + "zaak": rr.zaak, + }, + ) diff --git a/backend/src/zac/contrib/objects/kownsl/tests/test_zaak_review_request_reminder_view.py b/backend/src/zac/contrib/objects/kownsl/tests/test_zaak_review_request_reminder_view.py index 3dd4e67c5..e2645d571 100644 --- a/backend/src/zac/contrib/objects/kownsl/tests/test_zaak_review_request_reminder_view.py +++ b/backend/src/zac/contrib/objects/kownsl/tests/test_zaak_review_request_reminder_view.py @@ -76,6 +76,7 @@ def setUpTestData(cls): user_assignees = UserAssigneeFactory( **{ + "email": "some-other-author@email.zac", "username": "some-other-author", "first_name": "Some Other First", "last_name": "Some Last", @@ -95,10 +96,14 @@ def setUpTestData(cls): # Let resolve_assignee get the right users and groups UserFactory.create( - username=cls.review_request["assignedUsers"][0]["userAssignees"][0] + username=cls.review_request["assignedUsers"][0]["userAssignees"][0][ + "username" + ] ) UserFactory.create( - username=cls.review_request["assignedUsers"][1]["userAssignees"][0] + username=cls.review_request["assignedUsers"][1]["userAssignees"][0][ + "username" + ] ) review_request = factory(ReviewRequest, cls.review_request) cls.get_review_request_patcher = patch( @@ -203,6 +208,7 @@ def setUpTestData(cls): user_assignees = UserAssigneeFactory( **{ + "email": "some-other-author@email.zac", "username": "some-other-author", "first_name": "Some Other First", "last_name": "Some Last", diff --git a/backend/src/zac/contrib/objects/kownsl/tests/test_zaak_review_requests_views.py b/backend/src/zac/contrib/objects/kownsl/tests/test_zaak_review_requests_views.py index 255369bbe..72b3ff7f8 100644 --- a/backend/src/zac/contrib/objects/kownsl/tests/test_zaak_review_requests_views.py +++ b/backend/src/zac/contrib/objects/kownsl/tests/test_zaak_review_requests_views.py @@ -38,8 +38,9 @@ ZAKEN_ROOT, AdviceFactory, AssignedUsersFactory, + KownslZaakEigenschapFactory, ReviewRequestFactory, - ReviewsAdviceFactory, + ReviewsFactory, UserAssigneeFactory, ) @@ -54,6 +55,7 @@ class ZaakReviewRequestsResponseTests(ClearCachesMixin, APITestCase): @classmethod def setUpTestData(cls): + cls.maxDiff = None super().setUpTestData() cls.user = SuperUserFactory.create() Service.objects.create(api_type=APITypes.ztc, api_root=CATALOGI_ROOT) @@ -140,6 +142,7 @@ def setUpTestData(cls): # Mock kownsl components user_assignees = UserAssigneeFactory( **{ + "email": "some-other-author@email.zac", "username": "some-other-author", "first_name": "Some Other First", "last_name": "Some Last", @@ -159,7 +162,13 @@ def setUpTestData(cls): ) cls.review_request_dict["assignedUsers"].append(assigned_users2) cls.review_request = factory(ReviewRequest, cls.review_request_dict) - cls.reviews = ReviewsAdviceFactory() + zei = KownslZaakEigenschapFactory( + url=zaakeigenschap.url, + waarde=zaakeigenschap.waarde, + naam=cls.eigenschap["naam"], + ) + advice = AdviceFactory(zaakeigenschappen=[zei]) + cls.reviews = ReviewsFactory(reviews=[advice]) reviews = factory_reviews(cls.reviews) # Patchers @@ -307,6 +316,33 @@ def test_get_zaak_review_requests_detail(self, m): response_data = response.json() self.assertEqual( { + "advices": [ + { + "advice": self.reviews["reviews"][0]["advice"], + "author": self.reviews["reviews"][0]["author"], + "created": self.reviews["reviews"][0]["created"], + "group": {}, + "reviewDocuments": [ + { + "bestandsnaam": self.document["bestandsnaam"], + "document": self.document["url"], + "downloadReviewUrl": f"/core/documenten/{self.document['bronorganisatie']}/{self.document['identificatie']}/?versie=2", + "downloadSourceUrl": f"/core/documenten/{self.document['bronorganisatie']}/{self.document['identificatie']}/?versie=1", + "reviewUrl": "", + "reviewVersion": 2, + "sourceUrl": "", + "sourceVersion": 1, + } + ], + "zaakeigenschappen": [ + { + "naam": self.eigenschap["naam"], + "url": self.zaakeigenschap["url"], + "waarde": self.zaakeigenschap["waarde"], + } + ], + } + ], "created": self.review_request_dict["created"], "documents": [DOCUMENT_URL], "id": str(self.review_request.id), @@ -321,6 +357,7 @@ def test_get_zaak_review_requests_detail(self, m): } ], "requester": { + "email": "some-author@email.zac", "firstName": "Some First", "fullName": "Some First Some Last", "lastName": "Some Last", @@ -328,23 +365,10 @@ def test_get_zaak_review_requests_detail(self, m): }, "reviewType": self.review_request.review_type, "zaak": { - "identificatie": self.zaak["identificatie"], "bronorganisatie": self.zaak["bronorganisatie"], + "identificatie": self.zaak["identificatie"], "url": self.zaak["url"], }, - "zaakeigenschappen": [ - { - "url": self.zaakeigenschap["url"], - "formaat": self.eigenschap["specificatie"]["formaat"], - "waarde": self.zaakeigenschap["waarde"], - "eigenschap": { - "url": self.eigenschap["url"], - "naam": self.eigenschap["naam"], - "toelichting": self.eigenschap["toelichting"], - "specificatie": self.eigenschap["specificatie"], - }, - } - ], "zaakDocuments": [ { "auteur": self.document["auteur"], @@ -374,23 +398,17 @@ def test_get_zaak_review_requests_detail(self, m): "writeUrl": "", } ], - "advices": [ + "zaakeigenschappen": [ { - "advice": self.reviews["reviews"][0]["advice"], - "adviceDocuments": [ - { - "adviceUrl": "?versie=2", - "adviceVersion": 2, - "downloadAdviceUrl": "/core/documenten/123456782/DOC-2020-007/?versie=2", - "downloadSourceUrl": "/core/documenten/123456782/DOC-2020-007/?versie=1", - "sourceUrl": "?versie=1", - "sourceVersion": 1, - "bestandsnaam": self.document["bestandsnaam"], - } - ], - "author": self.reviews["reviews"][0]["author"], - "created": self.reviews["reviews"][0]["created"], - "group": {}, + "eigenschap": { + "url": self.eigenschap["url"], + "naam": self.eigenschap["naam"], + "toelichting": self.eigenschap["toelichting"], + "specificatie": self.eigenschap["specificatie"], + }, + "formaat": self.eigenschap["specificatie"]["formaat"], + "url": self.zaakeigenschap["url"], + "waarde": self.zaakeigenschap["waarde"], } ], }, @@ -570,6 +588,7 @@ def setUpTestData(cls): user_assignees = UserAssigneeFactory( **{ + "email": "some-other-author@email.zac", "username": "some-other-author", "first_name": "Some Other First", "last_name": "Some Last", diff --git a/backend/src/zac/contrib/objects/kownsl/tests/utils.py b/backend/src/zac/contrib/objects/kownsl/tests/utils.py index d9eca8dfe..9812b75eb 100644 --- a/backend/src/zac/contrib/objects/kownsl/tests/utils.py +++ b/backend/src/zac/contrib/objects/kownsl/tests/utils.py @@ -22,12 +22,15 @@ "title": "v2", } +RR_ID = "14aec7a0-06de-4b55-b839-a1c9a0415b46" + class UserAssigneeFactory(factory.DictFactory): - username = "some-author" + email = "some-author@email.zac" first_name = "Some First" - last_name = "Some Last" full_name = "Some First Some Last" + username = "some-author" + last_name = "Some Last" class Meta: rename = { @@ -61,9 +64,6 @@ class Meta: } -RR_ID = "14aec7a0-06de-4b55-b839-a1c9a0415b46" - - class ReviewRequestFactory(factory.DictFactory): assigned_users = factory.List([factory.SubFactory(AssignedUsersFactory)]) created = "2022-04-14T15:49:09.830235Z" @@ -96,36 +96,53 @@ class Meta: } -class AdviceDocumentFactory(factory.DictFactory): - document = deepcopy(DOCUMENT_URL) +class ReviewDocumentFactory(factory.DictFactory): + document = deepcopy(DOCUMENT_URL) + "?versie=1" source_version = 1 - advice_version = 2 + review_version = 2 class Meta: - rename = {"source_version": "sourceVersion", "advice_version": "adviceVersion"} + rename = {"source_version": "sourceVersion", "review_version": "reviewVersion"} + + +class KownslZaakEigenschapFactory(factory.DictFactory): + url = f"{ZAAK_URL}zaakeigenschappen/c0524527-3539-4313-8c00-41358069e65b" + naam = "SomeEigenschap" + waarde = "SomeWaarde" class AdviceFactory(factory.DictFactory): - created = "2022-04-14T15:50:09.830235Z" author = factory.SubFactory(UserAssigneeFactory) advice = "some-advice" - advice_documents = factory.List([factory.SubFactory(AdviceDocumentFactory)]) + created = "2022-04-14T15:50:09.830235Z" + group = factory.Dict(dict()) + review_documents = factory.List([factory.SubFactory(ReviewDocumentFactory)]) + zaakeigenschappen = factory.List([factory.SubFactory(KownslZaakEigenschapFactory)]) class Meta: rename = { - "advice_documents": "adviceDocuments", + "review_documents": "reviewDocuments", } class ApprovalFactory(factory.DictFactory): - created = ("2022-04-14T15:51:09.830235Z",) author = factory.SubFactory(UserAssigneeFactory) approved = True + created = "2022-04-14T15:51:09.830235Z" + group = factory.Dict(dict()) + review_documents = factory.List([factory.SubFactory(ReviewDocumentFactory)]) toelichting = "some-toelichting" + zaakeigenschappen = factory.List([factory.SubFactory(KownslZaakEigenschapFactory)]) + + class Meta: + rename = { + "review_documents": "reviewDocuments", + } -class ReviewsAdviceFactory(factory.DictFactory): +class ReviewsFactory(factory.DictFactory): id = "6a9a169e-aa6f-4dd7-bbea-6bedea74c456" + requester = factory.SubFactory(UserAssigneeFactory) reviews = factory.List([factory.SubFactory(AdviceFactory)]) review_request = deepcopy(RR_ID) review_type = KownslTypes.advice @@ -357,9 +374,13 @@ class Meta: "advice": {"type": "string"}, "author": {"$ref": "#/$defs/user"}, "created": {"$ref": "#/$defs/created"}, - "adviceDocuments": { + "reviewDocuments": { "type": "array", - "items": {"$ref": "#/$defs/adviceDocument"}, + "items": {"$ref": "#/$defs/reviewDocument"}, + }, + "zaakeigenschappen": { + "type": "array", + "items": {"$ref": "#/$defs/zaakeigenschap"}, }, }, }, @@ -374,23 +395,48 @@ class Meta: "created": {"$ref": "#/$defs/created"}, "approved": {"type": "boolean"}, "toelichting": {"type": "string"}, + "reviewDocuments": { + "type": "array", + "items": {"$ref": "#/$defs/reviewDocument"}, + }, + "zaakeigenschappen": { + "type": "array", + "items": {"$ref": "#/$defs/zaakeigenschap"}, + }, }, }, "reviewType": {"type": "string"}, "reviewRequest": {"type": "string"}, - "adviceDocument": { + "reviewDocument": { "type": "object", - "title": "AdviceDocument", - "required": ["url", "sourceVersion", "adviceVersion"], + "title": "reviewDocument", + "required": ["document", "sourceVersion", "reviewVersion"], "properties": { - "url": {"type": "string"}, - "adviceVersion": {"type": "string"}, + "document": {"type": "string"}, + "reviewVersion": {"type": "string"}, "sourceVersion": {"type": "string"}, }, }, + "zaakeigenschap": { + "type": "object", + "title": "zaakeigenschap", + "required": ["url", "naam", "waarde"], + "properties": { + "url": {"type": "string"}, + "naam": {"type": "string"}, + "waarde": {"type": "string"}, + }, + }, }, "title": "Reviews", - "required": ["id", "meta", "reviewRequest", "reviewType", "zaak", "reviews"], + "required": [ + "id", + "requester", + "reviewRequest", + "reviewType", + "reviews", + "zaak", + ], "properties": { "id": {"$ref": "#/$defs/id"}, "zaak": {"$ref": "#/$defs/zaak"}, @@ -400,6 +446,7 @@ class Meta: "oneOf": [{"$ref": "#/$defs/advice"}, {"$ref": "#/$defs/approval"}] }, }, + "requester": {"$ref": "#/$defs/user"}, "reviewType": {"$ref": "#/$defs/reviewType"}, "reviewRequest": {"$ref": "#/$defs/reviewRequest"}, }, diff --git a/backend/src/zac/contrib/objects/services.py b/backend/src/zac/contrib/objects/services.py index 07d664d4d..8232a398d 100644 --- a/backend/src/zac/contrib/objects/services.py +++ b/backend/src/zac/contrib/objects/services.py @@ -1,7 +1,7 @@ import datetime import logging from typing import Callable, Dict, List, Optional, Tuple, Union -from uuid import UUID, uuid4 +from uuid import uuid4 from django.conf import settings from django.http import Http404 @@ -630,15 +630,17 @@ def get_reviews_for_requester( def create_reviews_for_review_request( data: Dict, review_request: ReviewRequest ) -> Reviews: - data = { + object_data = { "zaak": review_request.zaak, - "review_request": review_request.id, + "review_request": str(review_request.id), "review_type": review_request.review_type, "reviews": [data], "id": _create_unique_uuid_for_object(fetch_review_on_id), "requester": review_request.requester, } - result = create_meta_object_and_relate_to_zaak("review", data, data["zaak"]) + result = create_meta_object_and_relate_to_zaak( + "review", object_data, review_request.zaak + ) return factory_reviews(result["record"]["data"]) @@ -651,7 +653,7 @@ def update_reviews_for_review_request(data: Dict, reviews_object: Dict) -> Revie def submit_review(data: Dict, review_request: ReviewRequest) -> Reviews: - reviews_object = fetch_reviews(review_request=review_request.id) + reviews_object = fetch_reviews(review_request=str(review_request.id)) if reviews_object: return update_reviews_for_review_request(data, reviews_object[0]) diff --git a/backend/src/zac/core/tests/test_zet_resultaat.py b/backend/src/zac/core/tests/test_zet_resultaat.py index dea512e3e..9ed3bd9d9 100644 --- a/backend/src/zac/core/tests/test_zet_resultaat.py +++ b/backend/src/zac/core/tests/test_zet_resultaat.py @@ -21,7 +21,7 @@ from zac.contrib.objects.kownsl.tests.utils import ( AssignedUsersFactory, ReviewRequestFactory, - ReviewsAdviceFactory, + ReviewsFactory, UserAssigneeFactory, ) from zac.tests.utils import mock_resource_get, paginated_response @@ -201,6 +201,7 @@ def test_zet_resultaat_context_serializer(self, m): user_assignees = UserAssigneeFactory( **{ + "email": "some-other-author@email.zac", "username": "some-other-author", "first_name": "Some Other First", "last_name": "Some Last", @@ -220,7 +221,7 @@ def test_zet_resultaat_context_serializer(self, m): rr = factory(ReviewRequest, review_request) # Avoid patching fetch_reviews and everything - reviews = factory(Reviews, ReviewsAdviceFactory()) + reviews = factory(Reviews, ReviewsFactory()) rr.fetched_reviews = True with patch( diff --git a/backend/src/zac/werkvoorraad/tests/test_review_requests_endpoint.py b/backend/src/zac/werkvoorraad/tests/test_review_requests_endpoint.py index bd4ee0cc4..211b108e2 100644 --- a/backend/src/zac/werkvoorraad/tests/test_review_requests_endpoint.py +++ b/backend/src/zac/werkvoorraad/tests/test_review_requests_endpoint.py @@ -23,7 +23,7 @@ AdviceFactory, AssignedUsersFactory, ReviewRequestFactory, - ReviewsAdviceFactory, + ReviewsFactory, UserAssigneeFactory, ) from zac.core.models import CoreConfig, MetaObjectTypesConfig @@ -42,6 +42,7 @@ class ReviewRequestsTests(ESMixin, ClearCachesMixin, APITestCase): @classmethod def setUpTestData(cls): + cls.maxDiff = None super().setUpTestData() Service.objects.create(api_type=APITypes.ztc, api_root=CATALOGI_ROOT) Service.objects.create(api_type=APITypes.zrc, api_root=ZAKEN_ROOT) @@ -92,6 +93,7 @@ def setUpTestData(cls): ) user_assignees = UserAssigneeFactory( **{ + "email": "some-other-author@email.zac", "username": "some-other-author", "first_name": "Some Other First", "last_name": "Some Last", @@ -194,7 +196,7 @@ def test_workstack_review_requests_endpoint_found_zaak(self, m, *mocks): self.client.force_authenticate(user=self.user) advice = AdviceFactory() - reviews_advice = ReviewsAdviceFactory() + reviews_advice = ReviewsFactory() reviews_advice["reviews"] = [advice] review_object = deepcopy(REVIEW_OBJECT) @@ -253,6 +255,7 @@ def test_workstack_review_requests_endpoint_found_zaak(self, m, *mocks): "advices": [ { "author": { + "email": advice["author"]["email"], "firstName": advice["author"]["firstName"], "lastName": advice["author"]["lastName"], "username": advice["author"]["username"], From 4633ab33b0c90f65733dc704793071611110e035 Mon Sep 17 00:00:00 2001 From: daniel Date: Wed, 21 Feb 2024 09:45:31 +0100 Subject: [PATCH 5/7] :ok_hand: Implement feedback from FE --- .../contrib/objects/kownsl/api/serializers.py | 5 ++- .../zac/contrib/objects/kownsl/api/views.py | 10 +++++ .../objects/kownsl/tests/test_review_views.py | 38 ++++++++++--------- 3 files changed, 34 insertions(+), 19 deletions(-) diff --git a/backend/src/zac/contrib/objects/kownsl/api/serializers.py b/backend/src/zac/contrib/objects/kownsl/api/serializers.py index cb1c839e8..dc8c9a4a8 100644 --- a/backend/src/zac/contrib/objects/kownsl/api/serializers.py +++ b/backend/src/zac/contrib/objects/kownsl/api/serializers.py @@ -211,6 +211,7 @@ class ApprovalSerializer(APIModelSerializer): required=False, help_text=_("`name` of the group that author answered for."), allow_null=True, + allow_empty=True, ) review_documents = ReviewDocumentSerializer( label=_("review documents"), @@ -421,6 +422,7 @@ class SubmitApprovalSerializer(APIModelSerializer): group = KownslGroupSerializerSlugRelatedField( slug_field="name", queryset=Group.objects.all(), + required=False, help_text=_("`name` of the group that author answered for."), allow_null=True, ) @@ -428,8 +430,9 @@ class SubmitApprovalSerializer(APIModelSerializer): label=_("review documents"), help_text=_("Documents relevant to the review."), many=True, + required=False, ) - zaakeigenschappen = KownslZaakEigenschapSerializer(many=True) + zaakeigenschappen = KownslZaakEigenschapSerializer(many=True, required=False) class Meta: model = Approval diff --git a/backend/src/zac/contrib/objects/kownsl/api/views.py b/backend/src/zac/contrib/objects/kownsl/api/views.py index 1e68e0b4a..5870c2421 100644 --- a/backend/src/zac/contrib/objects/kownsl/api/views.py +++ b/backend/src/zac/contrib/objects/kownsl/api/views.py @@ -8,6 +8,7 @@ from django_camunda.api import send_message from drf_spectacular.types import OpenApiTypes from drf_spectacular.utils import OpenApiParameter, extend_schema +from furl import furl from rest_framework import authentication, exceptions, permissions, status from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response @@ -27,6 +28,7 @@ update_review_request, ) from ..cache import invalidate_review_cache, invalidate_review_requests_cache +from ..constants import KownslTypes from ..data import ReviewRequest from ..permissions import ( CanReadOrUpdateReviews, @@ -122,6 +124,14 @@ def post(self, request, request_uuid): data["author"] = request.user.username data["requester"] = rr.requester + # in approvals documents can't be changed - set them here. + if rr.review_type == KownslTypes.approval: + docs = rr.get_zaak_documents() + data["review_documents"] = [ + {"document": furl(doc.url).set({"versie": doc.versie}).url} + for doc in docs + ] + # pass to serializer serializer = self.serializer_class(data=data) serializer.is_valid(raise_exception=True) diff --git a/backend/src/zac/contrib/objects/kownsl/tests/test_review_views.py b/backend/src/zac/contrib/objects/kownsl/tests/test_review_views.py index 88b8bd255..52fe97197 100644 --- a/backend/src/zac/contrib/objects/kownsl/tests/test_review_views.py +++ b/backend/src/zac/contrib/objects/kownsl/tests/test_review_views.py @@ -133,6 +133,7 @@ def setUpTestData(cls): informatieobjecttype=cls.informatieobjecttype["url"], vertrouwelijkheidaanduiding=VertrouwelijkheidsAanduidingen.openbaar, bestandsomvang=10, + versie=1, ) # Dict to models @@ -391,7 +392,10 @@ def test_success_create_approval_review_request(self, m): mock_service_oas_get(m, ZAKEN_ROOT, "zrc") mock_resource_get(m, self.zaak_json) - rr = factory_review_request(self.review_request) + _rr = deepcopy(self.review_request) + _rr["documents"] = [self.document.url] + + rr = factory_review_request(_rr) rr.fetched_reviews = True rr.reviews = [] rr.review_type = KownslTypes.approval @@ -402,12 +406,6 @@ def test_success_create_approval_review_request(self, m): payload = { "approved": True, "toelichting": "some toelichting here", - "group": "", - "reviewDocuments": [ - { - "document": self.document.url + "?versie=1", - } - ], "zaakeigenschappen": [ { "url": self.zaakeigenschap.url, @@ -421,19 +419,23 @@ def test_success_create_approval_review_request(self, m): reviews_object = deepcopy(REVIEW_OBJECT) reviews_object["record"]["data"] = reviews - with patch( - "zac.contrib.objects.kownsl.api.views.get_review_request", return_value=rr - ): - with patch("zac.contrib.objects.services.fetch_reviews", return_value=None): + with self.search_informatieobjects_patcher: + with patch( + "zac.contrib.objects.kownsl.api.views.get_review_request", + return_value=rr, + ): with patch( - "zac.contrib.objects.services._create_unique_uuid_for_object", - return_value=reviews["id"], + "zac.contrib.objects.services.fetch_reviews", return_value=None ): with patch( - "zac.contrib.objects.services.create_meta_object_and_relate_to_zaak", - return_value=reviews_object, - ) as mock_create_object: - response = self.client.post(url, payload) + "zac.contrib.objects.services._create_unique_uuid_for_object", + return_value=reviews["id"], + ): + with patch( + "zac.contrib.objects.services.create_meta_object_and_relate_to_zaak", + return_value=reviews_object, + ) as mock_create_object: + response = self.client.post(url, payload) self.assertEqual(response.status_code, 204) mock_create_object.assert_called_once_with( @@ -452,7 +454,6 @@ def test_success_create_approval_review_request(self, m): "username": self.user.username, }, "created": "2022-04-14T15:51:09.830235Z", - "group": dict(), "approved": payload["approved"], "review_documents": [ { @@ -461,6 +462,7 @@ def test_success_create_approval_review_request(self, m): "source_version": 1, } ], + "group": None, "toelichting": payload["toelichting"], "zaakeigenschappen": [ { From b221494022edada3701d50df3dac1595b740494e Mon Sep 17 00:00:00 2001 From: damm89 Date: Thu, 22 Feb 2024 14:59:57 +0100 Subject: [PATCH 6/7] :bug: Allow null on group of review --- .../contrib/objects/kownsl/api/serializers.py | 16 ++-------------- .../objects/kownsl/tests/test_review_views.py | 6 +++--- 2 files changed, 5 insertions(+), 17 deletions(-) diff --git a/backend/src/zac/contrib/objects/kownsl/api/serializers.py b/backend/src/zac/contrib/objects/kownsl/api/serializers.py index dc8c9a4a8..782d25934 100644 --- a/backend/src/zac/contrib/objects/kownsl/api/serializers.py +++ b/backend/src/zac/contrib/objects/kownsl/api/serializers.py @@ -208,7 +208,6 @@ class ApprovalSerializer(APIModelSerializer): group = KownslGroupSerializerSlugRelatedField( slug_field="name", queryset=Group.objects.all(), - required=False, help_text=_("`name` of the group that author answered for."), allow_null=True, allow_empty=True, @@ -251,7 +250,6 @@ class AdviceSerializer(APIModelSerializer): group = KownslGroupSerializerSlugRelatedField( slug_field="name", queryset=Group.objects.all(), - required=False, help_text=_("`name` of the group that author answered for."), allow_null=True, allow_empty=True, @@ -422,9 +420,9 @@ class SubmitApprovalSerializer(APIModelSerializer): group = KownslGroupSerializerSlugRelatedField( slug_field="name", queryset=Group.objects.all(), - required=False, help_text=_("`name` of the group that author answered for."), allow_null=True, + default=None, ) review_documents = SubmitReviewDocumentSerializer( label=_("review documents"), @@ -450,11 +448,6 @@ class Meta: "toelichting": {"required": False}, } - def validate_group(self, group): - if not group: - return "" - return group - def validate(self, data): if not data.get("zaakeigenschappen") and not data.get("review_documents"): raise serializers.ValidationError( @@ -480,7 +473,7 @@ class SubmitAdviceSerializer(APIModelSerializer): group = KownslGroupSerializerSlugRelatedField( slug_field="name", queryset=Group.objects.all(), - required=False, + default=None, help_text=_("`name` of the group that author answered for."), allow_null=True, ) @@ -506,11 +499,6 @@ class Meta: "advice": {"help_text": _("Advice given for review request.")}, } - def validate_group(self, group): - if not group: - return "" - return group - def validate(self, data): if not data.get("zaakeigenschappen") and not data.get("review_documents"): raise serializers.ValidationError( diff --git a/backend/src/zac/contrib/objects/kownsl/tests/test_review_views.py b/backend/src/zac/contrib/objects/kownsl/tests/test_review_views.py index 52fe97197..11cdf9c13 100644 --- a/backend/src/zac/contrib/objects/kownsl/tests/test_review_views.py +++ b/backend/src/zac/contrib/objects/kownsl/tests/test_review_views.py @@ -545,7 +545,7 @@ def test_success_create_advice_review_request(self, m): "username": self.user.username, }, "created": "2022-04-14T15:51:09.830235Z", - "group": dict(), + "group": None, "review_documents": [ { "document": self.document.url + "?versie=1", @@ -661,7 +661,7 @@ def test_success_create_successive_advice_review_request(self, m): "username": user.username, }, "created": "2022-04-14T15:51:09.830235Z", - "group": dict(), + "group": None, "reviewDocuments": [ { "document": self.document.url + "?versie=2", @@ -726,7 +726,7 @@ def test_success_create_successive_advice_review_request(self, m): "username": user.username, }, "created": "2022-04-14T15:51:09.830235Z", - "group": dict(), + "group": None, "reviewDocuments": [ { "document": self.document.url + "?versie=2", From d0b8fd06fd4f6fd2b02ca747bbbbe02c524a5ebe Mon Sep 17 00:00:00 2001 From: Kelvin Chan Date: Thu, 22 Feb 2024 15:29:04 +0100 Subject: [PATCH 7/7] Fix FE kownsl --- .../src/lib/advice/advice.component.html | 42 +++++++++++------- .../kownsl/src/lib/advice/advice.component.ts | 20 ++++++--- .../src/lib/approval/approval.component.html | 43 ++++++++++++------- .../src/lib/approval/approval.component.ts | 9 +++- .../features/kownsl/src/models/advice-form.ts | 9 +++- .../kownsl/src/models/approval-form.ts | 5 +++ .../kownsl/src/models/review-request.ts | 4 +- 7 files changed, 90 insertions(+), 42 deletions(-) diff --git a/frontend/zac-ui/libs/features/kownsl/src/lib/advice/advice.component.html b/frontend/zac-ui/libs/features/kownsl/src/lib/advice/advice.component.html index 22a1ccc52..e901d8b74 100644 --- a/frontend/zac-ui/libs/features/kownsl/src/lib/advice/advice.component.html +++ b/frontend/zac-ui/libs/features/kownsl/src/lib/advice/advice.component.html @@ -20,18 +20,18 @@

Advies gevraagd

{{zaakData.zaaktype.omschrijving}}

-

Zaakomschrijving:

+

Zaakomschrijving:

-

{{zaakData.omschrijving}}

+

{{zaakData.omschrijving}}

-

Zaaktoelichting:

+

Zaaktoelichting:

-

{{zaakData.toelichting}}

+

{{zaakData.toelichting}}

@@ -41,37 +41,48 @@

{{zaakData.zaaktype.omschrijving}}

-

Zaak:

+

Zaak:

- +
+
+

Zaakinformatie:

+
+
+

+ {{eigenschap.eigenschap.naam}}: {{eigenschap.waarde}} +

+
+
+
-

Aanvraag ingediend op:

+

Aanvraag ingediend op:

-

{{adviceData.created | date:'fullDate'}}

+

{{adviceData.created | date:'fullDate'}}

-

Ingediend door:

+

Ingediend door:

-

{{requester}}

+

{{requester}}

-

Toelichting aanvraag:

+

Toelichting aanvraag:

-

+

{{adviceData.toelichting}}

@@ -82,7 +93,7 @@

{{zaakData.zaaktype.omschrijving}}

-

Vorige adviezen

+

Vorige adviezen

@@ -95,7 +106,7 @@

Vorige adviezen

-

Documenten

+

Documenten

@@ -105,10 +116,11 @@

Documenten

- (maximaal 1000 karakters) + (maximaal 1000 karakters) diff --git a/frontend/zac-ui/libs/features/kownsl/src/lib/advice/advice.component.ts b/frontend/zac-ui/libs/features/kownsl/src/lib/advice/advice.component.ts index a701fbdc8..f65ed0c1e 100644 --- a/frontend/zac-ui/libs/features/kownsl/src/lib/advice/advice.component.ts +++ b/frontend/zac-ui/libs/features/kownsl/src/lib/advice/advice.component.ts @@ -39,12 +39,13 @@ export class AdviceComponent implements OnInit { tableData: Table = new Table(['Adviseur', 'Gedaan op'], []); - documentTableData: Table = new Table(['Acties', '', 'Documentnaam'], []); + documentTableData: Table = new Table(['Bestandsnaam', 'Acties', ''], []); adviceForm: FormGroup; adviceFormData: AdviceForm = { advice: "", - adviceDocuments: [], + reviewDocuments: [], + zaakeigenschappen: [] }; docsInEditMode: string[] = []; @@ -169,6 +170,7 @@ export class AdviceComponent implements OnInit { const docName = document.bestandsnaam; const rowData: RowData = { cellData: { + docName: docName, lezen: { type: 'button', label: 'Lezen', @@ -178,8 +180,7 @@ export class AdviceComponent implements OnInit { type: 'button', label: 'Bewerken', value: document.identificatie - }, - docName: docName + } } } return rowData @@ -243,12 +244,19 @@ export class AdviceComponent implements OnInit { this.adviceFormData.advice = this.adviceForm.controls['advice'].value; + this.adviceFormData.zaakeigenschappen = this.adviceData.zaakeigenschappen.map(eigenschap => { + return { + url: eigenschap.url, + naam: eigenschap.eigenschap.naam, + waarde: eigenschap.waarde, + } + }) this.adviceService.closeDocumentEdit(this.deleteUrls) .pipe( switchMap( (closedDocs: CloseDocument[]) => { if (closedDocs.length > 0) { - this.adviceFormData.adviceDocuments = closedDocs.map( (doc, i) => { + this.adviceFormData.reviewDocuments = closedDocs.map( (doc, i) => { return { document: this.deleteUrls[i].drcUrl, editedDocument: doc.versionedUrl @@ -263,7 +271,7 @@ export class AdviceComponent implements OnInit { return of(null) } }), - switchMap((formData: AdviceForm) => { + switchMap(() => { return this.adviceService.postAdvice(this.adviceFormData, this.uuid, this.assignee) }) ).subscribe( () => { diff --git a/frontend/zac-ui/libs/features/kownsl/src/lib/approval/approval.component.html b/frontend/zac-ui/libs/features/kownsl/src/lib/approval/approval.component.html index 12b4ee91a..21a972728 100644 --- a/frontend/zac-ui/libs/features/kownsl/src/lib/approval/approval.component.html +++ b/frontend/zac-ui/libs/features/kownsl/src/lib/approval/approval.component.html @@ -20,18 +20,18 @@

Accordering gevraagd

{{zaakData.zaaktype.omschrijving}}

-

Zaakomschrijving:

+

Zaakomschrijving:

-

{{zaakData.omschrijving}}

+

{{zaakData.omschrijving}}

-

Zaaktoelichting:

+

Zaaktoelichting:

-

{{zaakData.toelichting}}

+

{{zaakData.toelichting}}

@@ -41,37 +41,48 @@

{{zaakData.zaaktype.omschrijving}}

-

Zaak:

+

Zaak:

+
+
+

Zaakinformatie:

+
+
+

+ {{eigenschap.eigenschap.naam}}: {{eigenschap.waarde}} +

+
+
+
-

Aanvraag ingediend op:

+

Aanvraag ingediend op:

-

{{approvalData.created | date:'fullDate'}}

+

{{approvalData.created | date:'fullDate'}}

-

Ingediend door:

+

Ingediend door:

-

{{requester}}

+

{{requester}}

-

Toelichting aanvraag:

+

Toelichting aanvraag:

-

+

{{approvalData.toelichting}}

@@ -82,7 +93,7 @@

{{zaakData.zaaktype.omschrijving}}

-

Vorige accorderingen

+

Vorige accorderingen

@@ -93,7 +104,7 @@

Vorige accorderingen

-

Documenten

+

Documenten

Documenten
- +
-
+
- +
diff --git a/frontend/zac-ui/libs/features/kownsl/src/lib/approval/approval.component.ts b/frontend/zac-ui/libs/features/kownsl/src/lib/approval/approval.component.ts index 6b8f2ab0f..71c6cf897 100644 --- a/frontend/zac-ui/libs/features/kownsl/src/lib/approval/approval.component.ts +++ b/frontend/zac-ui/libs/features/kownsl/src/lib/approval/approval.component.ts @@ -154,7 +154,14 @@ export class ApprovalComponent implements OnInit { submitForm(): void { const formData: ApprovalForm = { approved: this.approvalForm.controls['approved'].value, - toelichting: this.approvalForm.controls['toelichting'].value + toelichting: this.approvalForm.controls['toelichting'].value, + zaakeigenschappen: this.approvalData.zaakeigenschappen.map(eigenschap => { + return { + url: eigenschap.url, + naam: eigenschap.eigenschap.naam, + waarde: eigenschap.waarde, + } + }) } this.postApproval(formData); } diff --git a/frontend/zac-ui/libs/features/kownsl/src/models/advice-form.ts b/frontend/zac-ui/libs/features/kownsl/src/models/advice-form.ts index 736a78b0f..d0cf8606c 100644 --- a/frontend/zac-ui/libs/features/kownsl/src/models/advice-form.ts +++ b/frontend/zac-ui/libs/features/kownsl/src/models/advice-form.ts @@ -1,9 +1,14 @@ export interface AdviceForm { advice: string; - adviceDocuments: AdviceDocument[] | []; + reviewDocuments: ReviewDocument[] | []; + zaakeigenschappen: { + url: string, + naam: string, + waarde: string + }[] } -export interface AdviceDocument { +export interface ReviewDocument { document: string; editedDocument: string; } diff --git a/frontend/zac-ui/libs/features/kownsl/src/models/approval-form.ts b/frontend/zac-ui/libs/features/kownsl/src/models/approval-form.ts index bac50451a..ad2cb3949 100644 --- a/frontend/zac-ui/libs/features/kownsl/src/models/approval-form.ts +++ b/frontend/zac-ui/libs/features/kownsl/src/models/approval-form.ts @@ -1,4 +1,9 @@ export interface ApprovalForm { approved: boolean | string; toelichting: string; + zaakeigenschappen: { + url: string, + naam: string, + waarde: string + }[] } diff --git a/frontend/zac-ui/libs/features/kownsl/src/models/review-request.ts b/frontend/zac-ui/libs/features/kownsl/src/models/review-request.ts index 7893ec2b9..78542631e 100644 --- a/frontend/zac-ui/libs/features/kownsl/src/models/review-request.ts +++ b/frontend/zac-ui/libs/features/kownsl/src/models/review-request.ts @@ -2,7 +2,7 @@ import { Review } from './review'; import { Zaak } from './zaak'; import {Approval} from "./approval"; import {Advice} from "./advice"; -import {Document, User, UserGroupDetail} from '@gu/models'; +import { Document, EigenschapWaarde, User, UserGroupDetail, ZaaktypeEigenschap } from '@gu/models'; export interface Metadata { taskDefinitionId: string; @@ -28,7 +28,7 @@ export interface ReviewRequest { metadata: Metadata; zaak: Zaak; zaakDocuments: Document[]; - zaakeigenschappen: any[]; + zaakeigenschappen: EigenschapWaarde[]; approvals?: Review[]; advices?: Review[]; }