diff --git a/requirements/base.txt b/requirements/base.txt index 782c8f11a9..ae5f6cb3c3 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -161,7 +161,7 @@ django-csp==3.7 # via -r requirements/base.in django-csp-reports==1.8.1 # via -r requirements/base.in -django-digid-eherkenning==0.7.0 +django-digid-eherkenning==0.9.0 # via -r requirements/base.in django-elasticsearch-dsl==7.2.1 # via -r requirements/base.in diff --git a/requirements/ci.txt b/requirements/ci.txt index c4a2185bad..2c0c11c304 100644 --- a/requirements/ci.txt +++ b/requirements/ci.txt @@ -249,7 +249,7 @@ django-csp-reports==1.8.1 # via # -c requirements/base.txt # -r requirements/base.txt -django-digid-eherkenning==0.7.0 +django-digid-eherkenning==0.9.0 # via # -c requirements/base.txt # -r requirements/base.txt diff --git a/requirements/dev.txt b/requirements/dev.txt index 70c4d5322b..da46fb6976 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -276,7 +276,7 @@ django-csp-reports==1.8.1 # -r requirements/ci.txt django-debug-toolbar==3.2.2 # via -r requirements/dev.in -django-digid-eherkenning==0.7.0 +django-digid-eherkenning==0.9.0 # via # -c requirements/ci.txt # -r requirements/ci.txt diff --git a/src/open_inwoner/accounts/tests/test_auth.py b/src/open_inwoner/accounts/tests/test_auth.py index 76823b2954..518b054f4f 100644 --- a/src/open_inwoner/accounts/tests/test_auth.py +++ b/src/open_inwoner/accounts/tests/test_auth.py @@ -1,4 +1,5 @@ from datetime import date +from unittest.mock import patch from urllib.parse import urlencode from django.contrib.sites.models import Site @@ -68,7 +69,11 @@ def test_registration_page_only_digid_with_invite(self): furl(reverse("digid:login")).add({"next": necessary_url}).url, ) - def test_digid_fail_without_invite_redirects_to_login_page(self): + @patch("digid_eherkenning.validators.Proef11ValidatorBase.__call__") + def test_digid_fail_without_invite_redirects_to_login_page(self, m): + # disable mock form validation to check redirect + m.return_value = True + self.assertNotIn("invite_url", self.client.session.keys()) url = reverse("digid-mock:password") @@ -87,7 +92,11 @@ def test_digid_fail_without_invite_redirects_to_login_page(self): self.assertRedirectsLogin(response, with_host=True) - def test_digid_fail_without_invite_and_next_url_redirects_to_login_page(self): + @patch("digid_eherkenning.validators.Proef11ValidatorBase.__call__") + def test_digid_fail_without_invite_and_next_url_redirects_to_login_page(self, m): + # disable mock form validation to check redirect + m.return_value = True + self.assertNotIn("invite_url", self.client.session.keys()) url = reverse("digid-mock:password") @@ -106,7 +115,10 @@ def test_digid_fail_without_invite_and_next_url_redirects_to_login_page(self): self.assertRedirectsLogin(response, with_host=True) - def test_digid_fail_with_invite_redirects_to_register_page(self): + @patch("digid_eherkenning.validators.Proef11ValidatorBase.__call__") + def test_digid_fail_with_invite_redirects_to_register_page(self, m): + # disable mock form validation to check redirect + m.return_value = True invite = InviteFactory() session = self.client.session session[ @@ -149,7 +161,7 @@ def test_invite_url_not_in_session_after_successful_login(self): url = f"{url}?{urlencode(params)}" data = { - "auth_name": "123456789", + "auth_name": "533458225", "auth_pass": "bar", } @@ -175,7 +187,7 @@ def test_user_can_modify_only_email_when_digid_and_brp(self, m): "next": reverse("profile:registration_necessary"), } data = { - "auth_name": "123456789", + "auth_name": "533458225", "auth_pass": "bar", } url = f"{url}?{urlencode(params)}" @@ -223,7 +235,7 @@ def test_partial_response_from_haalcentraal_when_digid_and_brp(self, m): "next": reverse("profile:registration_necessary"), } data = { - "auth_name": "123456789", + "auth_name": "533458225", "auth_pass": "bar", } url = f"{url}?{urlencode(params)}" @@ -260,7 +272,7 @@ def test_first_digid_login_updates_brp_fields(self, m): url = f"{url}?{urlencode(params)}" data = { - "auth_name": "123456782", + "auth_name": "533458225", "auth_pass": "bar", } # post our password to the IDP @@ -691,7 +703,7 @@ def test_digid_user_success(self): """Assert that digid users can register with duplicate emails""" test_user = DigidUserFactory.create( email="test@example.com", - bsn="123456789", + bsn="648197724", ) url = reverse("digid-mock:password") @@ -703,7 +715,7 @@ def test_digid_user_success(self): data = { # different BSN - "auth_name": "112083948", + "auth_name": "533458225", "auth_pass": "bar", } # post our password to the IDP @@ -780,7 +792,7 @@ def test_digid_user_non_digid_duplicate_fail(self): url = f"{url}?{urlencode(params)}" data = { - "auth_name": "123456789", + "auth_name": "533458225", "auth_pass": "bar", } # post our password to the IDP @@ -816,7 +828,7 @@ def test_digid_user_can_edit_profile(self): url = f"{url}?{urlencode(params)}" data = { - "auth_name": "123456782", + "auth_name": "533458225", "auth_pass": "bar", } # post our password to the IDP diff --git a/src/open_inwoner/cms/cases/tests/test_htmx.py b/src/open_inwoner/cms/cases/tests/test_htmx.py index e7502953d3..6d873c94e2 100644 --- a/src/open_inwoner/cms/cases/tests/test_htmx.py +++ b/src/open_inwoner/cms/cases/tests/test_htmx.py @@ -411,15 +411,6 @@ def test_cases(self, m): # check case is visible expect(page.get_by_text(self.zaak["identificatie"])).to_be_visible() - # out-of-band anchor menu - menu_items = page.get_by_role( - "complementary", name=_("Secundaire paginanavigatie") - ).get_by_role("listitem") - - expect(menu_items.get_by_role("link", name=_("Gegevens"))).to_be_visible() - expect(menu_items.get_by_role("link", name=_("Status"))).to_be_visible() - expect(menu_items.get_by_role("link", name=_("Documenten"))).to_be_visible() - # check documents show documents = page.locator(".file-list").get_by_role("listitem") diff --git a/src/open_inwoner/components/templates/components/Dashboard/Dashboard.html b/src/open_inwoner/components/templates/components/Dashboard/Dashboard.html index cfda1c7f3b..2e6c897efa 100644 --- a/src/open_inwoner/components/templates/components/Dashboard/Dashboard.html +++ b/src/open_inwoner/components/templates/components/Dashboard/Dashboard.html @@ -4,9 +4,7 @@ diff --git a/src/open_inwoner/components/templatetags/dashboard_tags.py b/src/open_inwoner/components/templatetags/dashboard_tags.py index 5544c7f5d7..371edbde37 100644 --- a/src/open_inwoner/components/templatetags/dashboard_tags.py +++ b/src/open_inwoner/components/templatetags/dashboard_tags.py @@ -9,7 +9,6 @@ class Metric(TypedDict): - icon: str label: str value: Optional[str] @@ -27,7 +26,7 @@ def case_dashboard(case: dict, **kwargs) -> dict: {% case_dashboard case %} Variables: - + case: dict | The case to be able to build the dashboard, fetching the documents and statusses of the case. + + case: dict | The case to be able to build the dashboard, fetching the documents and statuses of the case. Extra context: + config: DashboardConfig | The configuration of the dashboard. @@ -35,24 +34,16 @@ def case_dashboard(case: dict, **kwargs) -> dict: config: DashboardConfig = { "metrics": [ { - "icon": "inventory_2", - "label": _("Aanvraag"), + "label": _("Zaaknummer:"), "value": case.get("identification"), }, { - "icon": "calendar_today", - "label": _("Datum"), + "label": _("Aanvraag ingediend op:"), "value": case.get("start_date"), }, { - "icon": "task_alt", - "label": _("Status"), - "value": case.get("current_status"), - }, - { - "icon": "description", - "label": _("Documenten"), - "value": len(case.get("documents")), + "label": _("Verwachte uitslag:"), + "value": case.get("end_date_legal"), }, ] } @@ -80,18 +71,15 @@ def contactmoment_dashboard(kcm: KCMDict, **kwargs) -> dict: config: DashboardConfig = { "metrics": [ { - "icon": "calendar_today", - "label": _("Ontvangstdatum"), + "label": _("Ontvangstdatum: "), "value": kcm.get("registered_date"), }, { - "icon": "inventory_2", - "label": _("Contactwijze"), + "label": _("Contactwijze: "), "value": kcm.get("channel"), }, { - "icon": "task_alt", - "label": _("Status"), + "label": _("Status: "), "value": kcm.get("status"), }, ] diff --git a/src/open_inwoner/openzaak/admin.py b/src/open_inwoner/openzaak/admin.py index 95e5336465..bab3b3cad3 100644 --- a/src/open_inwoner/openzaak/admin.py +++ b/src/open_inwoner/openzaak/admin.py @@ -19,6 +19,7 @@ UserCaseStatusNotification, ZaakTypeConfig, ZaakTypeInformatieObjectTypeConfig, + ZaakTypeResultaatTypeConfig, ZaakTypeStatusTypeConfig, ) from .resources.import_resource import StatusTranslationImportResource @@ -202,11 +203,37 @@ def has_delete_permission(self, request, obj=None): return request.user.is_superuser +class ZaakTypeResultaattypeConfigInline(admin.TabularInline): + model = ZaakTypeResultaatTypeConfig + fields = [ + "omschrijving", + "resultaattype_url", + "zaaktype_uuids", + "description", + ] + readonly_fields = [ + "omschrijving", + "resultaattype_url", + "zaaktype_uuids", + ] + ordering = ( + "zaaktype_uuids", + "omschrijving", + ) + + def has_add_permission(self, request, obj): + return False + + def has_delete_permission(self, request, obj=None): + return request.user.is_superuser + + @admin.register(ZaakTypeConfig) class ZaakTypeConfigAdmin(admin.ModelAdmin): inlines = [ ZaakTypeInformatieObjectTypeConfigInline, ZaakTypeStatusTypeConfigInline, + ZaakTypeResultaattypeConfigInline, ] actions = [ "mark_as_notify_status_changes", diff --git a/src/open_inwoner/openzaak/catalog.py b/src/open_inwoner/openzaak/catalog.py index de40998b06..c0a13b23ed 100644 --- a/src/open_inwoner/openzaak/catalog.py +++ b/src/open_inwoner/openzaak/catalog.py @@ -83,6 +83,26 @@ def fetch_single_status_type(status_type_url: str) -> Optional[StatusType]: return status_type +@cache_result( + "resultaat_type:{resultaat_type_url}", timeout=settings.CACHE_ZGW_CATALOGI_TIMEOUT +) +def fetch_single_resultaat_type(resultaat_type_url: str) -> Optional[ResultaatType]: + client = build_client("catalogi") + + if client is None: + return + + try: + response = client.retrieve("resultaattype", url=resultaat_type_url) + except (RequestException, ClientError) as e: + logger.exception("exception while making request", exc_info=e) + return + + resultaat_type = factory(ResultaatType, response) + + return resultaat_type + + # not cached because only used by tools, # and because caching (stale) listings can break lookups def fetch_zaaktypes_no_cache() -> List[ZaakType]: diff --git a/src/open_inwoner/openzaak/management/commands/zgw_import_data.py b/src/open_inwoner/openzaak/management/commands/zgw_import_data.py index d032c319a7..e2915b4ebb 100644 --- a/src/open_inwoner/openzaak/management/commands/zgw_import_data.py +++ b/src/open_inwoner/openzaak/management/commands/zgw_import_data.py @@ -6,6 +6,7 @@ import_catalog_configs, import_zaaktype_configs, import_zaaktype_informatieobjecttype_configs, + import_zaaktype_resultaattype_configs, import_zaaktype_statustype_configs, ) @@ -15,7 +16,38 @@ class Command(BaseCommand): help = "Import ZGW catalog data" + def log_supplement_imports_to_stdout( + self, import_func: callable, config_type: str + ) -> None: + """ + Convenience function for logging zaaktype config types to stdout + + Example input: + import_func=import_zaaktype_informatieobjecttype_configs + config_type="informatiebjecttype" + + Example output: + imported 3 new zaaktype-informatiebjecttype configs + AAA - zaaktype-aaa + info-aaa-1 + info-aaa-2 + BBB - zaaktype-bbb + info-bbb + """ + imported = import_func() + + count = sum(len(t[1]) for t in imported) + self.stdout.write(f"imported {count} new zaaktype-{config_type} configs") + + for ztc, config_types in sorted(imported, key=lambda t: str(t[0])): + self.stdout.write(str(ztc)) + for c in sorted(map(str, config_types)): + self.stdout.write(f" {c}") + + self.stdout.write("") + def handle(self, *args, **options): + # catalogus config imported = import_catalog_configs() self.stdout.write(f"imported {len(imported)} new catalogus configs") @@ -24,6 +56,7 @@ def handle(self, *args, **options): self.stdout.write("") + # zaaktype config imported = import_zaaktype_configs() self.stdout.write(f"imported {len(imported)} new zaaktype configs") @@ -32,24 +65,13 @@ def handle(self, *args, **options): self.stdout.write("") - imported = import_zaaktype_informatieobjecttype_configs() - - count = sum(len(t[1]) for t in imported) - - self.stdout.write(f"imported {count} new zaaktype-informatiebjecttype configs") - for ztc, info_types in sorted(imported, key=lambda t: str(t[0])): - self.stdout.write(str(ztc)) - for c in sorted(map(str, info_types)): - self.stdout.write(f" {c}") - - self.stdout.write("") - - imported = import_zaaktype_statustype_configs() - - count = sum(len(t[1]) for t in imported) - - self.stdout.write(f"imported {count} new zaaktype-statustype configs") - for ztc, status_types in sorted(imported, key=lambda t: str(t[0])): - self.stdout.write(str(ztc)) - for c in sorted(map(str, status_types)): - self.stdout.write(f" {c}") + # supplemental configs + self.log_supplement_imports_to_stdout( + import_zaaktype_informatieobjecttype_configs, "informatiebjecttype" + ) + self.log_supplement_imports_to_stdout( + import_zaaktype_statustype_configs, "statustype" + ) + self.log_supplement_imports_to_stdout( + import_zaaktype_resultaattype_configs, "resultaattype" + ) diff --git a/src/open_inwoner/openzaak/migrations/0027_zaaktype_resultaattype_config.py b/src/open_inwoner/openzaak/migrations/0027_zaaktype_resultaattype_config.py index 840666039f..9b32306884 100644 --- a/src/open_inwoner/openzaak/migrations/0027_zaaktype_resultaattype_config.py +++ b/src/open_inwoner/openzaak/migrations/0027_zaaktype_resultaattype_config.py @@ -1,8 +1,7 @@ # Generated by Django 3.2.20 on 2023-10-26 10:05 -import django.db.models.deletion from django.db import migrations, models - +import django.db.models.deletion import django_better_admin_arrayfield.models.fields diff --git a/src/open_inwoner/openzaak/models.py b/src/open_inwoner/openzaak/models.py index 6d1e998c96..49995e1bba 100644 --- a/src/open_inwoner/openzaak/models.py +++ b/src/open_inwoner/openzaak/models.py @@ -377,6 +377,50 @@ def __str__(self): return f"{self.zaaktype_config.identificatie} - {self.omschrijving}" +class ZaakTypeResultaatTypeConfig(models.Model): + zaaktype_config = models.ForeignKey( + "openzaak.ZaakTypeConfig", + on_delete=models.CASCADE, + ) + resultaattype_url = models.URLField( + verbose_name=_("Resultaattype URL"), + max_length=1000, + ) + omschrijving = models.CharField( + verbose_name=_("Omschrijving"), + max_length=20, + ) + zaaktype_uuids = ArrayField( + models.UUIDField( + verbose_name=_("Zaaktype UUID"), + ), + default=list, + ) + + # configuration + description = models.TextField( + blank=True, + default="", + verbose_name=_("Frontend description"), + help_text=_( + "Determines the text that will be shown to the user if a case is set to this result" + ), + ) + + class Meta: + verbose_name = _("Zaaktype Resultaattype Configuration") + + constraints = [ + UniqueConstraint( + name="unique_zaaktype_config_resultaattype_url", + fields=["zaaktype_config", "resultaattype_url"], + ) + ] + + def __str__(self): + return f"{self.zaaktype_config.identificatie} - {self.omschrijving}" + + class UserCaseStatusNotificationBase(models.Model): user = models.ForeignKey( "accounts.User", diff --git a/src/open_inwoner/openzaak/tests/test_case_detail.py b/src/open_inwoner/openzaak/tests/test_case_detail.py index 3dedeabf24..bac41a4e45 100644 --- a/src/open_inwoner/openzaak/tests/test_case_detail.py +++ b/src/open_inwoner/openzaak/tests/test_case_detail.py @@ -496,8 +496,6 @@ def test_page_displays_expected_data(self, m): self.assertContains(response, "ZAAK-2022-0000000024") self.assertContains(response, "Coffee zaaktype") self.assertContains(response, "uploaded_document_title") - self.assertContains(response, "Foo Bar van der Bazz") - self.assertContains(response, "resultaat toelichting") def test_page_reformats_zaak_identificatie(self, m): self._setUpMocks(m) diff --git a/src/open_inwoner/openzaak/tests/test_zgw_imports_command.py b/src/open_inwoner/openzaak/tests/test_zgw_imports_command.py index 88542d6a81..21b71a6e31 100644 --- a/src/open_inwoner/openzaak/tests/test_zgw_imports_command.py +++ b/src/open_inwoner/openzaak/tests/test_zgw_imports_command.py @@ -12,6 +12,7 @@ OpenZaakConfig, ZaakTypeConfig, ZaakTypeInformatieObjectTypeConfig, + ZaakTypeResultaatTypeConfig, ZaakTypeStatusTypeConfig, ) from open_inwoner.openzaak.tests.factories import ServiceFactory @@ -49,6 +50,7 @@ def test_zgw_import_data_command(self, m): self.assertEqual(ZaakTypeConfig.objects.count(), 2) self.assertEqual(ZaakTypeInformatieObjectTypeConfig.objects.count(), 3) self.assertEqual(ZaakTypeStatusTypeConfig.objects.count(), 2) + self.assertEqual(ZaakTypeResultaatTypeConfig.objects.count(), 2) stdout = out.getvalue().strip() @@ -73,6 +75,12 @@ def test_zgw_import_data_command(self, m): AAA - zaaktype-aaa AAA - status-aaa-1 AAA - status-aaa-2 + + imported 2 new zaaktype-resultaattype configs + AAA - zaaktype-aaa + AAA - test + BBB - zaaktype-bbb + BBB - test """ ).strip() @@ -87,6 +95,7 @@ def test_zgw_import_data_command(self, m): self.assertEqual(ZaakTypeConfig.objects.count(), 2) self.assertEqual(ZaakTypeInformatieObjectTypeConfig.objects.count(), 3) self.assertEqual(ZaakTypeStatusTypeConfig.objects.count(), 2) + self.assertEqual(ZaakTypeResultaatTypeConfig.objects.count(), 2) stdout = out.getvalue().strip() @@ -99,6 +108,8 @@ def test_zgw_import_data_command(self, m): imported 0 new zaaktype-informatiebjecttype configs imported 0 new zaaktype-statustype configs + + imported 0 new zaaktype-resultaattype configs """ ).strip() @@ -111,7 +122,7 @@ def test_zgw_import_data_command_without_catalog(self, m): ) InformationObjectTypeMockData().install_mocks(m, with_catalog=False) - # run it to import our data + # # run it to import our data out = StringIO() call_command("zgw_import_data", stdout=out) @@ -119,6 +130,7 @@ def test_zgw_import_data_command_without_catalog(self, m): self.assertEqual(ZaakTypeConfig.objects.count(), 2) self.assertEqual(ZaakTypeInformatieObjectTypeConfig.objects.count(), 3) self.assertEqual(ZaakTypeStatusTypeConfig.objects.count(), 2) + self.assertEqual(ZaakTypeResultaatTypeConfig.objects.count(), 2) stdout = out.getvalue().strip() @@ -141,6 +153,12 @@ def test_zgw_import_data_command_without_catalog(self, m): AAA - zaaktype-aaa AAA - status-aaa-1 AAA - status-aaa-2 + + imported 2 new zaaktype-resultaattype configs + AAA - zaaktype-aaa + AAA - test + BBB - zaaktype-bbb + BBB - test """ ).strip() @@ -155,6 +173,7 @@ def test_zgw_import_data_command_without_catalog(self, m): self.assertEqual(ZaakTypeConfig.objects.count(), 2) self.assertEqual(ZaakTypeInformatieObjectTypeConfig.objects.count(), 3) self.assertEqual(ZaakTypeStatusTypeConfig.objects.count(), 2) + self.assertEqual(ZaakTypeResultaatTypeConfig.objects.count(), 2) stdout = out.getvalue().strip() @@ -167,6 +186,8 @@ def test_zgw_import_data_command_without_catalog(self, m): imported 0 new zaaktype-informatiebjecttype configs imported 0 new zaaktype-statustype configs + + imported 0 new zaaktype-resultaattype configs """ ).strip() diff --git a/src/open_inwoner/openzaak/tests/test_zgw_imports_iotypes.py b/src/open_inwoner/openzaak/tests/test_zgw_imports_iotypes.py index 1612fa80c4..79f3984017 100644 --- a/src/open_inwoner/openzaak/tests/test_zgw_imports_iotypes.py +++ b/src/open_inwoner/openzaak/tests/test_zgw_imports_iotypes.py @@ -88,6 +88,18 @@ def __init__(self): statustypen=[ self.statustype_aaa_1["url"], ], + resultaattypen=[ + f"{CATALOGI_ROOT}resultaatypen/b1a268dd-4322-47bb-a930-b83066b4a32c" + ], + ) + self.resultaat_type_1 = generate_oas_component( + "ztc", + "schemas/ResultaatType", + url=f"{CATALOGI_ROOT}resultaatypen/b1a268dd-4322-47bb-a930-b83066b4a32c", + zaaktype=self.zaaktype_aaa_1, + omschrijving="test", + resultaattypeomschrijving="test1", + selectielijstklasse="ABC", ) self.zaaktype_bbb = generate_oas_component( "ztc", @@ -103,6 +115,9 @@ def __init__(self): self.info_type_bbb["url"], ], statustypen=[], + resultaattypen=[ + f"{CATALOGI_ROOT}resultaatypen/b1a268dd-4322-47bb-a930-b83066b4a32c" + ], ) self.zaaktype_aaa_2 = generate_oas_component( "ztc", @@ -121,6 +136,9 @@ def __init__(self): statustypen=[ self.statustype_aaa_2["url"], ], + resultaattypen=[ + f"{CATALOGI_ROOT}resultaatypen/b1a268dd-4322-47bb-a930-b83066b4a32c", + ], ) self.zaaktype_aaa_intern = generate_oas_component( "ztc", @@ -137,6 +155,7 @@ def __init__(self): self.info_type_aaa_1["url"], ], statustypen=[], + resultaattypen=[], ) self.extra_zaaktype_aaa = generate_oas_component( "ztc", @@ -155,6 +174,9 @@ def __init__(self): self.extra_info_type_aaa_3["url"], ], statustypen=[], + resultaattypen=[ + self.resultaat_type_1["url"], + ], ) self.all_io_types = [ @@ -174,6 +196,9 @@ def __init__(self): self.statustype_aaa_1, self.statustype_aaa_2, ] + self.all_resultaat_types = [ + self.resultaat_type_1, + ] def setUpOASMocks(self, m): mock_service_oas_get(m, CATALOGI_ROOT, "ztc") @@ -188,6 +213,7 @@ def install_mocks(self, m, *, with_catalog=True) -> "InformationObjectTypeMockDa self.extra_info_type_aaa_3, self.statustype_aaa_1, self.statustype_aaa_2, + self.resultaat_type_1, ]: m.get(resource["url"], json=resource) @@ -202,6 +228,14 @@ def install_mocks(self, m, *, with_catalog=True) -> "InformationObjectTypeMockDa ] ), ) + m.get( + f"{CATALOGI_ROOT}resultaattypen", + json=paginated_response( + [ + self.resultaat_type_1, + ] + ), + ) if with_catalog: cat_a = f"&catalogus={CATALOGI_ROOT}catalogussen/aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa" diff --git a/src/open_inwoner/openzaak/zgw_imports.py b/src/open_inwoner/openzaak/zgw_imports.py index 38d6b3a276..038a5fcac1 100644 --- a/src/open_inwoner/openzaak/zgw_imports.py +++ b/src/open_inwoner/openzaak/zgw_imports.py @@ -3,13 +3,18 @@ from django.db import transaction -from zgw_consumers.api_models.catalogi import InformatieObjectType, StatusType +from zgw_consumers.api_models.catalogi import ( + InformatieObjectType, + ResultaatType, + StatusType, +) from open_inwoner.openzaak.api_models import ZaakType from open_inwoner.openzaak.catalog import ( fetch_case_types_by_identification_no_cache, fetch_catalogs_no_cache, fetch_single_information_object_type, + fetch_single_resultaat_type, fetch_single_status_type, fetch_zaaktypes_no_cache, ) @@ -17,6 +22,7 @@ CatalogusConfig, ZaakTypeConfig, ZaakTypeInformatieObjectTypeConfig, + ZaakTypeResultaatTypeConfig, ZaakTypeStatusTypeConfig, ) @@ -148,6 +154,20 @@ def import_zaaktype_statustype_configs() -> List[Tuple[ZaakTypeConfig, StatusTyp return created +def import_zaaktype_resultaattype_configs() -> List[ + Tuple[ZaakTypeConfig, ResultaatType] +]: + """ + generate ZaakTypeResultaatTypeConfigs for all ZaakTypeConfig + """ + created = [] + for ztc in ZaakTypeConfig.objects.all(): + imported = import_resultaattype_configs_for_type(ztc) + if imported: + created.append((ztc, imported)) + return created + + def import_zaaktype_informatieobjecttype_configs_for_type( ztc: ZaakTypeConfig, ) -> List[ZaakTypeInformatieObjectTypeConfig]: @@ -243,7 +263,7 @@ def import_statustype_configs_for_type( for zaaktype_statustype in ztc.zaaktypestatustypeconfig_set.all() } - # collect and implicitly de-duplicate informatieobjecttype url's and track which zaaktype used it + # collect and implicitly de-duplicate statustype url's and track which zaaktype used it info_queue = defaultdict(list) for zaak_type in zaak_types: for url in zaak_type.statustypen: @@ -282,3 +302,70 @@ def import_statustype_configs_for_type( ZaakTypeStatusTypeConfig.objects.bulk_update(update, ["zaaktype_uuids"]) return create + + +def import_resultaattype_configs_for_type( + ztc: ZaakTypeConfig, +) -> List[ZaakTypeResultaatTypeConfig]: + """ + generate ZaakTypeResultaatTypeConfigs for all ResultaatTypes used by each ZaakTypeConfigs source ZaakTypes + + this is a bit complicated because one ZaakTypeConfig can represent multiple ZaakTypes + """ + + # grab actual ZaakTypes for this identificatie + zaak_types: List[ZaakType] = get_configurable_zaaktypes_by_identification( + ztc.identificatie, ztc.catalogus_url + ) + if not zaak_types: + return [] + + create = [] + update = [] + + with transaction.atomic(): + # map existing config records by url + + info_map = { + zaaktype_resultaattype.resultaattype_url: zaaktype_resultaattype + for zaaktype_resultaattype in ztc.zaaktyperesultaattypeconfig_set.all() + } + + # collect and implicitly de-duplicate resultaattype url's and track which zaaktype used it + info_queue = defaultdict(list) + for zaak_type in zaak_types: + for url in zaak_type.resultaattypen: + info_queue[url].append(zaak_type) + + if info_queue: + # load urls and update/create records + for resultaattype_url, using_zaak_types in info_queue.items(): + resultaat_type = fetch_single_resultaat_type(resultaattype_url) + + zaaktype_resultaattype = info_map.get(resultaat_type.url) + if zaaktype_resultaattype: + # we got a record for this, see if we got data to update + for using in using_zaak_types: + # track which zaaktype UUID's are interested in this resultaattype + if using.uuid not in zaaktype_resultaattype.zaaktype_uuids: + zaaktype_resultaattype.zaaktype_uuids.append(using.uuid) + if zaaktype_resultaattype not in create: + update.append(zaaktype_resultaattype) + else: + # new record + zaaktype_resultaattype = ZaakTypeResultaatTypeConfig( + zaaktype_config=ztc, + resultaattype_url=resultaat_type.url, + omschrijving=resultaat_type.omschrijving, + zaaktype_uuids=[zt.uuid for zt in using_zaak_types], + ) + create.append(zaaktype_resultaattype) + # not strictly necessary but let's be accurate + info_map[resultaat_type.uuid] = zaaktype_resultaattype + + if create: + ZaakTypeResultaatTypeConfig.objects.bulk_create(create) + if update: + ZaakTypeResultaatTypeConfig.objects.bulk_update(update, ["zaaktype_uuids"]) + + return create diff --git a/src/open_inwoner/scss/components/Cases/Cases.scss b/src/open_inwoner/scss/components/Cases/Cases.scss index 372613b974..38f6a718a4 100644 --- a/src/open_inwoner/scss/components/Cases/Cases.scss +++ b/src/open_inwoner/scss/components/Cases/Cases.scss @@ -1,6 +1,22 @@ .cases { margin-top: var(--spacing-giant); + &__grid .grid { + grid-row-gap: var(--spacing-medium); + grid-column-gap: var(--spacing-extra-large); + + // Tablets + @media (min-width: 768px) and (max-width: 900px) { + grid-template-columns: 1fr; + } + + .column--start-4 { + @media (min-width: 768px) and (max-width: 900px) { + grid-column-start: 1; + } + } + } + /// cards on cases page .card { .cases__link { diff --git a/src/open_inwoner/scss/components/Contactmomenten/Contactmomenten.scss b/src/open_inwoner/scss/components/Contactmomenten/Contactmomenten.scss index 2aaa67d79e..fd6b11bdfa 100644 --- a/src/open_inwoner/scss/components/Contactmomenten/Contactmomenten.scss +++ b/src/open_inwoner/scss/components/Contactmomenten/Contactmomenten.scss @@ -8,3 +8,28 @@ } } } + +.contactmoment { + &__details { + // Set table width for word-break + table { + margin-top: 0; + overflow-x: auto; + table-layout: fixed; + width: 100%; + @media (min-width: 767px) { + width: var(--mobile-ms-width); + } + } + table th { + width: 100px; + white-space: nowrap; + } + table td { + width: 300px; + overflow: hidden; + word-break: break-word; + white-space: normal; + } + } +} diff --git a/src/open_inwoner/scss/components/Container/Container.scss b/src/open_inwoner/scss/components/Container/Container.scss index 9a699cfdfe..b31155e94a 100644 --- a/src/open_inwoner/scss/components/Container/Container.scss +++ b/src/open_inwoner/scss/components/Container/Container.scss @@ -14,7 +14,9 @@ } @media (min-width: 768px) { - $hm: max(calc((100vw - var(--container-width)) / 2), var(--spacing-large)); + // $hm has a minimum of var(--spacing-large), and a maximum of $hlargest + $hlargest: calc(((100vw - var(--container-width)) / 2) - 7px); + $hm: max($hlargest, var(--spacing-large)); margin: var(--spacing-extra-large) $hm calc(var(--row-height) * 2); &--no-margin { diff --git a/src/open_inwoner/scss/components/Dashboard/Dashboard.scss b/src/open_inwoner/scss/components/Dashboard/Dashboard.scss index 4d495f9541..ead34f4832 100644 --- a/src/open_inwoner/scss/components/Dashboard/Dashboard.scss +++ b/src/open_inwoner/scss/components/Dashboard/Dashboard.scss @@ -1,28 +1,57 @@ .dashboard { + margin-bottom: var(--spacing-mega); + &__list { - display: grid; - grid-template-columns: 1fr 1fr; - gap: var(--spacing-large) var(--spacing-medium); - justify-content: space-between; + display: block; list-style: none; margin: 0; padding: 0; + border-bottom: var(--border-width-thin) solid var(--color-gray); - @media (min-width: 768px) { + @media (min-width: 900px) { display: flex; - gap: var(--spacing-medium); + gap: 0; + border-top: var(--border-width-thin) solid var(--color-gray); } } &__list-item { + padding: var(--spacing-large) 0; + color: var(--color-gray-dark); white-space: nowrap; - text-align: center; + border-top: var(--border-width-thin) solid var(--color-gray); + @media (min-width: 900px) { + border-top: none; + margin-left: var(--spacing-extra-large); + } - @media (min-width: 768px) { + &:first-child { + margin-left: 0; + } + + p::after { + color: var(--color-gray); + @media (min-width: 900px) { + content: '|'; + margin-left: var(--spacing-extra-large); + } + } + + &:last-child p::after { + @media (min-width: 900px) { + content: none; + } + } + + @media (min-width: 900px) { text-align: start; } } + &__item-label { + color: var(--color-gray-90); + } + .h4 { @media (min-width: 768px) { margin-top: var(--spacing-large); diff --git a/src/open_inwoner/scss/components/Grid/Grid.scss b/src/open_inwoner/scss/components/Grid/Grid.scss index c8b045be5b..a1bdfee348 100644 --- a/src/open_inwoner/scss/components/Grid/Grid.scss +++ b/src/open_inwoner/scss/components/Grid/Grid.scss @@ -18,7 +18,8 @@ display: grid; grid-template-columns: repeat(12, 1fr); grid-template-rows: 1fr; - gap: var(--gutter-width); + grid-row-gap: var(--spacing-extra-large); + grid-column-gap: var(--gutter-width); } /// Layout. diff --git a/src/open_inwoner/scss/components/Spinner/Spinner.scss b/src/open_inwoner/scss/components/Spinner/Spinner.scss index 3604b85c81..9bc0ba8192 100644 --- a/src/open_inwoner/scss/components/Spinner/Spinner.scss +++ b/src/open_inwoner/scss/components/Spinner/Spinner.scss @@ -20,6 +20,11 @@ align-items: flex-start; height: 70vh; margin: 100px 0 0 200px; + + &.case__spinner-container { + justify-content: center; + margin: 100px 0 0 0; + } } .spinner { diff --git a/src/open_inwoner/scss/views/App.scss b/src/open_inwoner/scss/views/App.scss index db1aa713b7..1999e15287 100644 --- a/src/open_inwoner/scss/views/App.scss +++ b/src/open_inwoner/scss/views/App.scss @@ -25,6 +25,7 @@ --color-black: #000; --color-blue: #1261a3; --color-gray: #d2d2d2; + --color-gray-90: #676767; --color-gray-dark: #4b4b4b; --color-gray-lighter: #7a7a7a; --color-gray-light: #d2d2d2; @@ -246,6 +247,7 @@ --spacing-large: 16px; --spacing-extra-large: 24px; --spacing-giant: 32px; + --spacing-mega: 80px; /// Common widths --form-width: 500px; diff --git a/src/open_inwoner/templates/pages/cases/status_inner.html b/src/open_inwoner/templates/pages/cases/status_inner.html index 42b911fad2..c6b129d678 100644 --- a/src/open_inwoner/templates/pages/cases/status_inner.html +++ b/src/open_inwoner/templates/pages/cases/status_inner.html @@ -1,31 +1,22 @@ {% load i18n ssd_tags anchor_menu_tags card_tags dashboard_tags file_tags grid_tags table_tags solo_tags link_tags button_tags icon_tags notification_tags %} -{# Anchor menu-mobile #} -
- {% anchor_menu anchors=anchors desktop=False %} -
- {# Messages #}
{% notifications messages %}
-{# Anchor menu-desktop #} -
- {% anchor_menu anchors desktop=True %} -
- {% get_solo 'openzaak.OpenZaakConfig' as openzaak_config %} +
{% if case %} {% render_grid %} - {% render_column span=9 %} - + {% render_column span=12 %} {# Title/dashboard. #}

{{ case.description }}

{% case_dashboard case %} - {% case_table case %} + {% endrender_column %} + {% render_column start=4 span=6 %} {# Status history. #} {% if case.statuses %}

{% trans 'Status' %}

@@ -151,3 +142,4 @@

{% trans "Document toevoegen" %}

{% else %}

{% trans 'There is no available data at the moment.' %}

{% endif %} +
diff --git a/src/open_inwoner/templates/pages/cases/status_outer.html b/src/open_inwoner/templates/pages/cases/status_outer.html index ed7b79a8da..30cfdcb081 100644 --- a/src/open_inwoner/templates/pages/cases/status_outer.html +++ b/src/open_inwoner/templates/pages/cases/status_outer.html @@ -1,22 +1,14 @@ {% extends 'master.html' %} {% load i18n icon_tags %} -{% block mobile_anchors %} -
-{% endblock mobile_anchors %} - {% block notifications %}
{% endblock notifications %} -{% block sidebar_content %} -
-{% endblock sidebar_content %} - {% block content %}
-
+
{% icon icon="rotate_right" extra_classes="spinner-icon rotate" %}
{% trans "Gegevens laden..." %}
diff --git a/src/open_inwoner/templates/pages/contactmoment/detail.html b/src/open_inwoner/templates/pages/contactmoment/detail.html index 9055fe43fc..d55d0fb0f8 100644 --- a/src/open_inwoner/templates/pages/contactmoment/detail.html +++ b/src/open_inwoner/templates/pages/contactmoment/detail.html @@ -1,18 +1,19 @@ {% extends 'master.html' %} {% load i18n anchor_menu_tags card_tags dashboard_tags file_tags grid_tags table_tags solo_tags form_tags button_tags %} -{% block sidebar_content %} - {% if contactmoment %} - {% anchor_menu anchors desktop=True %} - {% endif %} -{% endblock sidebar_content %} - {% block content %} {% if contactmoment %} {% render_grid %} - {% render_column span=9 %} + {% render_column span=12 %} + {# Contactmoment/dashboard. #} +

{{ page_title }}

{% contactmoment_dashboard contactmoment %} + {% endrender_column %} + {% render_column start=4 span=6 %} + +
{% contactmoment_table contactmoment %} +
{% endrender_column %} {% endrender_grid %} {% else %}