Skip to content

Commit 1310a23

Browse files
committed
✨ [#2149] Implement new answer indicator for questions in aanvraag detail
replaced KlantCntactMomentLocal.klantcontactmoment_url with contactmoment_url, so that we can easily get the local mapping without having to fetch KlantContactMoment resources
1 parent 9d21db9 commit 1310a23

File tree

10 files changed

+75
-34
lines changed

10 files changed

+75
-34
lines changed

src/open_inwoner/accounts/views/contactmoments.py

+8-3
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,9 @@ def get_kcm_data(
9191
timedelta(days=settings.CONTACTMOMENT_NEW_DAYS),
9292
)
9393
if local_kcm_mapping:
94-
is_seen = getattr(local_kcm_mapping.get(kcm.url), "is_seen", False)
94+
is_seen = getattr(
95+
local_kcm_mapping.get(kcm.contactmoment.url), "is_seen", False
96+
)
9597
else:
9698
# In the detail view, this is automatically true
9799
is_seen = True
@@ -178,7 +180,10 @@ def get_context_data(self, **kwargs):
178180
)
179181
ctx["contactmomenten"] = [
180182
self.get_kcm_data(
181-
kcm, local_kcm_mapping=get_local_kcm_mapping(kcms, self.request.user)
183+
kcm,
184+
local_kcm_mapping=get_local_kcm_mapping(
185+
[kcm.contactmoment for kcm in kcms], self.request.user
186+
),
182187
)
183188
for kcm in kcms
184189
]
@@ -215,7 +220,7 @@ def get_context_data(self, **kwargs):
215220
raise Http404()
216221

217222
local_kcm, is_created = KlantContactMomentLocal.objects.get_or_create( # noqa
218-
user=self.request.user, klantcontactmoment_url=kcm.url
223+
user=self.request.user, contactmoment_url=kcm.contactmoment.url
219224
)
220225
if not local_kcm.is_seen:
221226
local_kcm.is_seen = True

src/open_inwoner/cms/cases/views/status.py

+11-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,11 @@
2222

2323
from open_inwoner.openklant.clients import build_client as build_client_openklant
2424
from open_inwoner.openklant.models import OpenKlantConfig
25-
from open_inwoner.openklant.wrap import get_fetch_parameters
25+
from open_inwoner.openklant.wrap import (
26+
contactmoment_has_new_answer,
27+
get_fetch_parameters,
28+
get_local_kcm_mapping,
29+
)
2630
from open_inwoner.openzaak.api_models import Status, StatusType, Zaak
2731
from open_inwoner.openzaak.clients import (
2832
ZakenClient,
@@ -155,6 +159,12 @@ def get_context_data(self, **kwargs):
155159
questions = [ocm.contactmoment for ocm in objectcontactmomenten]
156160
questions.sort(key=lambda q: q.registratiedatum, reverse=True)
157161

162+
local_mapping = get_local_kcm_mapping(questions, self.request.user)
163+
for question in questions:
164+
question.new_answer_available = contactmoment_has_new_answer(
165+
question, local_mapping
166+
)
167+
158168
statustypen = []
159169
if catalogi_client := build_client_openzaak("catalogi"):
160170
statustypen = catalogi_client.fetch_status_types_no_cache(

src/open_inwoner/openklant/admin.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ class KlantContactMomentLocalAdmin(admin.ModelAdmin):
7878
"user__first_name",
7979
"user__last_name",
8080
"user__email",
81-
"klantcontactmoment_url",
81+
"contactmoment_url",
8282
]
8383
list_filter = ["is_seen"]
84-
list_display = ["user", "klantcontactmoment_url", "is_seen"]
84+
list_display = ["user", "contactmoment_url", "is_seen"]

src/open_inwoner/openklant/migrations/0009_klantcontactmomentlocal.py

+4-7
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Generated by Django 4.2.10 on 2024-03-15 09:43
1+
# Generated by Django 4.2.10 on 2024-03-15 12:57
22

33
from django.conf import settings
44
from django.db import migrations, models
@@ -26,12 +26,8 @@ class Migration(migrations.Migration):
2626
),
2727
),
2828
(
29-
"klantcontactmoment_url",
30-
models.URLField(
31-
max_length=1000,
32-
unique=True,
33-
verbose_name="KlantContactMoment URL",
34-
),
29+
"contactmoment_url",
30+
models.URLField(max_length=1000, verbose_name="ContactMoment URL"),
3531
),
3632
(
3733
"is_seen",
@@ -55,6 +51,7 @@ class Migration(migrations.Migration):
5551
options={
5652
"verbose_name": "KlantContactMoment",
5753
"verbose_name_plural": "KlantContactMomenten",
54+
"unique_together": {("user", "contactmoment_url")},
5855
},
5956
),
6057
]

