Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[#2840] Refactor contactmomenten list and detail to use a protocol for data fetching #1473

Merged
merged 3 commits into from
Nov 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
227 changes: 75 additions & 152 deletions src/open_inwoner/accounts/views/contactmoments.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import logging
from datetime import datetime
from typing import TypedDict
from typing import Iterable, Protocol

from django.contrib.auth.mixins import AccessMixin
from django.http import Http404, HttpResponseRedirect
Expand All @@ -11,30 +10,20 @@
from django.views import View
from django.views.generic import TemplateView

from glom import glom
from view_breadcrumbs import BaseBreadcrumbMixin

from open_inwoner.openklant.api_models import KlantContactMoment
from open_inwoner.openklant.clients import (
build_contactmomenten_client,
build_klanten_client,
)
from open_inwoner.openklant.constants import Status
from open_inwoner.openklant.models import (
ContactFormSubject,
KlantContactMomentAnswer,
OpenKlantConfig,
from open_inwoner.accounts.models import User
from open_inwoner.openklant.models import KlantContactMomentAnswer
from open_inwoner.openklant.services import (
KlantenService,
Question,
QuestionValidator,
ZaakWithApiGroup,
eSuiteKlantenService,
eSuiteVragenService,
)
from open_inwoner.openklant.views.contactform import ContactFormView
from open_inwoner.openklant.wrap import (
contactmoment_has_new_answer,
fetch_klantcontactmoment,
fetch_klantcontactmomenten,
get_fetch_parameters,
get_kcm_answer_mapping,
)
from open_inwoner.openzaak.clients import MultiZgwClientProxy
from open_inwoner.openzaak.models import ZGWApiGroupConfig
from open_inwoner.openklant.wrap import FetchParameters
from open_inwoner.utils.mixins import PaginationMixin
from open_inwoner.utils.views import CommonPageMixin

Expand Down Expand Up @@ -70,84 +59,52 @@ def handle_no_permission(self):
return super().handle_no_permission()


class KCMDict(TypedDict):
registered_date: datetime
channel: str
text: str
url: str
identificatie: str
type: str
onderwerp: str
status: str
antwoord: str
new_answer_available: bool
class VragenService(Protocol):
def list_questions(
self,
fetch_params: FetchParameters,
user: User,
) -> Iterable[Question]: # noqa: E704
...

def retrieve_question(
self,
fetch_params: FetchParameters,
question_uuid: str,
user: User,
) -> tuple[Question | None, ZaakWithApiGroup | None]: # noqa: E704
...

def get_fetch_parameters(
self,
request=None,
user: User | None = None,
use_vestigingsnummer: bool = False,
) -> FetchParameters | None: # noqa: E704
...


class KlantContactMomentBaseView(
CommonPageMixin, BaseBreadcrumbMixin, KlantContactMomentAccessMixin, TemplateView
):
def get_kcm_data(
self,
kcm: KlantContactMoment,
local_kcm_mapping: dict[str, KlantContactMomentAnswer] | None = None,
) -> KCMDict:
data = {
"registered_date": kcm.contactmoment.registratiedatum,
"channel": kcm.contactmoment.kanaal.title(),
"text": kcm.contactmoment.tekst,
"url": reverse("cases:contactmoment_detail", kwargs={"kcm_uuid": kcm.uuid}),
# eSuite extra
"identificatie": kcm.contactmoment.identificatie,
"type": kcm.contactmoment.type,
"status": Status.safe_label(kcm.contactmoment.status, _("Onbekend")),
"antwoord": kcm.contactmoment.antwoord,
"new_answer_available": contactmoment_has_new_answer(
kcm.contactmoment, local_kcm_mapping=local_kcm_mapping
),
}

data["onderwerp"] = self.get_kcm_subject(kcm)

return data
def get_service(self) -> VragenService:
# TODO: Refactor to support both OpenKlant2 and eSuite services at once
return eSuiteVragenService()

def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx["anchors"] = self.get_anchors()
return ctx

def get_kcm_subject(
self,
kcm: KlantContactMoment,
) -> str | None:
"""
Determine the subject (`onderwerp`) of a `KlantContactMoment.contactmoment`:
1. replace e-suite subject code with corresponding OIP configured subject or
2. return the first OIP subject if multiple subjects are mapped to the same
e-suite code or
3. return the the e-suite subject code if no mapping exists
"""
e_suite_subject_code = getattr(kcm.contactmoment, "onderwerp", "")

try:
subject = ContactFormSubject.objects.get(subject_code=e_suite_subject_code)
except ContactFormSubject.MultipleObjectsReturned as exc:
logger.warning(
"Multiple OIP subjects mapped to the same e-suite subject code for ",
"contactmoment %s; using the first one",
kcm.contactmoment.url,
exc_info=exc,
)
return ContactFormSubject.objects.first().subject
except ContactFormSubject.DoesNotExist as exc:
logger.warning(
"Could not determine OIP subject for contactmoment %s; "
"falling back on e-suite subject code ('onderwerp')",
kcm.contactmoment.url,
exc_info=exc,
def get_fetch_params(self, service: VragenService):
if not (
fetch_params := service.get_fetch_parameters(
self.request, use_vestigingsnummer=True
)
return e_suite_subject_code
):
raise ValueError("User has no bsn or kvk attributes")

return subject.subject
return fetch_params


class KlantContactMomentListView(
Expand All @@ -174,30 +131,16 @@ def get_anchors(self) -> list:

def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)

kcms = fetch_klantcontactmomenten(
**get_fetch_parameters(self.request, use_vestigingsnummer=True)
service = self.get_service()
questions = service.list_questions(
self.get_fetch_params(service), user=self.request.user
)

klant_config = OpenKlantConfig.get_solo()
if exclude_range := klant_config.exclude_contactmoment_kanalen:
kcms = [
item
for item in kcms
if glom(item, "contactmoment.kanaal") not in exclude_range
]

ctx["contactmomenten"] = [
self.get_kcm_data(
kcm,
local_kcm_mapping=get_kcm_answer_mapping(
[kcm.contactmoment for kcm in kcms], self.request.user
),
)
for kcm in kcms
QuestionValidator.validate_python(q) for q in questions
]
paginator_dict = self.paginate_with_context(ctx["contactmomenten"])
ctx.update(paginator_dict)

return ctx


Expand All @@ -219,58 +162,37 @@ def get_anchors(self) -> list:

def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
service = self.get_service()

kcm = fetch_klantcontactmoment(
kwargs["kcm_uuid"],
**get_fetch_parameters(self.request, use_vestigingsnummer=True),
kcm, zaak = service.retrieve_question(
self.get_fetch_params(service), kwargs["kcm_uuid"], user=self.request.user
)

if not kcm:
raise Http404()

local_kcm, is_created = KlantContactMomentAnswer.objects.get_or_create( # noqa
user=self.request.user, contactmoment_url=kcm.contactmoment.url
QuestionValidator.validate_python(kcm)

local_kcm, created = KlantContactMomentAnswer.objects.get_or_create( # noqa
user=self.request.user, contactmoment_url=kcm["case_detail_url"]
)
if not local_kcm.is_seen:
local_kcm.is_seen = True
local_kcm.save()

zaak = None
case_url = None
ctx["zaak"] = None
if client := build_contactmomenten_client():
ocm = client.retrieve_objectcontactmoment(kcm.contactmoment, "zaak")
if ocm and ocm.object_type == "zaak":
zaak_url = ocm.object
groups = list(ZGWApiGroupConfig.objects.all())
proxy = MultiZgwClientProxy([group.zaken_client for group in groups])
proxy_response = proxy.fetch_case_by_url_no_cache(zaak_url)
cases_found = proxy_response.truthy_responses
if (case_count := len(cases_found)) == 0:
logger.error(
"Unable to find matched contactmomenten zaak in any zgw backend"
)
else:
zaak, client = cases_found[0].result, cases_found[0].client
group = ZGWApiGroupConfig.objects.resolve_group_from_hints(
client=client
)
case_url = reverse(
"cases:case_detail",
kwargs={
"object_id": str(zaak.uuid),
"api_group_id": group.id,
},
)
ctx["zaak"] = zaak
if case_count > 1:
# In principle this should not happen, a zaak should be stored in
# exactly one backend. But: https://www.xkcd.com/2200/
# We should at least receive a ping if this happens.
logger.error("Found one zaak in multiple backends")

contactmoment: KCMDict = self.get_kcm_data(kcm)
contactmoment = kcm
ctx["contactmoment"] = contactmoment
ctx["zaak"] = zaak.zaak if zaak else None
case_url = (
reverse(
"cases:case_detail",
kwargs={
"object_id": str(zaak.zaak.uuid),
"api_group_id": zaak.api_group.id,
},
)
if zaak
else None
)
ctx["metrics"] = [
{
"label": _("Status: "),
Expand All @@ -282,7 +204,7 @@ def get_context_data(self, **kwargs):
},
{
"label": _("Vraag nummer: "),
"value": contactmoment["identificatie"],
"value": contactmoment["identification"],
},
{
"label": _("Contact gehad via: "),
Expand Down Expand Up @@ -318,11 +240,12 @@ class KlantContactMomentRedirectView(KlantContactMomentAccessMixin, View):
"""

def get(self, request, *args, **kwargs):
klanten_client = build_klanten_client()
contactmoment_client = build_contactmomenten_client()
vragen_service: VragenService = eSuiteVragenService()
klanten_service: KlantenService = eSuiteKlantenService()
fetch_params = klanten_service.get_fetch_parameters(self.request)

klant = klanten_client.retrieve_klant(**get_fetch_parameters(self.request))
kcms = contactmoment_client.retrieve_klantcontactmomenten_for_klant(klant)
klant = klanten_service.retrieve_klant(**fetch_params)
kcms = vragen_service.retrieve_klantcontactmomenten_for_klant(klant)

if not kcms:
raise Http404
Expand Down
Loading
Loading