diff --git a/src/open_inwoner/accounts/signals.py b/src/open_inwoner/accounts/signals.py index 6970aaf471..5a0e4c66af 100644 --- a/src/open_inwoner/accounts/signals.py +++ b/src/open_inwoner/accounts/signals.py @@ -1,6 +1,7 @@ import logging from django.contrib.auth.signals import user_logged_in, user_logged_out +from django.db.models.signals import post_save from django.dispatch import receiver from django.urls import reverse from django.utils.translation import gettext as _ @@ -57,7 +58,7 @@ def update_user_on_login(sender, user, request, *args, **kwargs): def _update_user_from_openklant2( - user: User, service: OpenKlant2Service, request + user: User, service: OpenKlant2Service, request: None = None ) -> None: if fetch_params := service.get_fetch_parameters(request=request): partij, created = service.get_or_create_partij_for_user( @@ -68,7 +69,7 @@ def _update_user_from_openklant2( def _update_user_from_esuite( - user: User, service: eSuiteKlantenService, request + user: User, service: eSuiteKlantenService, request: None = None ) -> None: if not (fetch_params := service.get_fetch_parameters(request=request)): return @@ -88,6 +89,38 @@ def _update_eherkenning_user_from_kvk_api(user: User): user.save() +# TODO: Should we also try to fetch pre-existing klant for new user and update? +# The klant could have been created by a different service. +@receiver(post_save, sender=User) +def get_or_create_klant_for_new_user( + sender: type, instance: User, created: bool, **kwargs +) -> None: + if not created: + logger.info("No klanten sync performed because user has just been created") + return + + user = instance + + # OpenKlant2 + try: + service = OpenKlant2Service() + except Exception: + logger.error("OpenKlant2 service failed to build") + else: + _update_user_from_openklant2( + user, + service, + ) + + # eSuite + try: + service = eSuiteKlantenService() + except Exception: + logger.error("eSuiteKlantenService failed to build") + else: + _update_user_from_esuite(user, service) + + @receiver(user_logged_in) def log_user_login(sender, user, request, *args, **kwargs): current_path = request.path diff --git a/src/open_inwoner/accounts/views/signals.py b/src/open_inwoner/accounts/views/signals.py deleted file mode 100644 index c41346763a..0000000000 --- a/src/open_inwoner/accounts/views/signals.py +++ /dev/null @@ -1,74 +0,0 @@ -import logging - -from django.db.models.signals import post_save -from django.dispatch import receiver - -from open_inwoner.accounts.models import User -from open_inwoner.openklant.services import OpenKlant2Service, eSuiteKlantenService - -logger = logging.getLogger(__name__) - - -# TODO: Should we also try to fetch pre-existing klant for new user and update? -# The klant could have been created by a different service. -@receiver(post_save, sender=User) -def get_or_create_klant_for_new_user( - sender: type, instance: User, created: bool, **kwargs -) -> None: - if not created: - logger.info("No klanten sync performed because user has just been created") - return - - user = instance - - # OpenKlant2 - try: - service = OpenKlant2Service() - except Exception: - logger.error("OpenKlant2 service failed to build") - return - - if not ( - fetch_params := service.get_fetch_parameters( - user=user, use_vestigingsnummer=True - ) - ): - return - - partij, partij_created = service.get_or_create_partij_for_user( - fetch_params=fetch_params, user=user - ) - if not partij: - logger.error("Failed to create partij for new user %s", user) - return - - if not partij_created: - service.update_user_from_partij(partij_uuid=partij["uuid"], user=user) - - logger.info("Created partij %s for new user %s", partij, user) - - # eSuite - try: - service = eSuiteKlantenService() - except Exception: - logger.error("eSuiteKlantenService failed to build") - return - - if not ( - fetch_params := service.get_fetch_parameters( - user=user, use_vestigingsnummer=True - ) - ): - return - - klant, klant_created = service.get_or_create_klant( - fetch_params=fetch_params, user=user - ) - if not klant: - logger.error("Failed to create klant for new user %s", user) - return - - if not klant_created: - service.update_user_from_klant(klant, user) - - logger.info("Created klant %s for new user %s", klant, user) diff --git a/src/open_inwoner/cms/cases/views/status.py b/src/open_inwoner/cms/cases/views/status.py index 9cacc7d884..c97f022312 100644 --- a/src/open_inwoner/cms/cases/views/status.py +++ b/src/open_inwoner/cms/cases/views/status.py @@ -7,7 +7,11 @@ from django.conf import settings from django.contrib import messages -from django.core.exceptions import ObjectDoesNotExist, PermissionDenied +from django.core.exceptions import ( + ImproperlyConfigured, + ObjectDoesNotExist, + PermissionDenied, +) from django.http import ( Http404, HttpRequest, @@ -994,43 +998,30 @@ def register_by_api(self, form, config: OpenKlantConfig): except ObjectDoesNotExist: ztc = None - # TODO openklant_config = OpenKlantConfig.get_solo() - service = eSuiteKlantenService(config=openklant_config) - - if klanten_client := service.client: - klant = service.retrieve_klant(**get_fetch_parameters(self.request)) - - if klant: + klant = None + try: + service = eSuiteKlantenService(config=openklant_config) + except (ImproperlyConfigured, RuntimeError): + self.log_system_action("could not build client for klanten API") + else: + klant, created = service.get_or_create_klant( + fetch_params=get_fetch_parameters(self.request), user=self.request.user + ) + if not klant: self.log_system_action( - "retrieved klant for user", user=self.request.user + "could not create klant for user", user=self.request.user ) else: - self.log_system_action( - "could not retrieve klant for user", user=self.request.user - ) - data = { - "bronorganisatie": config.register_bronorganisatie_rsin, - "voornaam": self.request.user.first_name, - "voorvoegselAchternaam": self.request.user.infix, - "achternaam": self.request.user.last_name, - "emailadres": self.request.user.email, - "telefoonnummer": self.request.user.phonenumber, - } - # registering klanten won't work in e-Suite as it always pulls from BRP (but try anyway and fallback to appending details to tekst if fails) - klant = service.create_klant(data) - - if klant: + if created: self.log_system_action( - "created klant for basic authenticated user", + ( + "created klant for basic authenticated user" + if created + else "retrieved klant for user" + ), user=self.request.user, ) - else: - self.log_system_action( - "could not create klant for user", user=self.request.user - ) - else: - self.log_system_action("could not build client for klanten API") # create contact moment question = form.cleaned_data["question"] diff --git a/src/open_inwoner/openklant/api_models.py b/src/open_inwoner/openklant/api_models.py index 58c0803895..5a94f71dd6 100644 --- a/src/open_inwoner/openklant/api_models.py +++ b/src/open_inwoner/openklant/api_models.py @@ -6,13 +6,19 @@ from zgw_consumers.api_models.base import ZGWModel -class KlantCreateData(TypedDict): - bronorganisatie: str - voornaam: str - voorvoegselAchternaam: str - achternaam: str +class KlantWritePayload(TypedDict, total=False): + """The writable fields for en eSuite klant. + + Note this excludes the subjectIdentificatie field which eSuite uses to identify + the user and fetch the remaining personal details from the BRP (such as name, + address, and so forth). It is intended to specify what fields can be written for a + _known_ user. + """ + telefoonnummer: str + telefoonnummerAlternatief: str emailadres: str + toestemmingZaakNotificatiesAlleenDigitaal: bool @dataclass @@ -32,6 +38,7 @@ class Klant(ZGWModel): telefoonnummer: str = "" emailadres: str = "" toestemming_zaak_notificaties_alleen_digitaal: bool | None = None + bedrijfsnaam: str = "" def get_name_display(self): return " ".join( diff --git a/src/open_inwoner/openklant/clients.py b/src/open_inwoner/openklant/clients.py index 96b6459ada..f1381efdcf 100644 --- a/src/open_inwoner/openklant/clients.py +++ b/src/open_inwoner/openklant/clients.py @@ -15,7 +15,6 @@ Klant, KlantContactMoment, KlantContactRol, - KlantCreateData, ObjectContactMoment, ) from .models import OpenKlantConfig @@ -24,68 +23,6 @@ class KlantenClient(APIClient): - def create_klant( - self, - user_bsn: str | None = None, - user_kvk_or_rsin: str | None = None, - vestigingsnummer: str | None = None, - data: KlantCreateData = None, - ) -> Klant | None: - if user_bsn: - return self._create_klant_for_bsn(user_bsn) - - if user_kvk_or_rsin: - return self._create_klant_for_kvk_or_rsin( - user_kvk_or_rsin, vestigingsnummer=vestigingsnummer - ) - - try: - response = self.post("klanten", json=data) - data = get_json_response(response) - except (RequestException, ClientError): - logger.exception("exception while making request") - return - - klant = factory(Klant, data) - - return klant - - def _create_klant_for_bsn(self, user_bsn: str) -> Klant: - payload = {"subjectIdentificatie": {"inpBsn": user_bsn}} - - try: - response = self.post("klanten", json=payload) - data = get_json_response(response) - except (RequestException, ClientError): - logger.exception("exception while making request") - return None - - klant = factory(Klant, data) - - return klant - - def _create_klant_for_kvk_or_rsin( - self, user_kvk_or_rsin: str, *, vestigingsnummer=None - ) -> list[Klant]: - payload = {"subjectIdentificatie": {"innNnpId": user_kvk_or_rsin}} - - if vestigingsnummer: - payload = {"subjectIdentificatie": {"vestigingsNummer": vestigingsnummer}} - - try: - response = self.post( - "klanten", - json=payload, - ) - data = get_json_response(response) - except (RequestException, ClientError): - logger.exception("exception while making request") - return None - - klant = factory(Klant, data) - - return klant - def retrieve_klant( self, user_bsn: str | None = None, user_kvk_or_rsin: str | None = None ) -> Klant | None: diff --git a/src/open_inwoner/openklant/services.py b/src/open_inwoner/openklant/services.py index bdd198b1c7..18c01808ea 100644 --- a/src/open_inwoner/openklant/services.py +++ b/src/open_inwoner/openklant/services.py @@ -31,7 +31,7 @@ Klant, KlantContactMoment, KlantContactRol, - KlantCreateData, + KlantWritePayload, ObjectContactMoment, ) from open_inwoner.openklant.constants import KlantenServiceType, Status @@ -153,7 +153,9 @@ def __init__(self, config: OpenKlantConfig | None = None): "eSuiteKlantenService instance needs a servivce configuration" ) - self.client = build_zgw_client(service=self.service_config) + self.client = build_zgw_client( + service=self.service_config, client_factory=APIClient + ) if not self.client: raise RuntimeError("eSuiteKlantenService instance needs a client") @@ -232,60 +234,35 @@ def create_klant( user_bsn: str | None = None, user_kvk_or_rsin: str | None = None, vestigingsnummer: str | None = None, - data: KlantCreateData = None, + *, + data: KlantWritePayload | None = None, ) -> Klant | None: - if user_bsn: - return self._create_klant_for_bsn(user_bsn) - - if user_kvk_or_rsin: - return self._create_klant_for_kvk_or_rsin( - user_kvk_or_rsin, vestigingsnummer=vestigingsnummer - ) - - try: - response = self.client.post("klanten", json=data) - data = get_json_response(response) - except (RequestException, ClientError): - logger.exception("exception while making request") - return + if sum(bool(arg) for arg in (user_bsn, user_kvk_or_rsin, vestigingsnummer)) > 1: + raise ValueError("Only one argument can be specified") - klant = factory(Klant, data) + payload = {} - return klant + # Include writable attributes, if provided + if data: + payload.update(data) - def _create_klant_for_bsn(self, user_bsn: str) -> Klant: - payload = {"subjectIdentificatie": {"inpBsn": user_bsn}} + if user_bsn: + payload = payload | {"subjectIdentificatie": {"inpBsn": user_bsn}} + elif user_kvk_or_rsin: + payload = payload | {"subjectIdentificatie": {"innNnpId": user_kvk_or_rsin}} + elif vestigingsnummer: + payload = payload | { + "subjectIdentificatie": {"vestigingsNummer": vestigingsnummer} + } try: response = self.client.post("klanten", json=payload) - data = get_json_response(response) + response_data = get_json_response(response) except (RequestException, ClientError): logger.exception("exception while making request") - return None - - klant = factory(Klant, data) - - return klant - - def _create_klant_for_kvk_or_rsin( - self, user_kvk_or_rsin: str, *, vestigingsnummer=None - ) -> list[Klant]: - payload = {"subjectIdentificatie": {"innNnpId": user_kvk_or_rsin}} - - if vestigingsnummer: - payload = {"subjectIdentificatie": {"vestigingsNummer": vestigingsnummer}} - - try: - response = self.client.post( - "klanten", - json=payload, - ) - data = get_json_response(response) - except (RequestException, ClientError): - logger.exception("exception while making request") - return None + return - klant = factory(Klant, data) + klant = factory(Klant, response_data) return klant diff --git a/src/open_inwoner/openklant/tests/data.py b/src/open_inwoner/openklant/tests/data.py index 6fad7f6308..98d833d1cb 100644 --- a/src/open_inwoner/openklant/tests/data.py +++ b/src/open_inwoner/openklant/tests/data.py @@ -156,6 +156,10 @@ def __init__(self): emailadres="foo@example.com", telefoonnummer="0612345678", toestemmingZaakNotificatiesAlleenDigitaal=False, + voornaam="John", + achternaam="Doe", + voorvoegselAchternaam="van der", + bedrijfsnaam="", ) self.klant_kvk = generate_oas_component_cached( "kc", @@ -169,6 +173,10 @@ def __init__(self): emailadres="foo@bar.com", telefoonnummer="0687654321", toestemmingZaakNotificatiesAlleenDigitaal=False, + voornaam="", + achternaam="", + voorvoegselAchternaam="", + bedrijfsnaam="AcmeCorp B.V.", ) self.klant_vestiging = generate_oas_component_cached( "kc", @@ -182,6 +190,10 @@ def __init__(self): emailadres="foo@bar.com", telefoonnummer="0612345678", toestemmingZaakNotificatiesAlleenDigitaal=False, + voornaam="", + achternaam="", + voorvoegselAchternaam="", + bedrijfsnaam="AcmeCorp B.V.", ) self.contactmoment = generate_oas_component_cached( "cmc", diff --git a/src/open_inwoner/openklant/tests/files/kc.yaml b/src/open_inwoner/openklant/tests/files/kc.yaml index 86bb30307f..ed01c732e5 100644 --- a/src/open_inwoner/openklant/tests/files/kc.yaml +++ b/src/open_inwoner/openklant/tests/files/kc.yaml @@ -1,36 +1,45 @@ +# Source: https://dmidoffice2.esuite-development.net/klanten-api-provider/api/v1/schema/openapi.yaml +# Docs: https://redocly.github.io/redoc/?url=https://dmidoffice2.esuite-development.net/klanten-api-provider/api/v1/schema/openapi.yaml#tag/klanten/operation/klant_create openapi: 3.0.1 info: - # source: https://dmidoffice2.esuite-development.net/klanten-api-provider/api/v1/schema/openapi.yaml - description: Deze implementatie is niet 'compliant' maar streeft er naar 'compatible' + description: + Deze implementatie is niet 'compliant' maar streeft er naar 'compatible' te zijn met de Klanten API specificatie. title: E-Suite implementatie van een deel van de Klanten API versie 1.0.0 - version: 1.3.0 + version: 1.5.0 servers: -- url: /api/v1 + - url: /api/v1 security: -- JWT-Claims: [] + - JWT-Claims: [] paths: /klanten: get: operationId: klant_list parameters: - - description: BSN van persoon - in: query - name: subjectNatuurlijkPersoon__inpBsn - schema: - type: string - - description: KVK nummer van bedrijf - in: query - name: subjectNietNatuurlijkPersoon__innNnpId - schema: - type: string - - description: Vestigingsnummer van bedrijf - in: query - name: subjectVestiging__vestigingsNummer - schema: - type: string + - description: + Gebruikersnaam van bestaande gebruiker in e-Suite. Wordt gebruikt + voor controleren van rechten en voor aanmaken historie gegevens. + in: header + name: X-Esuite-Request-User-Id + schema: + type: string + - description: BSN van persoon + in: query + name: subjectNatuurlijkPersoon__inpBsn + schema: + type: string + - description: KVK nummer van bedrijf + in: query + name: subjectNietNatuurlijkPersoon__innNnpId + schema: + type: string + - description: Vestigingsnummer van bedrijf + in: query + name: subjectVestiging__vestigingsNummer + schema: + type: string responses: - "200": + '200': content: application/json: schema: @@ -38,12 +47,13 @@ paths: description: OK headers: API-version: - description: Geeft een specifieke API-versie aan in de context van een + description: + Geeft een specifieke API-versie aan in de context van een specifieke aanroep. Voorbeeld 1.2.1. schema: type: string style: simple - "400": + '400': content: application/problem+json: schema: @@ -51,12 +61,13 @@ paths: description: Bad request headers: API-version: - description: Geeft een specifieke API-versie aan in de context van een + description: + Geeft een specifieke API-versie aan in de context van een specifieke aanroep. Voorbeeld 1.2.1. schema: type: string style: simple - "401": + '401': content: application/problem+json: schema: @@ -64,12 +75,13 @@ paths: description: Unauthorized headers: API-version: - description: Geeft een specifieke API-versie aan in de context van een + description: + Geeft een specifieke API-versie aan in de context van een specifieke aanroep. Voorbeeld 1.2.1. schema: type: string style: simple - "403": + '403': content: application/problem+json: schema: @@ -77,12 +89,13 @@ paths: description: Forbidden headers: API-version: - description: Geeft een specifieke API-versie aan in de context van een + description: + Geeft een specifieke API-versie aan in de context van een specifieke aanroep. Voorbeeld 1.2.1. schema: type: string style: simple - "500": + '500': content: application/problem+json: schema: @@ -90,21 +103,31 @@ paths: description: Internal server error headers: API-version: - description: Geeft een specifieke API-versie aan in de context van een + description: + Geeft een specifieke API-versie aan in de context van een specifieke aanroep. Voorbeeld 1.2.1. schema: type: string style: simple security: - - JWT-Claims: - - klanten.lezen - summary: "Klant (persoon of bedrijf in e-Suite) opvragen op basis van BSN, KVK\ + - JWT-Claims: + - klanten.lezen + summary: + "Klant (persoon of bedrijf in e-Suite) opvragen op basis van BSN, KVK\ \ nummer of vestigingsnummer. 1, en niet meer dan 1, van de query parameters\ \ moet gevuld zijn." tags: - - klanten + - klanten post: operationId: klant_create + parameters: + - description: + Gebruikersnaam van bestaande gebruiker in e-Suite. Wordt gebruikt + voor controleren van rechten en voor aanmaken historie gegevens. + in: header + name: X-Esuite-Request-User-Id + schema: + type: string requestBody: content: application/json: @@ -112,7 +135,7 @@ paths: $ref: '#/components/schemas/Klant' required: true responses: - "201": + '201': content: application/json: schema: @@ -120,12 +143,13 @@ paths: description: Created headers: API-version: - description: Geeft een specifieke API-versie aan in de context van een + description: + Geeft een specifieke API-versie aan in de context van een specifieke aanroep. Voorbeeld 1.2.1. schema: type: string style: simple - "400": + '400': content: application/problem+json: schema: @@ -133,12 +157,13 @@ paths: description: Bad request headers: API-version: - description: Geeft een specifieke API-versie aan in de context van een + description: + Geeft een specifieke API-versie aan in de context van een specifieke aanroep. Voorbeeld 1.2.1. schema: type: string style: simple - "401": + '401': content: application/problem+json: schema: @@ -146,12 +171,13 @@ paths: description: Unauthorized headers: API-version: - description: Geeft een specifieke API-versie aan in de context van een + description: + Geeft een specifieke API-versie aan in de context van een specifieke aanroep. Voorbeeld 1.2.1. schema: type: string style: simple - "403": + '403': content: application/problem+json: schema: @@ -159,12 +185,13 @@ paths: description: Forbidden headers: API-version: - description: Geeft een specifieke API-versie aan in de context van een + description: + Geeft een specifieke API-versie aan in de context van een specifieke aanroep. Voorbeeld 1.2.1. schema: type: string style: simple - "404": + '404': content: application/problem+json: schema: @@ -172,12 +199,13 @@ paths: description: Not found headers: API-version: - description: Geeft een specifieke API-versie aan in de context van een + description: + Geeft een specifieke API-versie aan in de context van een specifieke aanroep. Voorbeeld 1.2.1. schema: type: string style: simple - "409": + '409': content: application/problem+json: schema: @@ -185,12 +213,13 @@ paths: description: Conflict headers: API-version: - description: Geeft een specifieke API-versie aan in de context van een + description: + Geeft een specifieke API-versie aan in de context van een specifieke aanroep. Voorbeeld 1.2.1. schema: type: string style: simple - "500": + '500': content: application/problem+json: schema: @@ -198,32 +227,41 @@ paths: description: Internal server error headers: API-version: - description: Geeft een specifieke API-versie aan in de context van een + description: + Geeft een specifieke API-versie aan in de context van een specifieke aanroep. Voorbeeld 1.2.1. schema: type: string style: simple security: - - JWT-Claims: - - klanten.aanmaken - summary: Ophalen van een in e-Suite niet bestaand persoon of bedrijf uit extern + - JWT-Claims: + - klanten.aanmaken + summary: + Ophalen van een in e-Suite niet bestaand persoon of bedrijf uit extern systeem en opslaan in e-Suite als klant. Tevens wordt het meegegeven telefoonnummer of emailadres opgeslagen. tags: - - klanten + - klanten /klanten/{uuid}: get: operationId: klant_read parameters: - - description: Unieke resource identifier (UUID4) - in: path - name: uuid - required: true - schema: - type: string - format: uuid + - description: + Gebruikersnaam van bestaande gebruiker in e-Suite. Wordt gebruikt + voor controleren van rechten en voor aanmaken historie gegevens. + in: header + name: X-Esuite-Request-User-Id + schema: + type: string + - description: Unieke resource identifier (UUID4) + in: path + name: uuid + required: true + schema: + type: string + format: uuid responses: - "200": + '200': content: application/json: schema: @@ -231,12 +269,13 @@ paths: description: OK headers: API-version: - description: Geeft een specifieke API-versie aan in de context van een + description: + Geeft een specifieke API-versie aan in de context van een specifieke aanroep. Voorbeeld 1.2.1. schema: type: string style: simple - "400": + '400': content: application/problem+json: schema: @@ -244,12 +283,13 @@ paths: description: Bad request headers: API-version: - description: Geeft een specifieke API-versie aan in de context van een + description: + Geeft een specifieke API-versie aan in de context van een specifieke aanroep. Voorbeeld 1.2.1. schema: type: string style: simple - "401": + '401': content: application/problem+json: schema: @@ -257,12 +297,13 @@ paths: description: Unauthorized headers: API-version: - description: Geeft een specifieke API-versie aan in de context van een + description: + Geeft een specifieke API-versie aan in de context van een specifieke aanroep. Voorbeeld 1.2.1. schema: type: string style: simple - "403": + '403': content: application/problem+json: schema: @@ -270,12 +311,13 @@ paths: description: Forbidden headers: API-version: - description: Geeft een specifieke API-versie aan in de context van een + description: + Geeft een specifieke API-versie aan in de context van een specifieke aanroep. Voorbeeld 1.2.1. schema: type: string style: simple - "404": + '404': content: application/problem+json: schema: @@ -283,12 +325,13 @@ paths: description: Not found headers: API-version: - description: Geeft een specifieke API-versie aan in de context van een + description: + Geeft een specifieke API-versie aan in de context van een specifieke aanroep. Voorbeeld 1.2.1. schema: type: string style: simple - "500": + '500': content: application/problem+json: schema: @@ -296,27 +339,35 @@ paths: description: Internal server error headers: API-version: - description: Geeft een specifieke API-versie aan in de context van een + description: + Geeft een specifieke API-versie aan in de context van een specifieke aanroep. Voorbeeld 1.2.1. schema: type: string style: simple security: - - JWT-Claims: - - klanten.lezen + - JWT-Claims: + - klanten.lezen summary: Een specifieke Klant (persoon of bedrijf in e-Suite) opvragen. tags: - - klanten + - klanten patch: operationId: klant_partial_update parameters: - - description: Unieke resource identifier (UUID4) - in: path - name: uuid - required: true - schema: - type: string - format: uuid + - description: + Gebruikersnaam van bestaande gebruiker in e-Suite. Wordt gebruikt + voor controleren van rechten en voor aanmaken historie gegevens. + in: header + name: X-Esuite-Request-User-Id + schema: + type: string + - description: Unieke resource identifier (UUID4) + in: path + name: uuid + required: true + schema: + type: string + format: uuid requestBody: content: application/json: @@ -324,7 +375,7 @@ paths: $ref: '#/components/schemas/Klant' required: true responses: - "200": + '200': content: application/json: schema: @@ -332,12 +383,13 @@ paths: description: OK headers: API-version: - description: Geeft een specifieke API-versie aan in de context van een + description: + Geeft een specifieke API-versie aan in de context van een specifieke aanroep. Voorbeeld 1.2.1. schema: type: string style: simple - "400": + '400': content: application/problem+json: schema: @@ -345,12 +397,13 @@ paths: description: Bad request headers: API-version: - description: Geeft een specifieke API-versie aan in de context van een + description: + Geeft een specifieke API-versie aan in de context van een specifieke aanroep. Voorbeeld 1.2.1. schema: type: string style: simple - "401": + '401': content: application/problem+json: schema: @@ -358,12 +411,13 @@ paths: description: Unauthorized headers: API-version: - description: Geeft een specifieke API-versie aan in de context van een + description: + Geeft een specifieke API-versie aan in de context van een specifieke aanroep. Voorbeeld 1.2.1. schema: type: string style: simple - "403": + '403': content: application/problem+json: schema: @@ -371,12 +425,13 @@ paths: description: Forbidden headers: API-version: - description: Geeft een specifieke API-versie aan in de context van een + description: + Geeft een specifieke API-versie aan in de context van een specifieke aanroep. Voorbeeld 1.2.1. schema: type: string style: simple - "404": + '404': content: application/problem+json: schema: @@ -384,12 +439,13 @@ paths: description: Not found headers: API-version: - description: Geeft een specifieke API-versie aan in de context van een + description: + Geeft een specifieke API-versie aan in de context van een specifieke aanroep. Voorbeeld 1.2.1. schema: type: string style: simple - "409": + '409': content: application/problem+json: schema: @@ -397,12 +453,13 @@ paths: description: Conflict headers: API-version: - description: Geeft een specifieke API-versie aan in de context van een + description: + Geeft een specifieke API-versie aan in de context van een specifieke aanroep. Voorbeeld 1.2.1. schema: type: string style: simple - "500": + '500': content: application/problem+json: schema: @@ -410,18 +467,20 @@ paths: description: Internal server error headers: API-version: - description: Geeft een specifieke API-versie aan in de context van een + description: + Geeft een specifieke API-versie aan in de context van een specifieke aanroep. Voorbeeld 1.2.1. schema: type: string style: simple security: - - JWT-Claims: - - klanten.bijwerken - summary: Bijwerken van telefoonnummers of emailadres van een Klant (persoon + - JWT-Claims: + - klanten.bijwerken + summary: + Bijwerken van telefoonnummers of emailadres van een Klant (persoon of bedrijf in e-Suite). tags: - - klanten + - klanten components: schemas: Fout: @@ -431,20 +490,22 @@ components: type: string description: Systeemcode die het type fout aangeeft enum: - - INTERNAL - - VALIDATION - - NOT_FOUND - - NOT_AUTHORISED - - CONFLICT + - INTERNAL + - VALIDATION + - NOT_FOUND + - NOT_AUTHORISED + - CONFLICT + - FORBIDDEN title: Code detail: type: string - description: "Extra informatie bij de fout, indien beschikbaar" + description: 'Extra informatie bij de fout, indien beschikbaar' minLength: 1 title: Detail instance: type: string - description: "URI met referentie naar dit specifiek voorkomen van de fout.\ + description: + "URI met referentie naar dit specifiek voorkomen van de fout.\ \ Deze kan gebruikt worden in combinatie met server logs, bijvoorbeeld." minLength: 1 title: Instance @@ -460,13 +521,13 @@ components: title: Title type: type: string - description: "URI referentie naar het type fout, bedoeld voor developers" + description: 'URI referentie naar het type fout, bedoeld voor developers' title: Type required: - - code - - instance - - status - - title + - code + - instance + - status + - title Klant: type: object properties: @@ -505,9 +566,9 @@ components: type: string description: Type Klant enum: - - natuurlijk_persoon - - niet_natuurlijk_persoon - - vestiging + - natuurlijk_persoon + - niet_natuurlijk_persoon + - vestiging readOnly: true title: Subject type telefoonnummer: @@ -517,10 +578,25 @@ components: title: Telefoonnummer telefoonnummerAlternatief: type: string - description: Alternatief telefoonnummer. Indien gevuld moet ook het primaire + description: + Alternatief telefoonnummer. Indien gevuld moet ook het primaire telefoonnummer gevuld zijn. nullable: true title: Telefoonnummer alternatief + toestemmingZaakNotificatiesAlleenDigitaal: + type: boolean + description: + Wanneer aanwezig en True dan heeft de klant toegestemd om alleen + digitaal te worden genotificeerd over zijn zaken en documenten. Wanneer + aanwezig en False dan heeft de klant niet toegestemd om alleen digitaal + te worden genotificeerd over zijn zaken en documenten. Indien niet aanwezig + dan heeft de klant geen keuze gemaakt. Wanneer dit attribuut geen waarde + heeft bij het aanmaken van een klant dan wordt dit attribuut niet overgenomen + in e-Suite wat betekend dat de klant nog geen keuze heeft gemaakt. Wanneer + dit attribuut geen waarde heeft bij het updaten van een klant dan wordt + dit overgenomen in de e-Suite als een beslissing dat klant niet toestemd + om alleen digitaal te worden genotificeerd. + title: Toestemming zaak notificaties alleen digitaal url: type: string format: uri @@ -529,7 +605,7 @@ components: title: Url voornaam: type: string - description: "Voornaam, voorletters of roepnaam van persoon." + description: 'Voornaam, voorletters of roepnaam van persoon.' maxLength: 200 readOnly: true title: Voornaam @@ -540,10 +616,10 @@ components: readOnly: true title: Voorvoegsel achternaam required: - - bronorganisatie - - klantnummer - - subjectIdentificatie - - url + - bronorganisatie + - klantnummer + - subjectIdentificatie + - url KlantResults: type: object properties: @@ -555,8 +631,8 @@ components: items: $ref: '#/components/schemas/Klant' required: - - count - - results + - count + - results SubjectIdentificatie: type: object properties: @@ -586,20 +662,22 @@ components: type: string description: Systeemcode die het type fout aangeeft enum: - - INTERNAL - - VALIDATION - - NOT_FOUND - - NOT_AUTHORISED - - CONFLICT + - INTERNAL + - VALIDATION + - NOT_FOUND + - NOT_AUTHORISED + - CONFLICT + - FORBIDDEN title: Code detail: type: string - description: "Extra informatie bij de fout, indien beschikbaar" + description: 'Extra informatie bij de fout, indien beschikbaar' minLength: 1 title: Detail instance: type: string - description: "URI met referentie naar dit specifiek voorkomen van de fout.\ + description: + "URI met referentie naar dit specifiek voorkomen van de fout.\ \ Deze kan gebruikt worden in combinatie met server logs, bijvoorbeeld." minLength: 1 title: Instance @@ -615,10 +693,10 @@ components: title: Title type: type: string - description: "URI referentie naar het type fout, bedoeld voor developers" + description: 'URI referentie naar het type fout, bedoeld voor developers' title: Type required: - - code - - instance - - status - - title + - code + - instance + - status + - title diff --git a/src/open_inwoner/openklant/tests/test_esuite_service.py b/src/open_inwoner/openklant/tests/test_esuite_service.py new file mode 100644 index 0000000000..d32d98c295 --- /dev/null +++ b/src/open_inwoner/openklant/tests/test_esuite_service.py @@ -0,0 +1,134 @@ +import itertools + +from django.test import TestCase + +import requests_mock + +from open_inwoner.accounts.tests.factories import UserFactory +from open_inwoner.openklant.api_models import Klant +from open_inwoner.openklant.services import eSuiteKlantenService +from open_inwoner.openklant.tests.data import KLANTEN_ROOT, MockAPIReadData +from open_inwoner.utils.test import DisableRequestLogMixin + + +class eSuiteServiceTestCase(TestCase, DisableRequestLogMixin): + maxDiff = None + + def setUp(self): + super().setUp() + self.data = MockAPIReadData() + self.data.setUpServices() + self.service = eSuiteKlantenService() + self.user = UserFactory() + + def test_create_klant_can_only_specify_one_identifier(self): + identifiers = { + "vestigingsnummer": "123", + "user_bsn": "123", + "user_kvk_or_rsin": "123", + } + + # Test all pairs + for id1, id2 in itertools.combinations(identifiers.keys(), 2): + with self.subTest(f"{id1} with {id2}"): + test_ids = {id1: "123", id2: "123"} + with self.assertRaises(ValueError): + self.service.create_klant(**test_ids) + + # Test all three + with self.subTest("all identifiers"): + with self.assertRaises(ValueError): + self.service.create_klant(**identifiers) + + def test_create_klant_bsn(self): + with requests_mock.mock() as m: + m.post( + f"{KLANTEN_ROOT}klanten", + json=self.data.klant_bsn, + ) + + klant = self.service.create_klant(user_bsn="123456789") + + self.assertIsInstance(klant, Klant) + self.assertEqual( + m.request_history[0].json(), + {"subjectIdentificatie": {"inpBsn": "123456789"}}, + ) + self.assertEqual( + klant, + Klant( + url="https://klanten.nl/api/v1/klant/aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", + bronorganisatie="123456789", + klantnummer="12345678", + website_url="", + voornaam="John", + voorvoegsel_achternaam="van der", + achternaam="Doe", + telefoonnummer="0612345678", + emailadres="foo@example.com", + toestemming_zaak_notificaties_alleen_digitaal=False, + bedrijfsnaam="", + ), + ) + + def test_create_klant_kvk(self): + with requests_mock.mock() as m: + m.post( + f"{KLANTEN_ROOT}klanten", + json=self.data.klant_kvk, + ) + + klant = self.service.create_klant(user_kvk_or_rsin="87654321") + + self.assertIsInstance(klant, Klant) + self.assertEqual( + m.request_history[0].json(), + {"subjectIdentificatie": {"innNnpId": "87654321"}}, + ) + self.assertEqual( + klant, + Klant( + url="https://klanten.nl/api/v1/klant/aaaaaaaa-aaaa-aaaa-aaaa-ffffffffffff", + bronorganisatie="123456789", + klantnummer="87654321", + website_url="", + voornaam="", + voorvoegsel_achternaam="", + achternaam="", + telefoonnummer="0687654321", + emailadres="foo@bar.com", + toestemming_zaak_notificaties_alleen_digitaal=False, + bedrijfsnaam="AcmeCorp B.V.", + ), + ) + + def test_create_klant_vestigingsnummer(self): + with requests_mock.mock() as m: + m.post( + f"{KLANTEN_ROOT}klanten", + json=self.data.klant_vestiging, + ) + + klant = self.service.create_klant(vestigingsnummer="123456789000") + + self.assertIsInstance(klant, Klant) + self.assertEqual( + m.request_history[0].json(), + {"subjectIdentificatie": {"vestigingsNummer": "123456789000"}}, + ) + self.assertEqual( + klant, + Klant( + url="https://klanten.nl/api/v1/klant/bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb", + bronorganisatie="123456789", + klantnummer="11111111", + website_url="", + voornaam="", + voorvoegsel_achternaam="", + achternaam="", + telefoonnummer="0612345678", + emailadres="foo@bar.com", + toestemming_zaak_notificaties_alleen_digitaal=False, + bedrijfsnaam="AcmeCorp B.V.", + ), + )