Skip to content

Commit

Permalink
Merge pull request #1331 from maykinmedia/task/2645-case-filter-status
Browse files Browse the repository at this point in the history
[#2645] Filter cases by status
  • Loading branch information
alextreme authored Aug 12, 2024
2 parents 51f79fe + 0c8a157 commit e18dafe
Show file tree
Hide file tree
Showing 6 changed files with 304 additions and 50 deletions.
23 changes: 23 additions & 0 deletions src/open_inwoner/cms/cases/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,26 @@ class CaseContactForm(forms.Form):
widget=forms.Textarea(attrs={"rows": "5"}),
required=True,
)


class CaseFilterForm(forms.Form):
def __init__(
self,
status_freqs: dict[str, int],
status_initial: list[str] | None = None,
*args,
**kwargs,
):
super().__init__(*args, **kwargs)

self.fields["status"].choices = (
(status, f"{status} ({frequency})")
for status, frequency in status_freqs.items()
)
self.fields["status"].initial = status_initial or []

status = forms.MultipleChoiceField(
label=_("Filter by status"),
widget=forms.CheckboxSelectMultiple,
choices=dict(),
)
143 changes: 104 additions & 39 deletions src/open_inwoner/cms/cases/views/cases.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@
import logging
from dataclasses import dataclass

from django.http import HttpRequest
from django.urls import reverse
from django.utils.functional import cached_property
from django.utils.translation import gettext_lazy as _
from django.views.generic import TemplateView

from furl import furl
from view_breadcrumbs import BaseBreadcrumbMixin
from zgw_consumers.concurrent import parallel

Expand All @@ -20,11 +22,15 @@
from open_inwoner.utils.mixins import PaginationMixin
from open_inwoner.utils.views import CommonPageMixin

from ..forms import CaseFilterForm
from .mixins import CaseAccessMixin, CaseLogMixin, OuterCaseAccessMixin

logger = logging.getLogger(__name__)


SUBMISSION_STATUS_OPEN = _("Openstaande aanvraag")


@dataclass(frozen=True)
class ZaakWithApiGroup(UniformCase):
zaak: Zaak
Expand All @@ -38,6 +44,60 @@ def process_data(self) -> dict:
return {**self.zaak.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):
raw_cases = group.zaken_client.fetch_cases(
**get_user_fetch_parameters(self.request)
)
preprocessed_cases = preprocess_data(raw_cases, group)
return preprocessed_cases

def get_cases(self) -> list[ZaakWithApiGroup]:
all_api_groups = list(ZGWApiGroupConfig.objects.all())

with parallel() as executor:
futures = [
executor.submit(self.get_cases_for_api_group, group)
for group in all_api_groups
]

cases_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():
cases_with_api_group.append(
ZaakWithApiGroup(zaak=row, api_group=group_for_task)
)
except BaseException:
logger.exception("Error fetching and pre-processing cases")

# Ensure stable sorting for pagination and testing purposes
cases_with_api_group.sort(key=lambda c: all_api_groups.index(c.api_group))

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)

return subs

def get_case_status_frequencies(self):
cases = self.get_cases()
submissions = self.get_submissions()

case_statuses = [case.zaak.status_text for case in cases]

# add static text for open submissions
case_statuses += [SUBMISSION_STATUS_OPEN for submission in submissions]

return {status: case_statuses.count(status) for status in case_statuses}


class OuterCaseListView(
OuterCaseAccessMixin, CommonPageMixin, BaseBreadcrumbMixin, TemplateView
):
Expand All @@ -57,7 +117,12 @@ def page_title(self):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)

context["hxget"] = reverse("cases:cases_content")
statuses = self.request.GET.getlist("status")

f_url = furl(reverse("cases:cases_content"))
f_url.args.addlist("status", statuses)

context["hxget"] = f_url.url
return context


