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

✨ [#2179] Display list of Qmatic appointments in profiel #1099

Merged
merged 5 commits into from
Mar 26, 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
130 changes: 130 additions & 0 deletions src/open_inwoner/accounts/tests/test_profile_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
from django_webtest import WebTest
from pyquery import PyQuery as PQ
from webtest import Upload
from zgw_consumers.constants import APITypes
from zgw_consumers.test.factories import ServiceFactory

from open_inwoner.accounts.choices import StatusChoices
from open_inwoner.cms.profile.cms_appconfig import ProfileConfig
Expand All @@ -22,6 +24,8 @@
from open_inwoner.openklant.models import OpenKlantConfig
from open_inwoner.pdc.tests.factories import CategoryFactory
from open_inwoner.plans.tests.factories import PlanFactory
from open_inwoner.qmatic.models import QmaticConfig
from open_inwoner.qmatic.tests.factories import AppointmentFactory, BranchDetailFactory
from open_inwoner.utils.logentry import LOG_ACTIONS
from open_inwoner.utils.test import ClearCachesMixin
from open_inwoner.utils.tests.helpers import AssertTimelineLogMixin, create_image_bytes
Expand Down Expand Up @@ -1139,3 +1143,129 @@ def test_render_form_limit_newsletters_to_admin_selection(self, m):

# Second field was excluded by `LapostaConfig.limit_list_selection_to`
self.assertNotIn("Nieuwsbrief2: bar", response.text)


@requests_mock.Mocker()
@override_settings(
ROOT_URLCONF="open_inwoner.cms.tests.urls", MIDDLEWARE=PATCHED_MIDDLEWARE
)
class MyAppointmentsTests(ClearCachesMixin, WebTest):
def setUp(self):
super().setUp()

self.appointments_url = reverse("profile:appointments")
self.user = DigidUserFactory()

self.config = QmaticConfig.get_solo()
self.api_root = "https://qmatic.local/api/"
self.service = ServiceFactory.create(
api_root=self.api_root, api_type=APITypes.orc
)
self.config.service = self.service
self.config.save()

self.appointment_passport = AppointmentFactory.build(
title="Aanvraag paspoort",
start="2020-01-01T12:00:00+00:00",
notes="foo",
branch=BranchDetailFactory.build(
name="Hoofdkantoor",
timeZone="Europe/Amsterdam",
addressCity="Amsterdam",
addressLine2="Dam 1",
),
)
self.appointment_idcard = AppointmentFactory.build(
title="Aanvraag ID kaart",
start="2020-03-06T16:30:00+00:00",
notes="bar",
branch=BranchDetailFactory.build(
name="Hoofdkantoor",
timeZone="America/New_York",
addressCity="New York",
addressLine2="Wall Street 1",
),
)

def setUpMocks(self, m):
data = {
"notifications": [],
"meta": {
"start": "",
"end": "",
"totalResults": 1,
"offset": None,
"limit": None,
"fields": "",
"arguments": [],
},
"appointmentList": [
self.appointment_passport.dict(),
self.appointment_idcard.dict(),
],
}
m.get(
f"{self.api_root}v1/customers/externalId/{self.user.email}/appointments",
json=data,
)

def test_do_not_render_list_if_config_is_missing(self, m):
self.config.service = None
self.config.save()

response = self.app.get(self.appointments_url, user=self.user)

self.assertIn(_("Geen afspraken beschikbaar"), response.text)

def test_do_not_render_list_if_no_appointments_are_found(self, m):
m.get(
f"{self.api_root}v1/customers/externalId/{self.user.email}/appointments",
status_code=404,
)

response = self.app.get(self.appointments_url, user=self.user)

self.assertIn(_("Geen afspraken beschikbaar"), response.text)

def test_do_not_render_list_if_validation_error(self, m):
m.get(
f"{self.api_root}v1/customers/externalId/{self.user.email}/appointments",
json={"appointmentList": [{"invalid": "data"}]},
)

response = self.app.get(self.appointments_url, user=self.user)

self.assertIn(_("Geen afspraken beschikbaar"), response.text)

def test_render_list_if_appointments_are_found(self, m):
self.setUpMocks(m)

response = self.app.get(self.appointments_url, user=self.user)

self.assertIn(_("Een overzicht van uw afspraken"), response.text)

cards = response.pyquery(".appointment-info")

self.assertEqual(len(cards), 2)

passport_appointment = PQ(cards[0]).find("ul").children()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it necessary to use PyQuery directly, since you're already using response.pyquery, or just more convenient?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It actually is necessary, it's a bit weird but for some reason I can't use .find and other methods on the element that I get from cards[0] directly

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks familiar: iirc pyquery doesn't wrap recursively like some other api's do.


self.assertEqual(passport_appointment[0].text, "Aanvraag paspoort")
self.assertEqual(
PQ(passport_appointment[1]).text(), "woensdag 1 januari 2020 13:00"
)
self.assertEqual(PQ(passport_appointment[2]).text(), "foo")
self.assertEqual(PQ(passport_appointment[3]).text(), "Locatie\nHoofdkantoor")
self.assertEqual(PQ(passport_appointment[4]).text(), "Amsterdam")
self.assertEqual(PQ(passport_appointment[5]).text(), "Dam 1")

id_card_appointment = PQ(cards[1]).find("ul").children()

self.assertEqual(id_card_appointment[0].text, "Aanvraag ID kaart")
self.assertEqual(
PQ(id_card_appointment[1]).text(), "vrijdag 6 maart 2020 11:30"
)
self.assertEqual(PQ(id_card_appointment[2]).text(), "bar")
self.assertEqual(PQ(id_card_appointment[3]).text(), "Locatie\nHoofdkantoor")
self.assertEqual(PQ(id_card_appointment[4]).text(), "New York")
self.assertEqual(PQ(id_card_appointment[5]).text(), "Wall Street 1")
2 changes: 2 additions & 0 deletions src/open_inwoner/accounts/views/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
from .password_reset import PasswordResetView
from .profile import (
EditProfileView,
MyAppointmentsView,
MyCategoriesView,
MyDataView,
MyNotificationsView,
Expand Down Expand Up @@ -79,6 +80,7 @@
"MyNotificationsView",
"MyProfileView",
"NewsletterSubscribeView",
"MyAppointmentsView",
"CustomRegistrationView",
"NecessaryFieldsUserView",
]
31 changes: 31 additions & 0 deletions src/open_inwoner/accounts/views/profile.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import logging
from collections.abc import Generator
from datetime import date
from typing import Any
Expand Down Expand Up @@ -29,12 +30,15 @@
from open_inwoner.openklant.clients import build_client
from open_inwoner.openklant.wrap import get_fetch_parameters
from open_inwoner.plans.models import Plan
from open_inwoner.qmatic.client import NoServiceConfigured, QmaticClient
from open_inwoner.questionnaire.models import QuestionnaireStep
from open_inwoner.utils.views import CommonPageMixin, LogMixin

from ..forms import BrpUserForm, CategoriesForm, UserForm, UserNotificationsForm
from ..models import Action, User

logger = logging.getLogger(__name__)


class MyProfileView(
LogMixin,
Expand Down Expand Up @@ -343,3 +347,30 @@ def form_valid(self, form):
self.request.user, _("users newsletter subscriptions were modified")
)
return HttpResponseRedirect(self.get_success_url())


class MyAppointmentsView(
LogMixin, LoginRequiredMixin, CommonPageMixin, BaseBreadcrumbMixin, TemplateView
):
template_name = "pages/profile/appointments.html"

def get_context_data(self, **kwargs) -> dict[str, Any]:
context = super().get_context_data(**kwargs)
# TODO email should be verified
try:
client = QmaticClient()
except NoServiceConfigured:
logger.exception("Error occurred while creating Qmatic client")
context["appointments"] = []
else:
context["appointments"] = client.list_appointments_for_customer(
self.request.user.email
)
return context

@cached_property
def crumbs(self):
return [
(_("Mijn profiel"), reverse("profile:detail")),
(_("Mijn afspraken"), reverse("profile:appointments")),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
{
"notifications": [],
"meta": {
"start": "",
"end": "",
"totalResults": 1,
"offset": null,
"limit": null,
"fields": "",
"arguments": {}
},
"appointmentList": [
{
"services": [
{
"additionalCustomerDuration": 0,
"duration": 5,
"updated": 1475589228781,
"created": 1475589228595,
"name": "Product 1",
"publicId": "1e0c3d34acb5a4ad0133b2927959e8",
"active": true,
"publicEnabled": true,
"custom": null
}
],
"allDay": false,
"status": 20,
"resource": {
"name": "Resource 1"
},
"customers": [
{
"dateOfBirth": -2206357200000,
"addressState": "Zuid Holland",
"lastName": "Achternaam",
"phone": "06-11223344",
"addressCity": "Plaatsnaam",
"externalId": null,
"addressLine2": null,
"addressLine1": "Straatnaam 1",
"updated": null,
"created": 1478619026558,
"email": "[email protected]",
"name": "Voornaam Achternaam",
"publicId": "f9c6a5fa1b978b4181accd7a6434e4b9",
"firstName": "Voornaam",
"addressCountry": "Nederland",
"custom": null,
"identificationNumber": "1234567890",
"addressZip": "1111AB"
}
],
"blocking": false,
"title": "Online booking",
"start": "2016-11-10T12:30:00.000+00:00",
"created": 1478618716117,
"updated": 1478619027200,
"publicId": "d50517a0ae88cdbc495f7a32e011cb",
"branch": {
"addressState": null,
"phone": null,
"addressCity": "City",
"fullTimeZone": "Europe/Amsterdam",
"timeZone": "Europe/Amsterdam",
"addressLine2": "Street 1",
"addressLine1": "Branch 1",
"updated": 1475589234069,
"created": 1475589234008,
"email": null,
"name": "Branch 1",
"publicId": "f364d92b7fa07a48c4ecc862de30",
"longitude": null,
"branchPrefix": null,
"latitude": null,
"addressCountry": "Netherlands",
"custom": null,
"addressZip": "1111 AA"
},
"notes": "Geboekt via internet",
"end": "2016-11-10T12:35:00.000+00:00",
"custom": null
},
{
"services": [
{
"additionalCustomerDuration": 0,
"duration": 5,
"updated": 1475589228781,
"created": 1475589228595,
"name": "Aanvraag paspoort",
"publicId": "f5f0b91cfa6b51c006b49b301abe67",
"active": true,
"publicEnabled": true,
"custom": null
}
],
"allDay": false,
"status": 20,
"resource": {
"name": "Resource 1"
},
"customers": [
{
"dateOfBirth": -2206357200000,
"addressState": "Zuid Holland",
"lastName": "Achternaam",
"phone": "06-11223344",
"addressCity": "Plaatsnaam",
"externalId": null,
"addressLine2": null,
"addressLine1": "Straatnaam 1",
"updated": null,
"created": 1478619026558,
"email": "[email protected]",
"name": "Voornaam Achternaam",
"publicId": "b0e1d5eb8931f9bfee08e81b28e112",
"firstName": "Voornaam",
"addressCountry": "Nederland",
"custom": null,
"identificationNumber": "1234567890",
"addressZip": "1111AB"
}
],
"blocking": false,
"title": "Aanvraag paspoort",
"start": "2018-03-05T16:30:00.000+00:00",
"created": 1478618716117,
"updated": 1478619027200,
"publicId": "80813a4ff5b6dd496dbb4034fd5e37",
"branch": {
"addressState": null,
"phone": null,
"addressCity": "Amsterdam",
"fullTimeZone": "Europe/Amsterdam",
"timeZone": "Europe/Amsterdam",
"addressLine2": "Dam 1",
"addressLine1": "Vestigingsnaam",
"updated": 1475589234069,
"created": 1475589234008,
"email": null,
"name": "Vestigingsnaam",
"publicId": "f364d92b7fa07a48c4ecc862de30",
"longitude": null,
"branchPrefix": null,
"latitude": null,
"addressCountry": "Netherlands",
"custom": null,
"addressZip": "1111 AA"
},
"notes": "Geboekt via internet",
"end": "2018-03-05T16:30:00.000+00:00",
"custom": null
}
]
}
1 change: 1 addition & 0 deletions src/open_inwoner/cms/profile/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@ def get_config_fields(self):
"questions",
"ssd",
"newsletters",
"appointments",
)
5 changes: 5 additions & 0 deletions src/open_inwoner/cms/profile/cms_appconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,8 @@ class ProfileConfig(AppHookConfig):
default=False,
help_text=_("Designates whether 'Nieuwsbrieven' section is rendered or not."),
)
appointments = models.BooleanField(
verbose_name=_("Mijn afspraken"),
default=False,
help_text=_("Designates whether 'Mijn afspraken' section is rendered or not."),
)
Loading
Loading