diff --git a/src/open_inwoner/accounts/views/contactmoments.py b/src/open_inwoner/accounts/views/contactmoments.py index 6467f1ebaa..a757f11ee2 100644 --- a/src/open_inwoner/accounts/views/contactmoments.py +++ b/src/open_inwoner/accounts/views/contactmoments.py @@ -91,7 +91,9 @@ def get_kcm_data( timedelta(days=settings.CONTACTMOMENT_NEW_DAYS), ) if local_kcm_mapping: - is_seen = getattr(local_kcm_mapping.get(kcm.url), "is_seen", False) + is_seen = getattr( + local_kcm_mapping.get(kcm.contactmoment.url), "is_seen", False + ) else: # In the detail view, this is automatically true is_seen = True @@ -178,7 +180,10 @@ def get_context_data(self, **kwargs): ) ctx["contactmomenten"] = [ self.get_kcm_data( - kcm, local_kcm_mapping=get_local_kcm_mapping(kcms, self.request.user) + kcm, + local_kcm_mapping=get_local_kcm_mapping( + [kcm.contactmoment for kcm in kcms], self.request.user + ), ) for kcm in kcms ] @@ -215,7 +220,7 @@ def get_context_data(self, **kwargs): raise Http404() local_kcm, is_created = KlantContactMomentLocal.objects.get_or_create( # noqa - user=self.request.user, klantcontactmoment_url=kcm.url + user=self.request.user, contactmoment_url=kcm.contactmoment.url ) if not local_kcm.is_seen: local_kcm.is_seen = True diff --git a/src/open_inwoner/cms/cases/views/status.py b/src/open_inwoner/cms/cases/views/status.py index b85f096ca6..e134d738c3 100644 --- a/src/open_inwoner/cms/cases/views/status.py +++ b/src/open_inwoner/cms/cases/views/status.py @@ -22,7 +22,11 @@ from open_inwoner.openklant.clients import build_client as build_client_openklant from open_inwoner.openklant.models import OpenKlantConfig -from open_inwoner.openklant.wrap import get_fetch_parameters +from open_inwoner.openklant.wrap import ( + contactmoment_has_new_answer, + get_fetch_parameters, + get_local_kcm_mapping, +) from open_inwoner.openzaak.api_models import Status, StatusType, Zaak from open_inwoner.openzaak.clients import ( ZakenClient, @@ -155,6 +159,12 @@ def get_context_data(self, **kwargs): questions = [ocm.contactmoment for ocm in objectcontactmomenten] questions.sort(key=lambda q: q.registratiedatum, reverse=True) + local_mapping = get_local_kcm_mapping(questions, self.request.user) + for question in questions: + question.new_answer_available = contactmoment_has_new_answer( + question, local_mapping + ) + statustypen = [] if catalogi_client := build_client_openzaak("catalogi"): statustypen = catalogi_client.fetch_status_types_no_cache( diff --git a/src/open_inwoner/openklant/admin.py b/src/open_inwoner/openklant/admin.py index 824be807d7..c4871afc75 100644 --- a/src/open_inwoner/openklant/admin.py +++ b/src/open_inwoner/openklant/admin.py @@ -78,7 +78,7 @@ class KlantContactMomentLocalAdmin(admin.ModelAdmin): "user__first_name", "user__last_name", "user__email", - "klantcontactmoment_url", + "contactmoment_url", ] list_filter = ["is_seen"] - list_display = ["user", "klantcontactmoment_url", "is_seen"] + list_display = ["user", "contactmoment_url", "is_seen"] diff --git a/src/open_inwoner/openklant/migrations/0009_klantcontactmomentlocal.py b/src/open_inwoner/openklant/migrations/0009_klantcontactmomentlocal.py index b57f9d1292..8d0e460e9b 100644 --- a/src/open_inwoner/openklant/migrations/0009_klantcontactmomentlocal.py +++ b/src/open_inwoner/openklant/migrations/0009_klantcontactmomentlocal.py @@ -1,4 +1,4 @@ -# Generated by Django 4.2.10 on 2024-03-15 09:43 +# Generated by Django 4.2.10 on 2024-03-15 12:57 from django.conf import settings from django.db import migrations, models @@ -26,12 +26,8 @@ class Migration(migrations.Migration): ), ), ( - "klantcontactmoment_url", - models.URLField( - max_length=1000, - unique=True, - verbose_name="KlantContactMoment URL", - ), + "contactmoment_url", + models.URLField(max_length=1000, verbose_name="ContactMoment URL"), ), ( "is_seen", @@ -55,6 +51,7 @@ class Migration(migrations.Migration): options={ "verbose_name": "KlantContactMoment", "verbose_name_plural": "KlantContactMomenten", + "unique_together": {("user", "contactmoment_url")}, }, ), ] diff --git a/src/open_inwoner/openklant/models.py b/src/open_inwoner/openklant/models.py index 0a82c7e346..b6c8694839 100644 --- a/src/open_inwoner/openklant/models.py +++ b/src/open_inwoner/openklant/models.py @@ -139,8 +139,8 @@ class KlantContactMomentLocal(models.Model): "This is the user that asked the question to which this is an answer." ), ) - klantcontactmoment_url = models.URLField( - verbose_name=_("KlantContactMoment URL"), unique=True, max_length=1000 + contactmoment_url = models.URLField( + verbose_name=_("ContactMoment URL"), max_length=1000 ) is_seen = models.BooleanField( verbose_name=_("Is seen"), @@ -151,3 +151,4 @@ class KlantContactMomentLocal(models.Model): class Meta: verbose_name = _("KlantContactMoment") verbose_name_plural = _("KlantContactMomenten") + unique_together = [["user", "contactmoment_url"]] diff --git a/src/open_inwoner/openklant/tests/factories.py b/src/open_inwoner/openklant/tests/factories.py index 078b1446e9..754310f638 100644 --- a/src/open_inwoner/openklant/tests/factories.py +++ b/src/open_inwoner/openklant/tests/factories.py @@ -21,5 +21,5 @@ class KlantContactMomentLocalFactory(factory.django.DjangoModelFactory): class Meta: model = "openklant.KlantContactMomentLocal" - klantcontactmoment_url = factory.Faker("url") + contactmoment_url = factory.Faker("url") user = factory.SubFactory(UserFactory) diff --git a/src/open_inwoner/openklant/tests/test_helpers.py b/src/open_inwoner/openklant/tests/test_helpers.py index 8b731903ae..f03dbf841c 100644 --- a/src/open_inwoner/openklant/tests/test_helpers.py +++ b/src/open_inwoner/openklant/tests/test_helpers.py @@ -24,19 +24,23 @@ def test_get_local_kcm_mapping(self, m): kcms = fetch_klantcontactmomenten(user_bsn=data.user.bsn) with self.subTest("running the first time will create KlantContactMomentLocal"): - mapping = get_local_kcm_mapping(kcms, data.user) + mapping = get_local_kcm_mapping( + [kcm.contactmoment for kcm in kcms], data.user + ) self.assertEqual(KlantContactMomentLocal.objects.count(), 1) kcm_local = KlantContactMomentLocal.objects.get() - self.assertEqual(mapping, {kcms[0].url: kcm_local}) + self.assertEqual(mapping, {kcms[0].contactmoment.url: kcm_local}) self.assertEqual(kcm_local.user, data.user) - self.assertEqual(kcm_local.klantcontactmoment_url, kcms[0].url) + self.assertEqual(kcm_local.contactmoment_url, kcms[0].contactmoment.url) self.assertEqual(kcm_local.is_seen, False) with self.subTest("running function again will ignore existing entries"): - mapping = get_local_kcm_mapping(kcms, data.user) + mapping = get_local_kcm_mapping( + [kcm.contactmoment for kcm in kcms], data.user + ) self.assertEqual(KlantContactMomentLocal.objects.count(), 1) - self.assertEqual(mapping, {kcms[0].url: kcm_local}) + self.assertEqual(mapping, {kcms[0].contactmoment.url: kcm_local}) diff --git a/src/open_inwoner/openklant/tests/test_views.py b/src/open_inwoner/openklant/tests/test_views.py index a94d17700e..414f9858a3 100644 --- a/src/open_inwoner/openklant/tests/test_views.py +++ b/src/open_inwoner/openklant/tests/test_views.py @@ -86,7 +86,9 @@ def test_list_for_bsn(self, m, mock_get_local_kcm_mapping): kcm.contactmoment = factory(ContactMoment, data.contactmoment) kcm.klant = factory(Klant, data.klant) - mock_get_local_kcm_mapping.assert_called_once_with([kcm], data.user) + mock_get_local_kcm_mapping.assert_called_once_with( + [kcm.contactmoment], data.user + ) status_item = response.pyquery.find(f"p:contains('{_('Status')}')").parent() @@ -100,7 +102,7 @@ def test_list_for_bsn_new_answer_available(self, m, mock_get_local_kcm_mapping): mock_get_local_kcm_mapping.return_value = { data.klant_contactmoment["url"]: KlantContactMomentLocalFactory.create( user=data.user, - klantcontactmoment_url=data.klant_contactmoment["url"], + contactmoment_url=data.klant_contactmoment["contactmoment"], is_seen=False, ) } @@ -136,7 +138,9 @@ def test_list_for_bsn_new_answer_available(self, m, mock_get_local_kcm_mapping): kcm.contactmoment = factory(ContactMoment, data.contactmoment) kcm.klant = factory(Klant, data.klant) - mock_get_local_kcm_mapping.assert_called_once_with([kcm], data.user) + mock_get_local_kcm_mapping.assert_called_once_with( + [kcm.contactmoment], data.user + ) status_item = response.pyquery.find(f"p:contains('{_('Status')}')").parent() @@ -300,7 +304,7 @@ def test_show_detail_for_bsn_with_zaak(self, m, mock_get_local_kcm_mapping): ) kcm_local = KlantContactMomentLocal.objects.get( - klantcontactmoment_url=data.klant_contactmoment["url"] + contactmoment_url=data.klant_contactmoment["contactmoment"] ) self.assertEqual(kcm_local.user, data.user) diff --git a/src/open_inwoner/openklant/wrap.py b/src/open_inwoner/openklant/wrap.py index ffc47a0647..6c10e437be 100644 --- a/src/open_inwoner/openklant/wrap.py +++ b/src/open_inwoner/openklant/wrap.py @@ -1,11 +1,15 @@ import logging +from datetime import timedelta from typing import List, Optional +from django.conf import settings + from open_inwoner.accounts.models import User from open_inwoner.kvk.branches import get_kvk_branch_number -from open_inwoner.openklant.api_models import KlantContactMoment +from open_inwoner.openklant.api_models import ContactMoment, KlantContactMoment from open_inwoner.openklant.clients import build_client from open_inwoner.openklant.models import KlantContactMomentLocal, OpenKlantConfig +from open_inwoner.utils.time import instance_is_new logger = logging.getLogger(__name__) @@ -99,28 +103,40 @@ def get_fetch_parameters(request, use_vestigingsnummer: bool = False) -> dict: def get_local_kcm_mapping( - kcms: list[KlantContactMoment], user: User + contactmomenten: list[ContactMoment], + user: User, ) -> dict[str, KlantContactMomentLocal]: + to_create = [] existing_kcms = set( KlantContactMomentLocal.objects.filter(user=user).values_list( - "klantcontactmoment_url", flat=True + "contactmoment_url", flat=True ) ) - - to_create = [] - for kcm in kcms: - if kcm.url in existing_kcms: + for contactmoment in contactmomenten: + if contactmoment.url in existing_kcms: continue to_create.append( - KlantContactMomentLocal(user=user, klantcontactmoment_url=kcm.url) + KlantContactMomentLocal(user=user, contactmoment_url=contactmoment.url) ) KlantContactMomentLocal.objects.bulk_create(to_create) kcm_answer_mapping = { - kcm_answer.klantcontactmoment_url: kcm_answer + kcm_answer.contactmoment_url: kcm_answer for kcm_answer in KlantContactMomentLocal.objects.filter(user=user) } return kcm_answer_mapping + + +def contactmoment_has_new_answer( + contactmoment: ContactMoment, local_kcm_mapping: dict[str, KlantContactMomentLocal] +) -> bool: + is_new = instance_is_new( + contactmoment, + "registratiedatum", + timedelta(days=settings.CONTACTMOMENT_NEW_DAYS), + ) + is_seen = getattr(local_kcm_mapping.get(contactmoment.url), "is_seen", False) + return bool(contactmoment.antwoord) and is_new and not is_seen diff --git a/src/open_inwoner/templates/pages/questions/questions.html b/src/open_inwoner/templates/pages/questions/questions.html index 86abcf1ec6..8bbdcd78f6 100644 --- a/src/open_inwoner/templates/pages/questions/questions.html +++ b/src/open_inwoner/templates/pages/questions/questions.html @@ -5,6 +5,10 @@