-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
17 changed files
with
424 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
85 changes: 85 additions & 0 deletions
85
src/open_inwoner/apimock/apis/qmatic/qmatic-api/appointments.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
[ | ||
{ | ||
"notifications": [], | ||
"meta": { | ||
"start": "", | ||
"end": "", | ||
"totalResults": 1, | ||
"offset": null, | ||
"limit": null, | ||
"fields": "", | ||
"arguments": {} | ||
}, | ||
"appointment": { | ||
"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 | ||
} | ||
} | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,4 +19,5 @@ def get_config_fields(self): | |
"questions", | ||
"ssd", | ||
"newsletters", | ||
"appointments", | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
22 changes: 22 additions & 0 deletions
22
src/open_inwoner/cms/profile/migrations/0009_profileconfig_appointments.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
# Generated by Django 4.2.10 on 2024-03-18 13:13 | ||
|
||
from django.db import migrations, models | ||
|
||
|
||
class Migration(migrations.Migration): | ||
|
||
dependencies = [ | ||
("profile", "0008_profileconfig_newsletters"), | ||
] | ||
|
||
operations = [ | ||
migrations.AddField( | ||
model_name="profileconfig", | ||
name="appointments", | ||
field=models.BooleanField( | ||
default=False, | ||
help_text="Designates whether 'Mijn afspraken' section is rendered or not.", | ||
verbose_name="Mijn afspraken", | ||
), | ||
), | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
default_app_config = "open_inwoner.qmatic.apps.QmaticConfig" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
from django.contrib import admin | ||
|
||
from solo.admin import SingletonModelAdmin | ||
|
||
from .models import QmaticConfig | ||
|
||
|
||
|
||
@admin.register(QmaticConfig) | ||
class QmaticConfigAdmin(SingletonModelAdmin): | ||
pass |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
from django.apps import AppConfig | ||
|
||
|
||
class QmaticConfig(AppConfig): | ||
name = "open_inwoner.qmatic" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
from datetime import date, datetime, time | ||
from typing import TypedDict | ||
from zoneinfo import ZoneInfo | ||
|
||
from ape_pie.client import APIClient | ||
from dateutil.parser import isoparse | ||
from zgw_consumers.client import build_client | ||
from zgw_consumers.models import Service | ||
|
||
from .exceptions import QmaticException | ||
from .models import QmaticConfig | ||
|
||
# API DATA DEFINITIONS | ||
|
||
|
||
class ServiceDict(TypedDict): | ||
publicId: str | ||
name: str | ||
# could be float too in theory, documentation is not specific (it gives an int example) | ||
duration: int | ||
additionalCustomerDuration: int | ||
custom: str | None | ||
|
||
|
||
class FullServiceDict(ServiceDict): | ||
active: bool | ||
publicEnabled: bool | ||
created: int | ||
updated: int | ||
|
||
|
||
class ServiceGroupDict(TypedDict): | ||
services: list[ServiceDict] | ||
|
||
|
||
class BranchDict(TypedDict): | ||
branchPublicId: str | ||
branchName: str | ||
serviceGroups: list[ServiceGroupDict] | ||
|
||
|
||
class BranchDetailDict(TypedDict): | ||
name: str | ||
publicId: str | ||
phone: str | ||
email: str | ||
branchPrefix: str | None | ||
|
||
addressLine1: str | None | ||
addressLine2: str | None | ||
addressZip: str | None | ||
addressCity: str | None | ||
addressState: str | None | ||
addressCountry: str | None | ||
|
||
latitude: float | None | ||
longitude: float | None | ||
timeZone: str | ||
fullTimeZone: str | ||
custom: str | None | ||
created: int | ||
updated: int | ||
|
||
|
||
class Appointment(TypedDict): | ||
services: list[ServiceDict] | ||
title: str | ||
start: datetime | ||
end: datetime | ||
created: int | ||
updated: int | ||
publicId: str | ||
branch: BranchDetailDict | ||
notes: str | None | ||
|
||
|
||
class NoServiceConfigured(RuntimeError): | ||
pass | ||
|
||
|
||
# API CLIENT IMPLEMENTATIONS, per major version of the API | ||
|
||
|
||
def QmaticClient() -> "Client": | ||
""" | ||
Create a Qmatic client instance from the database configuration. | ||
""" | ||
config = QmaticConfig.get_solo() | ||
assert isinstance(config, QmaticConfig) | ||
if (service := config.service) is None: | ||
raise NoServiceConfigured("No Qmatic service defined, aborting!") | ||
assert isinstance(service, Service) | ||
return build_client(service, client_factory=Client) | ||
|
||
|
||
def startswith_version(url: str) -> bool: | ||
if url.startswith("v1/"): | ||
return True | ||
if url.startswith("v2/"): | ||
return True | ||
return False | ||
|
||
|
||
class Client(APIClient): | ||
""" | ||
Client implementation for Qmatic. | ||
""" | ||
|
||
def __init__(self, *args, **kwargs): | ||
super().__init__(*args, **kwargs) | ||
self.headers["Content-Type"] = "application/json" | ||
|
||
def request(self, method: str, url: str, *args, **kwargs): | ||
# ensure there is a version identifier in the URL | ||
if not startswith_version(url): | ||
url = f"v1/{url}" | ||
|
||
response = super().request(method, url, *args, **kwargs) | ||
|
||
if response.status_code == 500: | ||
error_msg = response.headers.get( | ||
"error_message", response.content.decode("utf-8") | ||
) | ||
raise QmaticException( | ||
f"Server error (HTTP {response.status_code}): {error_msg}" | ||
) | ||
|
||
return response | ||
|
||
def list_appointments_for_customer(self, customer_publicid: str) -> list[Appointment]: | ||
endpoint = f"customers/{customer_publicid}/appointments" | ||
response = self.get(endpoint) | ||
response.raise_for_status() | ||
appointment_list: list[Appointment] = response.json()["appointmentList"] | ||
return appointment_list |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
from django.db.models import TextChoices | ||
from django.utils.translation import gettext_lazy as _ | ||
|
||
|
||
class CustomerFields(TextChoices): | ||
""" | ||
Enum of possible customer field names offered by Qmatic. | ||
Documentation reference: "Book an appointment for a selected branch, service, date | ||
and time" and the "Customer" data model. | ||
.. note:: None of the customer fields are mandatory, but not providing any information | ||
makes it impossible to identify a customer. | ||
The enum values are the fields as they go in the customer record. | ||
""" | ||
|
||
first_name = "firstName", _("First name") # string, max length 200 | ||
last_name = "lastName", _("Last name") # string, max length 200 | ||
email = "email", _("Email address") # string, max length 255 | ||
phone_number = "phone", _("Phone number") # string, max length 50 | ||
address_line_1 = "addressLine1", _( | ||
"Street name and number" | ||
) # string, max length 255 | ||
address_line_2 = "addressLine2", _("Address line 2") # string, max length 255 | ||
address_city = "addressCity", _("City") # string, max length 255 | ||
address_state = "addressState", _("State") # string, max length 255 | ||
address_zip = "addressZip", _("Postal code") # string, max length 255 | ||
address_country = "addressCountry", _("Country") # string, max length 255 | ||
identification_number = "identificationNumber", _( | ||
"Identification number" | ||
) # string, max length 255 | ||
external_id = "externalId", _( | ||
"Unique customer identification/account number" | ||
) # string, max length 255 | ||
""" | ||
A unique customer identification or account number. | ||
This could be used programmatically, but should not be set by the end-user as it is | ||
untrusted input. | ||
""" | ||
birthday = "dateOfBirth", _( | ||
"Birthday" | ||
) # string, ISO-8601 date (return value is number) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
class QmaticException(BaseException): | ||
pass | ||
|
||
|
||
class GracefulQmaticException(QmaticException): | ||
""" | ||
Raise when the program execution can continue with a fallback error. | ||
""" |
Oops, something went wrong.