Expand All @@ -75,48 +140,42 @@ class InnerCaseListView(
def page_title(self):
return _("Mijn aanvragen")

def get_cases_for_api_group(self, group: ZGWApiGroupConfig):
raw_cases = group.zaken_client.fetch_cases(
**get_user_fetch_parameters(self.request)
)
preprocessed_cases = preprocess_data(raw_cases, group)
return preprocessed_cases

def get_cases(self) -> list[ZaakWithApiGroup]:
all_api_groups = list(ZGWApiGroupConfig.objects.all())
with parallel() as executor:
futures = [
executor.submit(self.get_cases_for_api_group, group)
for group in all_api_groups
]

cases = []
for task in concurrent.futures.as_completed(futures):
try:
group_for_task = all_api_groups[futures.index(task)]
for row in task.result():
cases.append(
ZaakWithApiGroup(zaak=row, api_group=group_for_task)
)
except BaseException:
logger.exception("Error fetching and pre-processing cases")

# Ensure stable sorting for pagination and testing purposes
cases.sort(key=lambda c: all_api_groups.index(c.api_group))
return cases

def get_submissions(self):
subs = fetch_open_submissions(self.request.user.bsn)
subs.sort(key=lambda sub: sub.datum_laatste_wijziging, reverse=True)
return subs

def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
config = OpenZaakConfig.get_solo()
case_service = CaseListService(self.request)
form = None

# update ctx with open submissions, cases, form for filtering
open_submissions: list[UniformCase] = case_service.get_submissions()
preprocessed_cases: list[UniformCase] = case_service.get_cases()

statuses = self.request.GET.getlist("status")
# GET.getlist returns [''] if no query params are passed, hence we test by GET.get
if self.request.GET.get("status"):
form = CaseFilterForm(
status_freqs=case_service.get_case_status_frequencies(),
status_initial=statuses,
data={"status": statuses},
)

# error message to user would be unhelpful;
# we pass silently over invalid input and send a ping to Sentry
if not form.is_valid():
form.errors["status"] = []
logger.error(
"Invalid data (%s) for case filtering by %s",
self.request.GET,
self.request.user,
)

open_submissions = (
open_submissions if SUBMISSION_STATUS_OPEN in statuses else []
)
preprocessed_cases = [
case for case in preprocessed_cases if case.zaak.status_text in statuses
]

# update ctx with submissions + cases
open_submissions: list[UniformCase] = self.get_submissions()
preprocessed_cases: list[UniformCase] = self.get_cases()
paginator_dict = self.paginate_with_context(
[*open_submissions, *preprocessed_cases]
)
Expand All @@ -127,7 +186,13 @@ def get_context_data(self, **kwargs):

self.log_access_cases(case_dicts)

context["form"] = form or CaseFilterForm(
status_freqs=case_service.get_case_status_frequencies(),
status_initial=statuses,
)

# other data
context["hxget"] = reverse("cases:cases_content")
context["title_text"] = config.title_text

return context
22 changes: 13 additions & 9 deletions src/open_inwoner/openzaak/api_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,18 +69,15 @@ def _format_zaak_identificatie(self) -> str:
def identification(self) -> str:
return self._format_zaak_identificatie()

def process_data(self) -> dict:
"""
Prepare data for template
"""

status_text = glom_multiple(
@property
def status_text(self) -> str:
_status_text = glom_multiple(
self,
("status.statustype.statustekst", "status.statustype.omschrijving"),
default="",
)
if self.einddatum and self.resultaat:
result_text = glom_multiple(
_status_text = glom_multiple(
self,
(
"resultaat.resultaattype.naam",
Expand All @@ -90,15 +87,22 @@ def process_data(self) -> dict:
),
default="",
)
status_text = result_text or status_text or _("No data available")
_status_text = _status_text or _("No data available")

return _status_text

def process_data(self) -> dict:
"""
Prepare data for template
"""

return {
"identification": self.identification,
"uuid": str(self.uuid),
"start_date": self.startdatum,
"end_date": getattr(self, "einddatum", None),
"description": self.zaaktype.omschrijving,
"current_status": status_text,
"current_status": self.status_text,
"zaaktype_config": getattr(self, "zaaktype_config", None),
"statustype_config": getattr(self, "statustype_config", None),
"case_type": "Zaak",
Expand Down
92 changes: 92 additions & 0 deletions src/open_inwoner/openzaak/tests/test_api_models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
from django.test import TestCase
from django.utils.translation import gettext as _

from zgw_consumers.api_models.base import factory

from open_inwoner.openzaak.api_models import Zaak


class ZaakAPIModelTest(TestCase):
def setUp(self):
self.zaak_data = {
"url": "",
"identificatie": "",
"bronorganisatie": "",
"omschrijving": "",
"zaaktype": "",
"registratiedatum": "2024-08-04",
"startdatum": "2024-08-04",
"vertrouwelijkheidaanduiding": "",
"status": {
"statustype": {
"statustekst": "",
"omschrijving": "",
}
},
"einddatum": None,
"resultaat": {
"resultaattype": {
"naam": "",
"omschrijving": "",
"omschrijving_generiek": "",
"resultaattypeomschrijving": "",
},
},
}

def test_status_text_no_result(self):
zaak_statustype = self.zaak_data["status"]["statustype"]

zaak_statustype["statustekst"] = "test statustekst"
zaak_statustype["omschrijving"] = "test omschrijving"

case = factory(Zaak, data=self.zaak_data)

self.assertEqual(case.status_text, "test statustekst")

case.status["statustype"]["statustekst"] = ""

self.assertEqual(case.status_text, "test omschrijving")

def test_status_text_with_result(self):
self.zaak_data["status"]["statustype"]["statustekst"] = "test statustekst"
self.zaak_data["einddatum"] = "2024-08-06"

resultaattype = self.zaak_data["resultaat"]["resultaattype"]

resultaattype["naam"] = "test naam"
resultaattype["omschrijving"] = "test omschrijving"
resultaattype["omschrijving_generiek"] = "test omschrijving_generiek"
resultaattype["resultaattypeomschrijving"] = "test resultaattypeomschrijving"

case = factory(Zaak, data=self.zaak_data)

self.assertEqual(case.status_text, "test naam")

case.resultaat["resultaattype"]["naam"] = ""

self.assertEqual(case.status_text, "test omschrijving")

case.resultaat["resultaattype"]["omschrijving"] = ""

self.assertEqual(case.status_text, "test omschrijving_generiek")

case.resultaat["resultaattype"]["omschrijving_generiek"] = ""

self.assertEqual(case.status_text, "test resultaattypeomschrijving")

def test_status_text_with_result_but_no_end_data(self):
self.zaak_data["status"]["statustype"]["statustekst"] = "test statustekst"

resultaattype = self.zaak_data["resultaat"]["resultaattype"]

resultaattype["naam"] = "test naam"

case = factory(Zaak, data=self.zaak_data)

self.assertEqual(case.status_text, "test statustekst")

def test_status_text_default(self):
case = factory(Zaak, data=self.zaak_data)

self.assertEqual(case.status_text, _("No data available"))
Loading

0 comments on commit e18dafe

Please sign in to comment.