src/open_inwoner/openklant/models.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -139,8 +139,8 @@ class KlantContactMomentLocal(models.Model):
139139
"This is the user that asked the question to which this is an answer."
140140
),
141141
)
142-
klantcontactmoment_url = models.URLField(
143-
verbose_name=_("KlantContactMoment URL"), unique=True, max_length=1000
142+
contactmoment_url = models.URLField(
143+
verbose_name=_("ContactMoment URL"), max_length=1000
144144
)
145145
is_seen = models.BooleanField(
146146
verbose_name=_("Is seen"),
@@ -151,3 +151,4 @@ class KlantContactMomentLocal(models.Model):
151151
class Meta:
152152
verbose_name = _("KlantContactMoment")
153153
verbose_name_plural = _("KlantContactMomenten")
154+
unique_together = [["user", "contactmoment_url"]]

src/open_inwoner/openklant/tests/factories.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,5 @@ class KlantContactMomentLocalFactory(factory.django.DjangoModelFactory):
2121
class Meta:
2222
model = "openklant.KlantContactMomentLocal"
2323

24-
klantcontactmoment_url = factory.Faker("url")
24+
contactmoment_url = factory.Faker("url")
2525
user = factory.SubFactory(UserFactory)

src/open_inwoner/openklant/tests/test_helpers.py

+9-5
Original file line numberDiff line numberDiff line change
@@ -24,19 +24,23 @@ def test_get_local_kcm_mapping(self, m):
2424
kcms = fetch_klantcontactmomenten(user_bsn=data.user.bsn)
2525

2626
with self.subTest("running the first time will create KlantContactMomentLocal"):
27-
mapping = get_local_kcm_mapping(kcms, data.user)
27+
mapping = get_local_kcm_mapping(
28+
[kcm.contactmoment for kcm in kcms], data.user
29+
)
2830

2931
self.assertEqual(KlantContactMomentLocal.objects.count(), 1)
3032

3133
kcm_local = KlantContactMomentLocal.objects.get()
3234

33-
self.assertEqual(mapping, {kcms[0].url: kcm_local})
35+
self.assertEqual(mapping, {kcms[0].contactmoment.url: kcm_local})
3436
self.assertEqual(kcm_local.user, data.user)
35-
self.assertEqual(kcm_local.klantcontactmoment_url, kcms[0].url)
37+
self.assertEqual(kcm_local.contactmoment_url, kcms[0].contactmoment.url)
3638
self.assertEqual(kcm_local.is_seen, False)
3739

3840
with self.subTest("running function again will ignore existing entries"):
39-
mapping = get_local_kcm_mapping(kcms, data.user)
41+
mapping = get_local_kcm_mapping(
42+
[kcm.contactmoment for kcm in kcms], data.user
43+
)
4044

4145
self.assertEqual(KlantContactMomentLocal.objects.count(), 1)
42-
self.assertEqual(mapping, {kcms[0].url: kcm_local})
46+
self.assertEqual(mapping, {kcms[0].contactmoment.url: kcm_local})

src/open_inwoner/openklant/tests/test_views.py

+8-4
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,9 @@ def test_list_for_bsn(self, m, mock_get_local_kcm_mapping):
8686
kcm.contactmoment = factory(ContactMoment, data.contactmoment)
8787
kcm.klant = factory(Klant, data.klant)
8888

89-
mock_get_local_kcm_mapping.assert_called_once_with([kcm], data.user)
89+
mock_get_local_kcm_mapping.assert_called_once_with(
90+
[kcm.contactmoment], data.user
91+
)
9092

9193
status_item = response.pyquery.find(f"p:contains('{_('Status')}')").parent()
9294

@@ -100,7 +102,7 @@ def test_list_for_bsn_new_answer_available(self, m, mock_get_local_kcm_mapping):
100102
mock_get_local_kcm_mapping.return_value = {
101103
data.klant_contactmoment["url"]: KlantContactMomentLocalFactory.create(
102104
user=data.user,
103-
klantcontactmoment_url=data.klant_contactmoment["url"],
105+
contactmoment_url=data.klant_contactmoment["contactmoment"],
104106
is_seen=False,
105107
)
106108
}
@@ -136,7 +138,9 @@ def test_list_for_bsn_new_answer_available(self, m, mock_get_local_kcm_mapping):
136138
kcm.contactmoment = factory(ContactMoment, data.contactmoment)
137139
kcm.klant = factory(Klant, data.klant)
138140

139-
mock_get_local_kcm_mapping.assert_called_once_with([kcm], data.user)
141+
mock_get_local_kcm_mapping.assert_called_once_with(
142+
[kcm.contactmoment], data.user
143+
)
140144

141145
status_item = response.pyquery.find(f"p:contains('{_('Status')}')").parent()
142146

@@ -300,7 +304,7 @@ def test_show_detail_for_bsn_with_zaak(self, m, mock_get_local_kcm_mapping):
300304
)
301305

302306
kcm_local = KlantContactMomentLocal.objects.get(
303-
klantcontactmoment_url=data.klant_contactmoment["url"]
307+
contactmoment_url=data.klant_contactmoment["contactmoment"]
304308
)
305309

306310
self.assertEqual(kcm_local.user, data.user)

src/open_inwoner/openklant/wrap.py

+25-9
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
import logging
2+
from datetime import timedelta
23
from typing import List, Optional
34

5+
from django.conf import settings
6+
47
from open_inwoner.accounts.models import User
58
from open_inwoner.kvk.branches import get_kvk_branch_number
6-
from open_inwoner.openklant.api_models import KlantContactMoment
9+
from open_inwoner.openklant.api_models import ContactMoment, KlantContactMoment
710
from open_inwoner.openklant.clients import build_client
811
from open_inwoner.openklant.models import KlantContactMomentLocal, OpenKlantConfig
12+
from open_inwoner.utils.time import instance_is_new
913

1014
logger = logging.getLogger(__name__)
1115

@@ -99,28 +103,40 @@ def get_fetch_parameters(request, use_vestigingsnummer: bool = False) -> dict:
99103

100104

101105
def get_local_kcm_mapping(
102-
kcms: list[KlantContactMoment], user: User
106+
contactmomenten: list[ContactMoment],
107+
user: User,
103108
) -> dict[str, KlantContactMomentLocal]:
109+
to_create = []
104110
existing_kcms = set(
105111
KlantContactMomentLocal.objects.filter(user=user).values_list(
106-
"klantcontactmoment_url", flat=True
112+
"contactmoment_url", flat=True
107113
)
108114
)
109-
110-
to_create = []
111-
for kcm in kcms:
112-
if kcm.url in existing_kcms:
115+
for contactmoment in contactmomenten:
116+
if contactmoment.url in existing_kcms:
113117
continue
114118

115119
to_create.append(
116-
KlantContactMomentLocal(user=user, klantcontactmoment_url=kcm.url)
120+
KlantContactMomentLocal(user=user, contactmoment_url=contactmoment.url)
117121
)
118122

119123
KlantContactMomentLocal.objects.bulk_create(to_create)
120124

121125
kcm_answer_mapping = {
122-
kcm_answer.klantcontactmoment_url: kcm_answer
126+
kcm_answer.contactmoment_url: kcm_answer
123127
for kcm_answer in KlantContactMomentLocal.objects.filter(user=user)
124128
}
125129

126130
return kcm_answer_mapping
131+
132+
133+
def contactmoment_has_new_answer(
134+
contactmoment: ContactMoment, local_kcm_mapping: dict[str, KlantContactMomentLocal]
135+
) -> bool:
136+
is_new = instance_is_new(
137+
contactmoment,
138+
"registratiedatum",
139+
timedelta(days=settings.CONTACTMOMENT_NEW_DAYS),
140+
)
141+
is_seen = getattr(local_kcm_mapping.get(contactmoment.url), "is_seen", False)
142+
return bool(contactmoment.antwoord) and is_new and not is_seen

src/open_inwoner/templates/pages/questions/questions.html

+4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ <h2 class="h3 contactmomenten__title">{% trans "Vragen" %}</h2>
55
{% for contactmoment in questions %}
66
{# redirect to fetch klantcontactmoment from contactmoment #}
77
<a href="{% url 'cases:kcm_redirect' contactmoment.uuid %}" class="card card--compact card--stretch card--toggle-hide contactmomenten__link">
8+
{% if contactmoment.new_answer_available %}
9+
{% translate "Nieuw antwoord beschikbaar" as new_answer_text %}
10+
{% include "components/StatusIndicator/StatusIndicator.html" with status_indicator="info" status_indicator_text=new_answer_text only %}
11+
{% endif %}
812
<div class="card__body">
913
{% render_list %}
1014
<span class="contactmomenten-list">

0 commit comments

Comments
 (0)