diff --git a/src/open_inwoner/cms/cases/views/cases.py b/src/open_inwoner/cms/cases/views/cases.py index 6321d21a2d..4c8f3d686d 100644 --- a/src/open_inwoner/cms/cases/views/cases.py +++ b/src/open_inwoner/cms/cases/views/cases.py @@ -14,9 +14,8 @@ from zgw_consumers.concurrent import parallel from open_inwoner.htmx.mixins import RequiresHtmxMixin -from open_inwoner.openzaak.api_models import Zaak +from open_inwoner.openzaak.api_models import OpenSubmission, Zaak from open_inwoner.openzaak.cases import preprocess_data -from open_inwoner.openzaak.formapi import fetch_open_submissions from open_inwoner.openzaak.models import OpenZaakConfig, ZGWApiGroupConfig from open_inwoner.openzaak.types import UniformCase from open_inwoner.openzaak.utils import get_user_fetch_parameters @@ -47,17 +46,37 @@ def process_data(self) -> dict: return {**self.zaak.process_data(), "api_group": self.api_group} +@dataclass(frozen=True) +class SubmissionWithApiGroup: + submission: OpenSubmission + api_group: ZGWApiGroupConfig + + @property + def identifier(self): + return self.submission.url + + def process_data(self) -> dict: + return {**self.submission.process_data(), "api_group": self.api_group} + + class CaseListService: def __init__(self, request: HttpRequest): self.request = request - def get_cases_for_api_group(self, group: ZGWApiGroupConfig): + def get_cases_for_api_group(self, group: ZGWApiGroupConfig) -> list[UniformCase]: raw_cases = group.zaken_client.fetch_cases( - **get_user_fetch_parameters(self.request) + **get_user_fetch_parameters(self.request, check_rsin=True) ) preprocessed_cases = preprocess_data(raw_cases, group) return preprocessed_cases + def get_submissions_for_api_group( + self, group: ZGWApiGroupConfig + ) -> list[UniformCase]: + return group.forms_client.fetch_open_submissions( + **get_user_fetch_parameters(self.request, check_rsin=False) + ) + def get_cases(self) -> list[ZaakWithApiGroup]: all_api_groups = list(ZGWApiGroupConfig.objects.all()) @@ -84,10 +103,35 @@ def get_cases(self) -> list[ZaakWithApiGroup]: return cases_with_api_group def get_submissions(self): - subs = fetch_open_submissions(self.request.user.bsn) - subs.sort(key=lambda sub: sub.datum_laatste_wijziging, reverse=True) + all_api_groups = list( + ZGWApiGroupConfig.objects.exclude(form_service__isnull=True) + ) + + with parallel() as executor: + futures = [ + executor.submit(self.get_submissions_for_api_group, group) + for group in all_api_groups + ] + + subs_with_api_group = [] + for task in concurrent.futures.as_completed(futures): + try: + group_for_task = all_api_groups[futures.index(task)] + for row in task.result(): + subs_with_api_group.append( + SubmissionWithApiGroup( + submission=row, api_group=group_for_task + ) + ) + except BaseException: + logger.exception("Error fetching and pre-processing cases") + + # Sort submissions by date modified + subs_with_api_group.sort( + key=lambda sub: sub.submission.datum_laatste_wijziging, reverse=True + ) - return subs + return subs_with_api_group @staticmethod def get_case_filter_status(zaak: Zaak) -> CaseFilterFormOption: diff --git a/src/open_inwoner/openzaak/clients.py b/src/open_inwoner/openzaak/clients.py index 35497a126b..b727826c6b 100644 --- a/src/open_inwoner/openzaak/clients.py +++ b/src/open_inwoner/openzaak/clients.py @@ -63,21 +63,25 @@ class ZakenClient(ZgwAPIClient): def fetch_cases( self, user_bsn: str | None = None, - user_kvk_or_rsin: str | None = None, + user_kvk: str | None = None, + user_rsin: str | None = None, max_requests: int = 4, identificatie: str | None = None, vestigingsnummer: str | None = None, ): - if user_bsn and (user_kvk_or_rsin or vestigingsnummer): + if user_bsn and (user_kvk or user_rsin or vestigingsnummer): raise ValueError( - "either `user_bsn` or `user_kvk_or_rsin`/`vestigingsnummer` should be supplied, not both" + "either `user_bsn` or `user_kvk`/`user_risin` (+ optionally `vestigingsnummer`) " + "should be supplied, not both" ) if user_bsn: return self.fetch_cases_by_bsn( user_bsn, max_requests=max_requests, identificatie=identificatie ) - elif user_kvk_or_rsin: + + if user_kvk or user_rsin: + user_kvk_or_rsin = user_rsin if user_rsin else user_kvk return self.fetch_cases_by_kvk_or_rsin( user_kvk_or_rsin, max_requests=max_requests, @@ -666,17 +670,71 @@ def upload_document( class FormClient(ZgwAPIClient): - def fetch_open_submissions(self, bsn: str) -> list[OpenSubmission]: - if not bsn: + def fetch_open_submissions( + self, + user_bsn: str | None = None, + user_kvk: str | None = None, + vestigingsnummer: str | None = None, + max_requests: int = 4, + **kwargs, + ): + if user_bsn and (user_kvk or vestigingsnummer): + raise ValueError( + "either `user_bsn` or `user_kvk` (optionally with `vestigingsnummer`) " + "should be supplied, not both" + ) + + if user_bsn: + return self.fetch_open_submissions_by_bsn( + user_bsn, max_requests=max_requests + ) + + if user_kvk: + return self.fetch_open_submissions_by_kvk( + user_kvk, + max_requests=max_requests, + vestigingsnummer=vestigingsnummer, + ) + + return [] + + def fetch_open_submissions_by_bsn( + self, + user_bsn: str, + max_requests: int, + ) -> list[OpenSubmission]: + try: + response = self.get( + "openstaande-inzendingen", + params={"bsn": user_bsn}, + ) + data = get_json_response(response) + all_data = list(pagination_helper(self, data, max_requests=max_requests)) + except (RequestException, ClientError) as e: + logger.exception("exception while making request", exc_info=e) return [] + results = factory(OpenSubmission, all_data) + + return results + + def fetch_open_submissions_by_kvk( + self, + user_kvk: str, + vestigingsnummer: str | None, + max_requests: int, + ) -> list[OpenSubmission]: + request_params = {"kvk": user_kvk} + if vestigingsnummer: + request_params["vestigingsnummer"] = vestigingsnummer + try: response = self.get( "openstaande-inzendingen", - params={"bsn": bsn}, + params=request_params, ) data = get_json_response(response) - all_data = list(pagination_helper(self, data)) + all_data = list(pagination_helper(self, data, max_requests=max_requests)) except (RequestException, ClientError) as e: logger.exception("exception while making request", exc_info=e) return [] diff --git a/src/open_inwoner/openzaak/formapi.py b/src/open_inwoner/openzaak/formapi.py deleted file mode 100644 index 4be12469cb..0000000000 --- a/src/open_inwoner/openzaak/formapi.py +++ /dev/null @@ -1,16 +0,0 @@ -import logging - -from open_inwoner.openzaak.clients import build_forms_client - -from .api_models import OpenSubmission - -logger = logging.getLogger(__name__) - - -def fetch_open_submissions(bsn: str) -> list[OpenSubmission]: - client = build_forms_client() - - if client is None: - return [] - - return client.fetch_open_submissions(bsn) diff --git a/src/open_inwoner/openzaak/tests/mocks.py b/src/open_inwoner/openzaak/tests/mocks.py index f4fecdee94..4cddacfa77 100644 --- a/src/open_inwoner/openzaak/tests/mocks.py +++ b/src/open_inwoner/openzaak/tests/mocks.py @@ -1,8 +1,17 @@ +from furl import furl +from zgw_consumers.api_models.constants import VertrouwelijkheidsAanduidingen + +from open_inwoner.accounts.models import User from open_inwoner.openzaak.tests.shared import FORMS_ROOT +from open_inwoner.utils.test import paginated_response class ESuiteSubmissionData: - def __init__(self): + def __init__(self, *, zaken_root: str, forms_root: str, user: User): + self.zaken_root = zaken_root + self.forms_root = forms_root + self.user = user + self.submission_1 = { "url": "https://dmidoffice2.esuite-development.net/formulieren-provider/api/v1/8e3ae29c-7bc5-4f7d-a27c-b0c83c13328e", "uuid": "8e3ae29c-7bc5-4f7d-a27c-b0c83c13328e", @@ -19,18 +28,64 @@ def __init__(self): "datumLaatsteWijziging": "2023-02-13T14:10:26.197000+0100", "eindDatumGeldigheid": "2023-05-14T14:10:26.197+02:00", } - # note this is a weird esuite response without pagination links - self.response = { - "count": 2, - "results": [ - self.submission_1, - self.submission_2, - ], + self.submission_3 = { + "url": "https://dmidoffice2.esuite-development.net/formulieren-provider/api/v1/e25769c1-dcb4-4d3c-a61c-fd7d0c78f296", + "uuid": "e25769c1-dcb4-4d3c-a61c-fd7d0c78f296", + "naam": "Indienen bezwaarschrift", + "vervolgLink": "https://dloket2.esuite-development.net/formulieren-nieuw/formulier/start/e25769c1-dcb4-4d3c-a61c-fd7d0c78f296", + "datumLaatsteWijziging": "2023-04-05T14:10:26.197000+0100", + "eindDatumGeldigheid": "2023-06-14T14:10:26.197+02:00", } - def install_mocks(self, m): + # esuite response without pagination links + if self.forms_root == FORMS_ROOT: + self.response = { + "count": 2, + "results": [ + self.submission_1, + self.submission_2, + ], + } + else: + self.response = { + "count": 1, + "results": [ + self.submission_3, + ], + } + + def install_digid_mocks(self, m): + m.get( + furl(f"{self.zaken_root}zaken") + .add( + { + "rol__betrokkeneIdentificatie__natuurlijkPersoon__inpBsn": self.user.bsn, + "maximaleVertrouwelijkheidaanduiding": VertrouwelijkheidsAanduidingen.openbaar, + } + ) + .url, + json=paginated_response([]), + ) + m.get( + f"{self.forms_root}openstaande-inzendingen", + json=self.response, + ) + return self + + def install_eherkenning_mocks(self, m): + m.get( + furl(f"{self.zaken_root}zaken") + .add( + { + "rol__betrokkeneIdentificatie__nietNatuurlijkPersoon__innNnpId": self.user.kvk, + "maximaleVertrouwelijkheidaanduiding": VertrouwelijkheidsAanduidingen.openbaar, + } + ) + .url, + json=paginated_response([]), + ) m.get( - f"{FORMS_ROOT}openstaande-inzendingen", + f"{self.forms_root}openstaande-inzendingen", json=self.response, ) return self diff --git a/src/open_inwoner/openzaak/tests/test_cases.py b/src/open_inwoner/openzaak/tests/test_cases.py index 936fa28d67..3328bbcc35 100644 --- a/src/open_inwoner/openzaak/tests/test_cases.py +++ b/src/open_inwoner/openzaak/tests/test_cases.py @@ -21,7 +21,6 @@ from open_inwoner.accounts.choices import LoginTypeChoices from open_inwoner.accounts.tests.factories import UserFactory, eHerkenningUserFactory from open_inwoner.cms.cases.views.cases import CaseFilterFormOption, InnerCaseListView -from open_inwoner.openzaak.tests.shared import FORMS_ROOT from open_inwoner.utils.test import ( ClearCachesMixin, paginated_response, @@ -41,7 +40,14 @@ ) from .helpers import generate_oas_component_cached from .mocks import ESuiteSubmissionData -from .shared import ANOTHER_CATALOGI_ROOT, ANOTHER_ZAKEN_ROOT, CATALOGI_ROOT, ZAKEN_ROOT +from .shared import ( + ANOTHER_CATALOGI_ROOT, + ANOTHER_FORMS_ROOT, + ANOTHER_ZAKEN_ROOT, + CATALOGI_ROOT, + FORMS_ROOT, + ZAKEN_ROOT, +) class SeededUUIDGenerator: @@ -1103,25 +1109,62 @@ def setUp(self): zrc_service__api_root=ZAKEN_ROOT, form_service__api_root=FORMS_ROOT, ) + ZGWApiGroupConfigFactory( + zrc_service__api_root=ANOTHER_ZAKEN_ROOT, + form_service__api_root=ANOTHER_FORMS_ROOT, + ) @requests_mock.Mocker() - def test_case_submission(self, m): - user = UserFactory( + def test_get_open_submissions_by_bsn(self, m): + digid_user = UserFactory( login_type=LoginTypeChoices.digid, bsn="900222086", email="john@smith.nl" ) - m.get( - furl(f"{ZAKEN_ROOT}zaken") - .add( - { - "rol__betrokkeneIdentificatie__natuurlijkPersoon__inpBsn": user.bsn, - "maximaleVertrouwelijkheidaanduiding": VertrouwelijkheidsAanduidingen.openbaar, - } - ) - .url, - json=paginated_response([]), + data = ESuiteSubmissionData( + zaken_root=ZAKEN_ROOT, forms_root=FORMS_ROOT, user=digid_user + ).install_digid_mocks(m) + data_alt = ESuiteSubmissionData( + zaken_root=ANOTHER_ZAKEN_ROOT, + forms_root=ANOTHER_FORMS_ROOT, + user=digid_user, + ).install_digid_mocks(m) + + response = self.app.get( + self.inner_url, user=digid_user, headers={"HX-Request": "true"} + ) + + cases = response.context["cases"] + + self.assertEqual(len(cases), 3) + + # submission cases are sorted in reverse by `last modified` + self.assertEqual(cases[0]["url"], data_alt.submission_3["url"]) + self.assertEqual(cases[0]["uuid"], data_alt.submission_3["uuid"]) + self.assertEqual(cases[0]["naam"], data_alt.submission_3["naam"]) + self.assertEqual(cases[0]["vervolg_link"], data_alt.submission_3["vervolgLink"]) + self.assertEqual( + cases[0]["datum_laatste_wijziging"].strftime("%Y-%m-%dT%H:%M:%S.%f%z"), + data_alt.submission_3["datumLaatsteWijziging"], + ) + + self.assertEqual(cases[1]["url"], data.submission_2["url"]) + self.assertEqual(cases[1]["uuid"], data.submission_2["uuid"]) + self.assertEqual(cases[1]["naam"], data.submission_2["naam"]) + self.assertEqual(cases[1]["vervolg_link"], data.submission_2["vervolgLink"]) + self.assertEqual( + cases[1]["datum_laatste_wijziging"].strftime("%Y-%m-%dT%H:%M:%S.%f%z"), + data.submission_2["datumLaatsteWijziging"], ) - data = ESuiteSubmissionData().install_mocks(m) + @requests_mock.Mocker() + @patch("open_inwoner.kvk.middleware.kvk_branch_selected_done") + def test_get_open_submissions_by_kvk(self, m, kvk_branch_selected): + user = UserFactory(login_type=LoginTypeChoices.eherkenning, kvk="68750110") + data = ESuiteSubmissionData( + zaken_root=ZAKEN_ROOT, forms_root=FORMS_ROOT, user=user + ).install_eherkenning_mocks(m) + data_alt = ESuiteSubmissionData( + zaken_root=ANOTHER_ZAKEN_ROOT, forms_root=ANOTHER_FORMS_ROOT, user=user + ).install_eherkenning_mocks(m) response = self.app.get( self.inner_url, user=user, headers={"HX-Request": "true"} @@ -1129,14 +1172,23 @@ def test_case_submission(self, m): cases = response.context["cases"] - self.assertEqual(len(cases), 2) + self.assertEqual(len(cases), 3) # submission cases are sorted in reverse by `last modified` - self.assertEqual(cases[0]["url"], data.submission_2["url"]) - self.assertEqual(cases[0]["uuid"], data.submission_2["uuid"]) - self.assertEqual(cases[0]["naam"], data.submission_2["naam"]) - self.assertEqual(cases[0]["vervolg_link"], data.submission_2["vervolgLink"]) + self.assertEqual(cases[0]["url"], data_alt.submission_3["url"]) + self.assertEqual(cases[0]["uuid"], data_alt.submission_3["uuid"]) + self.assertEqual(cases[0]["naam"], data_alt.submission_3["naam"]) + self.assertEqual(cases[0]["vervolg_link"], data_alt.submission_3["vervolgLink"]) self.assertEqual( cases[0]["datum_laatste_wijziging"].strftime("%Y-%m-%dT%H:%M:%S.%f%z"), + data_alt.submission_3["datumLaatsteWijziging"], + ) + + self.assertEqual(cases[1]["url"], data.submission_2["url"]) + self.assertEqual(cases[1]["uuid"], data.submission_2["uuid"]) + self.assertEqual(cases[1]["naam"], data.submission_2["naam"]) + self.assertEqual(cases[1]["vervolg_link"], data.submission_2["vervolgLink"]) + self.assertEqual( + cases[1]["datum_laatste_wijziging"].strftime("%Y-%m-%dT%H:%M:%S.%f%z"), data.submission_2["datumLaatsteWijziging"], ) diff --git a/src/open_inwoner/openzaak/utils.py b/src/open_inwoner/openzaak/utils.py index 9c484572d5..e798fb11b6 100644 --- a/src/open_inwoner/openzaak/utils.py +++ b/src/open_inwoner/openzaak/utils.py @@ -126,7 +126,7 @@ def get_zaak_type_info_object_type_config( return None -def get_user_fetch_parameters(request) -> dict: +def get_user_fetch_parameters(request, check_rsin: bool = True) -> dict: """ Determine the parameters used to perform ZGW resource fetches """ @@ -137,15 +137,19 @@ def get_user_fetch_parameters(request) -> dict: if user.bsn: return {"user_bsn": user.bsn} - elif user.kvk: - kvk_or_rsin = user.kvk - config = OpenZaakConfig.get_solo() - if config.fetch_eherkenning_zaken_with_rsin: - kvk_or_rsin = user.rsin - parameters = {"user_kvk_or_rsin": kvk_or_rsin} + if user.kvk: + parameters = {"user_kvk": user.kvk} + + if check_rsin: + config = OpenZaakConfig.get_solo() + if config.fetch_eherkenning_zaken_with_rsin: + parameters = {"user_rsin": user.rsin} + vestigingsnummer = get_kvk_branch_number(request.session) if vestigingsnummer: parameters.update({"vestigingsnummer": vestigingsnummer}) + return parameters + return {}