From 634a0f329896878486776771ee6c28a1e3ff4701 Mon Sep 17 00:00:00 2001 From: Daniel Mursa Date: Mon, 18 Nov 2024 14:56:24 +0100 Subject: [PATCH] [#233] Fix values partij identificatoren [#233] Fix tests [#233] Install django-digid-eherkenning [#233] Fix tests [#233] Improve Validator [#233] Fix tests [#233] Isort and Flake8 [#233] Improvements [#233] Remove django-digid-eherkenning [#233] Fix requirements [#233] Fix requirements 2 [#233] Fix tests [#233] Flake8 [#233] Fix validation errors [#233] Update class [#233] Remove unused import --- .../api/serializers/partijen.py | 9 + .../api/tests/test_filters.py | 29 +- .../api/tests/test_partijen.py | 300 ++++++++++++++-- ..._identificator_code_objecttype_and_more.py | 69 ++++ .../klantinteracties/models/constants.py | 21 ++ .../klantinteracties/models/partijen.py | 20 +- .../klantinteracties/models/validators.py | 185 ++++++++++ .../klantinteracties/tests/test_validators.py | 324 ++++++++++++++++++ 8 files changed, 908 insertions(+), 49 deletions(-) create mode 100644 src/openklant/components/klantinteracties/migrations/0024_alter_partijidentificator_partij_identificator_code_objecttype_and_more.py create mode 100644 src/openklant/components/klantinteracties/models/validators.py create mode 100644 src/openklant/components/klantinteracties/tests/test_validators.py diff --git a/src/openklant/components/klantinteracties/api/serializers/partijen.py b/src/openklant/components/klantinteracties/api/serializers/partijen.py index e8ed89bf..821b2fb6 100644 --- a/src/openklant/components/klantinteracties/api/serializers/partijen.py +++ b/src/openklant/components/klantinteracties/api/serializers/partijen.py @@ -42,6 +42,10 @@ Vertegenwoordigden, ) from openklant.components.klantinteracties.models.rekeningnummers import Rekeningnummer +from openklant.components.klantinteracties.models.validators import ( + PartijIdentificatorValidator, +) +from openklant.utils.serializers import get_field_value class PartijForeignkeyBaseSerializer(serializers.HyperlinkedModelSerializer): @@ -399,6 +403,11 @@ class Meta: }, } + def validate(self, attrs): + partij_identificator = get_field_value(self, attrs, "partij_identificator") + PartijIdentificatorValidator(**partij_identificator).validate() + return super().validate(attrs) + @transaction.atomic def update(self, instance, validated_data): if "partij" in validated_data: diff --git a/src/openklant/components/klantinteracties/api/tests/test_filters.py b/src/openklant/components/klantinteracties/api/tests/test_filters.py index 8b597bc2..0583cd16 100644 --- a/src/openklant/components/klantinteracties/api/tests/test_filters.py +++ b/src/openklant/components/klantinteracties/api/tests/test_filters.py @@ -694,16 +694,17 @@ def test_filter_vertegenwoordigde_partij_url(self): def test_filter_partij_identificator_code_objecttype(self): partij, partij2 = PartijFactory.create_batch(2) PartijIdentificatorFactory.create( - partij=partij, partij_identificator_code_objecttype="one" + partij=partij, partij_identificator_code_objecttype="natuurlijk_persoon" ) PartijIdentificatorFactory.create( - partij=partij2, partij_identificator_code_objecttype="two" + partij=partij2, + partij_identificator_code_objecttype="niet_natuurlijk_persoon", ) with self.subTest("happy flow"): response = self.client.get( self.url, - {"partijIdentificator__codeObjecttype": "two"}, + {"partijIdentificator__codeObjecttype": "niet_natuurlijk_persoon"}, ) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -724,16 +725,16 @@ def test_filter_partij_identificator_code_objecttype(self): def test_filter_identificator_soort_object_id(self): partij, partij2 = PartijFactory.create_batch(2) PartijIdentificatorFactory.create( - partij=partij, partij_identificator_code_soort_object_id="one" + partij=partij, partij_identificator_code_soort_object_id="bsn" ) PartijIdentificatorFactory.create( - partij=partij2, partij_identificator_code_soort_object_id="two" + partij=partij2, partij_identificator_code_soort_object_id="kvknummer" ) with self.subTest("happy flow"): response = self.client.get( self.url, - {"partijIdentificator__codeSoortObjectId": "one"}, + {"partijIdentificator__codeSoortObjectId": "bsn"}, ) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -756,16 +757,20 @@ def test_filter_identificator_soort_object_id(self): def test_filter_identificator_object_id(self): partij, partij2 = PartijFactory.create_batch(2) PartijIdentificatorFactory.create( - partij=partij, partij_identificator_object_id="one" + partij=partij, + partij_identificator_code_soort_object_id="bsn", + partij_identificator_object_id="296648875", ) PartijIdentificatorFactory.create( - partij=partij2, partij_identificator_object_id="two" + partij=partij2, + partij_identificator_code_soort_object_id="bsn", + partij_identificator_object_id="111222333", ) with self.subTest("happy flow"): response = self.client.get( self.url, - {"partijIdentificator__objectId": "one"}, + {"partijIdentificator__objectId": "296648875"}, ) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -786,16 +791,16 @@ def test_filter_identificator_object_id(self): def test_filter_identificator_code_register(self): partij, partij2 = PartijFactory.create_batch(2) PartijIdentificatorFactory.create( - partij=partij, partij_identificator_code_register="one" + partij=partij, partij_identificator_code_register="brp" ) PartijIdentificatorFactory.create( - partij=partij2, partij_identificator_code_register="two" + partij=partij2, partij_identificator_code_register="hr" ) with self.subTest("happy flow"): response = self.client.get( self.url, - {"partijIdentificator__code_register": "two"}, + {"partijIdentificator__code_register": "hr"}, ) self.assertEqual(response.status_code, status.HTTP_200_OK) diff --git a/src/openklant/components/klantinteracties/api/tests/test_partijen.py b/src/openklant/components/klantinteracties/api/tests/test_partijen.py index 03bc9bf3..3d7b9a5f 100644 --- a/src/openklant/components/klantinteracties/api/tests/test_partijen.py +++ b/src/openklant/components/klantinteracties/api/tests/test_partijen.py @@ -1877,10 +1877,10 @@ def test_create_partij_indetificator(self): "identificeerdePartij": {"uuid": str(partij.uuid)}, "anderePartijIdentificator": "anderePartijIdentificator", "partijIdentificator": { - "codeObjecttype": "codeObjecttype", - "codeSoortObjectId": "codeSoortObjectId", - "objectId": "objectId", - "codeRegister": "codeRegister", + "codeObjecttype": "natuurlijk_persoon", + "codeSoortObjectId": "bsn", + "objectId": "296648875", + "codeRegister": "brp", }, } @@ -1893,10 +1893,10 @@ def test_create_partij_indetificator(self): self.assertEqual( data["partijIdentificator"], { - "codeObjecttype": "codeObjecttype", - "codeSoortObjectId": "codeSoortObjectId", - "objectId": "objectId", - "codeRegister": "codeRegister", + "codeObjecttype": "natuurlijk_persoon", + "codeSoortObjectId": "bsn", + "objectId": "296648875", + "codeRegister": "brp", }, ) @@ -1905,10 +1905,10 @@ def test_update_partij_indetificator(self): partij_identificator = PartijIdentificatorFactory.create( partij=partij, andere_partij_identificator="anderePartijIdentificator", - partij_identificator_code_objecttype="codeObjecttype", - partij_identificator_code_soort_object_id="codeSoortObjectId", - partij_identificator_object_id="objectId", - partij_identificator_code_register="codeRegister", + partij_identificator_code_objecttype="natuurlijk_persoon", + partij_identificator_code_soort_object_id="bsn", + partij_identificator_object_id="296648875", + partij_identificator_code_register="brp", ) detail_url = reverse( @@ -1923,10 +1923,10 @@ def test_update_partij_indetificator(self): self.assertEqual( data["partijIdentificator"], { - "codeObjecttype": "codeObjecttype", - "codeSoortObjectId": "codeSoortObjectId", - "objectId": "objectId", - "codeRegister": "codeRegister", + "codeObjecttype": "natuurlijk_persoon", + "codeSoortObjectId": "bsn", + "objectId": "296648875", + "codeRegister": "brp", }, ) @@ -1934,10 +1934,10 @@ def test_update_partij_indetificator(self): "identificeerdePartij": {"uuid": str(partij2.uuid)}, "anderePartijIdentificator": "changed", "partijIdentificator": { - "codeObjecttype": "changed", - "codeSoortObjectId": "changed", - "objectId": "changed", - "codeRegister": "changed", + "codeObjecttype": "natuurlijk_persoon", + "codeSoortObjectId": "bsn", + "objectId": "296648875", + "codeRegister": "brp", }, } @@ -1950,10 +1950,10 @@ def test_update_partij_indetificator(self): self.assertEqual( data["partijIdentificator"], { - "codeObjecttype": "changed", - "codeSoortObjectId": "changed", - "objectId": "changed", - "codeRegister": "changed", + "codeObjecttype": "natuurlijk_persoon", + "codeSoortObjectId": "bsn", + "objectId": "296648875", + "codeRegister": "brp", }, ) @@ -1962,10 +1962,10 @@ def test_partial_update_partij_indetificator(self): partij_identificator = PartijIdentificatorFactory.create( partij=partij, andere_partij_identificator="anderePartijIdentificator", - partij_identificator_code_objecttype="codeObjecttype", - partij_identificator_code_soort_object_id="codeSoortObjectId", - partij_identificator_object_id="objectId", - partij_identificator_code_register="codeRegister", + partij_identificator_code_objecttype="natuurlijk_persoon", + partij_identificator_code_soort_object_id="bsn", + partij_identificator_object_id="296648875", + partij_identificator_code_register="brp", ) detail_url = reverse( @@ -1980,10 +1980,10 @@ def test_partial_update_partij_indetificator(self): self.assertEqual( data["partijIdentificator"], { - "codeObjecttype": "codeObjecttype", - "codeSoortObjectId": "codeSoortObjectId", - "objectId": "objectId", - "codeRegister": "codeRegister", + "codeObjecttype": "natuurlijk_persoon", + "codeSoortObjectId": "bsn", + "objectId": "296648875", + "codeRegister": "brp", }, ) @@ -2000,10 +2000,10 @@ def test_partial_update_partij_indetificator(self): self.assertEqual( data["partijIdentificator"], { - "codeObjecttype": "codeObjecttype", - "codeSoortObjectId": "codeSoortObjectId", - "objectId": "objectId", - "codeRegister": "codeRegister", + "codeObjecttype": "natuurlijk_persoon", + "codeSoortObjectId": "bsn", + "objectId": "296648875", + "codeRegister": "brp", }, ) @@ -2021,6 +2021,234 @@ def test_destroy_partij_identificator(self): data = response.json() self.assertEqual(data["count"], 0) + def test_invalid_choice_partij_identificator_code_register(self): + url = reverse("klantinteracties:partijidentificator-list") + partij = PartijFactory.create() + data = { + "identificeerdePartij": {"uuid": str(partij.uuid)}, + "anderePartijIdentificator": "anderePartijIdentificator", + "partijIdentificator": { + "codeObjecttype": "natuurlijk_persoon", + "codeSoortObjectId": "bsn", + "objectId": "296648875", + "codeRegister": "test", + }, + } + response = self.client.post(url, data) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(response.data["code"], "invalid") + self.assertEqual(response.data["title"], "Invalid input.") + self.assertEqual( + response.data["invalid_params"][0]["name"], + "partijIdentificator.codeRegister", + ) + self.assertEqual(response.data["invalid_params"][0]["code"], "invalid_choice") + self.assertEqual( + response.data["invalid_params"][0]["reason"], + '"test" is een ongeldige keuze.', + ) + + def test_invalid_choice_partij_identificator_code_objecttype(self): + url = reverse("klantinteracties:partijidentificator-list") + partij = PartijFactory.create() + data = { + "identificeerdePartij": {"uuid": str(partij.uuid)}, + "anderePartijIdentificator": "anderePartijIdentificator", + "partijIdentificator": { + "codeObjecttype": "test", + "codeSoortObjectId": "bsn", + "objectId": "296648875", + "codeRegister": "brp", + }, + } + response = self.client.post(url, data) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(response.data["code"], "invalid") + self.assertEqual(response.data["title"], "Invalid input.") + self.assertEqual( + response.data["invalid_params"][0]["name"], + "partijIdentificator.codeObjecttype", + ) + self.assertEqual(response.data["invalid_params"][0]["code"], "invalid_choice") + self.assertEqual( + response.data["invalid_params"][0]["reason"], + '"test" is een ongeldige keuze.', + ) + + def test_invalid_choice_partij_identificator_code_soort_object_id(self): + url = reverse("klantinteracties:partijidentificator-list") + partij = PartijFactory.create() + data = { + "identificeerdePartij": {"uuid": str(partij.uuid)}, + "anderePartijIdentificator": "anderePartijIdentificator", + "partijIdentificator": { + "codeObjecttype": "natuurlijk_persoon", + "codeSoortObjectId": "test", + "objectId": "296648875", + "codeRegister": "brp", + }, + } + response = self.client.post(url, data) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(response.data["code"], "invalid") + self.assertEqual(response.data["title"], "Invalid input.") + self.assertEqual( + response.data["invalid_params"][0]["name"], + "partijIdentificator.codeSoortObjectId", + ) + self.assertEqual(response.data["invalid_params"][0]["code"], "invalid_choice") + self.assertEqual( + response.data["invalid_params"][0]["reason"], + '"test" is een ongeldige keuze.', + ) + + def test_invalid_validation_partij_identificator_code_objecttype(self): + url = reverse("klantinteracties:partijidentificator-list") + partij = PartijFactory.create() + data = { + "identificeerdePartij": {"uuid": str(partij.uuid)}, + "anderePartijIdentificator": "anderePartijIdentificator", + "partijIdentificator": { + "codeObjecttype": "niet_natuurlijk_persoon", + "codeSoortObjectId": "bsn", + "objectId": "296648875", + "codeRegister": "brp", + }, + } + response = self.client.post(url, data) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(response.data["code"], "invalid") + self.assertEqual(response.data["title"], "Invalid input.") + self.assertEqual( + response.data["invalid_params"][0]["name"], + "partijIdentificatorCodeObjecttype", + ) + self.assertEqual(response.data["invalid_params"][0]["code"], "invalid") + self.assertEqual( + response.data["invalid_params"][0]["reason"], + "codeObjecttype keuzes zijn beperkt op basis van codeRegister.", + ) + + def test_invalid_validation_partij_identificator_code_soort_object_id(self): + url = reverse("klantinteracties:partijidentificator-list") + partij = PartijFactory.create() + data = { + "identificeerdePartij": {"uuid": str(partij.uuid)}, + "anderePartijIdentificator": "anderePartijIdentificator", + "partijIdentificator": { + "codeObjecttype": "natuurlijk_persoon", + "codeSoortObjectId": "kvknummer", + "objectId": "296648875", + "codeRegister": "brp", + }, + } + + response = self.client.post(url, data) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(response.data["code"], "invalid") + self.assertEqual(response.data["title"], "Invalid input.") + self.assertEqual( + response.data["invalid_params"][0]["name"], + "partijIdentificatorCodeSoortObjectId", + ) + self.assertEqual(response.data["invalid_params"][0]["code"], "invalid") + self.assertEqual( + response.data["invalid_params"][0]["reason"], + "codeSoortObjectId keuzes zijn beperkt op basis van codeObjecttype.", + ) + + def test_invalid_validation_partij_identificator_object_id(self): + url = reverse("klantinteracties:partijidentificator-list") + partij = PartijFactory.create() + data = { + "identificeerdePartij": {"uuid": str(partij.uuid)}, + "anderePartijIdentificator": "anderePartijIdentificator", + "partijIdentificator": { + "codeObjecttype": "natuurlijk_persoon", + "codeSoortObjectId": "bsn", + "objectId": "12", + "codeRegister": "brp", + }, + } + + response = self.client.post(url, data) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(response.data["code"], "invalid") + self.assertEqual(response.data["title"], "Invalid input.") + self.assertEqual( + response.data["invalid_params"][0]["name"], + "partijIdentificatorObjectId", + ) + self.assertEqual(response.data["invalid_params"][0]["code"], "invalid") + self.assertEqual( + response.data["invalid_params"][0]["reason"], + "ObjectId ongeldig, reden: The length must be in: [8, 9]", + ) + + def test_valid_validation_partij_identificator(self): + # All validations pass + url = reverse("klantinteracties:partijidentificator-list") + partij = PartijFactory.create() + data = { + "identificeerdePartij": {"uuid": str(partij.uuid)}, + "anderePartijIdentificator": "anderePartijIdentificator", + "partijIdentificator": { + "codeObjecttype": "natuurlijk_persoon", + "codeSoortObjectId": "bsn", + "objectId": "296648875", + "codeRegister": "brp", + }, + } + response = self.client.post(url, data) + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertEqual( + response.data["partij_identificator"]["code_objecttype"], + "natuurlijk_persoon", + ) + self.assertEqual( + response.data["partij_identificator"]["code_soort_object_id"], + "bsn", + ) + self.assertEqual( + response.data["partij_identificator"]["object_id"], "296648875" + ) + self.assertEqual( + response.data["partij_identificator"]["code_register"], + "brp", + ) + + def test_valid_overige_validation_partij_identificator(self): + # Overige no validation + url = reverse("klantinteracties:partijidentificator-list") + partij = PartijFactory.create() + data = { + "identificeerdePartij": {"uuid": str(partij.uuid)}, + "anderePartijIdentificator": "anderePartijIdentificator", + "partijIdentificator": { + "codeObjecttype": "natuurlijk_persoon", + "codeSoortObjectId": "bsn", + "objectId": 296648875, + "codeRegister": "overige", + }, + } + response = self.client.post(url, data) + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertEqual( + response.data["partij_identificator"]["code_objecttype"], + "natuurlijk_persoon", + ) + self.assertEqual( + response.data["partij_identificator"]["code_soort_object_id"], + "bsn", + ) + self.assertEqual( + response.data["partij_identificator"]["object_id"], "296648875" + ) + self.assertEqual( + response.data["partij_identificator"]["code_register"], + "overige", + ) + class CategorieRelatieTests(APITestCase): def test_list_categorie_relatie(self): diff --git a/src/openklant/components/klantinteracties/migrations/0024_alter_partijidentificator_partij_identificator_code_objecttype_and_more.py b/src/openklant/components/klantinteracties/migrations/0024_alter_partijidentificator_partij_identificator_code_objecttype_and_more.py new file mode 100644 index 00000000..e326f87c --- /dev/null +++ b/src/openklant/components/klantinteracties/migrations/0024_alter_partijidentificator_partij_identificator_code_objecttype_and_more.py @@ -0,0 +1,69 @@ +# Generated by Django 4.2.15 on 2024-12-20 11:34 + +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("klantinteracties", "0023_alter_digitaaladres_omschrijving"), + ] + + operations = [ + migrations.AlterField( + model_name="partijidentificator", + name="partij_identificator_code_objecttype", + field=models.CharField( + blank=True, + choices=[ + ("natuurlijk_persoon", "NatuurlijkPersoon"), + ("niet_natuurlijk_persoon", "NietNatuurlijkPersoon"), + ("vestiging", "Vestiging"), + ("overige", "Overige"), + ], + help_text="Type van het object, bijvoorbeeld: 'INGESCHREVEN NATUURLIJK PERSOON'.", + max_length=200, + verbose_name="objecttype", + ), + ), + migrations.AlterField( + model_name="partijidentificator", + name="partij_identificator_code_register", + field=models.CharField( + blank=True, + choices=[("brp", "BRP"), ("hr", "HR"), ("overige", "Overige")], + help_text="Binnen het landschap van registers unieke omschrijving van het register waarin het object is geregistreerd, bijvoorbeeld: 'BRP'.", + max_length=200, + verbose_name="register", + ), + ), + migrations.AlterField( + model_name="partijidentificator", + name="partij_identificator_code_soort_object_id", + field=models.CharField( + blank=True, + choices=[ + ("bsn", "Bsn"), + ("vestigingsnummer", "VestigingsNummer"), + ("kvknummer", "KvkNummer"), + ("rsin", "Rsin"), + ("overige", "Overige"), + ], + help_text="Naam van de eigenschap die het object identificeert, bijvoorbeeld: 'Burgerservicenummer'.", + max_length=200, + verbose_name="soort object ID", + ), + ), + migrations.AlterField( + model_name="partijidentificator", + name="partij_identificator_object_id", + field=models.CharField( + blank=True, + help_text="Waarde van de eigenschap die het object identificeert, bijvoorbeeld: '123456788'.", + max_length=200, + validators=[django.core.validators.validate_integer], + verbose_name="object ID", + ), + ), + ] diff --git a/src/openklant/components/klantinteracties/models/constants.py b/src/openklant/components/klantinteracties/models/constants.py index 19172c59..3871f8a7 100644 --- a/src/openklant/components/klantinteracties/models/constants.py +++ b/src/openklant/components/klantinteracties/models/constants.py @@ -45,3 +45,24 @@ class SoortPartij(TextChoices): class Klantcontrol(TextChoices): vertegenwoordiger = "vertegenwoordiger", _("Vertegenwoordiger") klant = "klant", _("Klant") + + +class PartijIdentificatorCodeSoortObjectId(TextChoices): + bsn = "bsn", _("Bsn") + vestigingsnummer = "vestigingsnummer", _("VestigingsNummer") + kvknummer = "kvknummer", _("KvkNummer") + rsin = "rsin", _("Rsin") + overige = "overige", _("Overige") + + +class PartijIdentificatorCodeObjectType(TextChoices): + natuurlijk_persoon = "natuurlijk_persoon", _("NatuurlijkPersoon") + niet_natuurlijk_persoon = "niet_natuurlijk_persoon", _("NietNatuurlijkPersoon") + vestiging = "vestiging", _("Vestiging") + overige = "overige", _("Overige") + + +class PartijIdentificatorCodeRegister(TextChoices): + brp = "brp", _("BRP") + hr = "hr", _("HR") + overige = "overige", _("Overige") diff --git a/src/openklant/components/klantinteracties/models/partijen.py b/src/openklant/components/klantinteracties/models/partijen.py index 790bd632..927f3ec2 100644 --- a/src/openklant/components/klantinteracties/models/partijen.py +++ b/src/openklant/components/klantinteracties/models/partijen.py @@ -10,8 +10,14 @@ from openklant.components.utils.mixins import APIMixin from openklant.components.utils.number_generator import number_generator -from .constants import SoortPartij +from .constants import ( + PartijIdentificatorCodeObjectType, + PartijIdentificatorCodeRegister, + PartijIdentificatorCodeSoortObjectId, + SoortPartij, +) from .mixins import BezoekadresMixin, ContactnaamMixin, CorrespondentieadresMixin +from .validators import PartijIdentificatorValidator class Partij(APIMixin, BezoekadresMixin, CorrespondentieadresMixin): @@ -352,6 +358,7 @@ class PartijIdentificator(models.Model): help_text=_( "Type van het object, bijvoorbeeld: 'INGESCHREVEN NATUURLIJK PERSOON'." ), + choices=PartijIdentificatorCodeObjectType.choices, max_length=200, blank=True, ) @@ -360,6 +367,7 @@ class PartijIdentificator(models.Model): help_text=_( "Naam van de eigenschap die het object identificeert, bijvoorbeeld: 'Burgerservicenummer'." ), + choices=PartijIdentificatorCodeSoortObjectId.choices, max_length=200, blank=True, ) @@ -368,6 +376,7 @@ class PartijIdentificator(models.Model): help_text=_( "Waarde van de eigenschap die het object identificeert, bijvoorbeeld: '123456788'." ), + validators=[validate_integer], max_length=200, blank=True, ) @@ -378,6 +387,7 @@ class PartijIdentificator(models.Model): "het object is geregistreerd, bijvoorbeeld: 'BRP'." ), max_length=200, + choices=PartijIdentificatorCodeRegister.choices, blank=True, ) @@ -405,3 +415,11 @@ def __str__(self): object = self.partij_identificator_object_id return f"{soort_object} - {object}" + + def save(self, *args, **kwargs): + self.full_clean() + super().save(*args, **kwargs) + + def clean(self): + super().clean() + PartijIdentificatorValidator(**self.partij_identificator).validate() diff --git a/src/openklant/components/klantinteracties/models/validators.py b/src/openklant/components/klantinteracties/models/validators.py new file mode 100644 index 00000000..fe29b738 --- /dev/null +++ b/src/openklant/components/klantinteracties/models/validators.py @@ -0,0 +1,185 @@ +from django.core.exceptions import ValidationError +from django.utils.translation import gettext_lazy as _ + +from .constants import ( + PartijIdentificatorCodeObjectType, + PartijIdentificatorCodeRegister, + PartijIdentificatorCodeSoortObjectId, +) + + +class ObjectIdValidator: + """ + Validates an ObjectId based on digit check, length, and optional 11-proof check. + """ + + list_size = [] + check_11proefnumber = False + + def __init__(self, value: str): + self.value = value + + def validate_isdigit(self) -> None: + """Validates that the value contains only digits.""" + if not self.value.isdigit(): + raise ValidationError("Expected a numerical value", code="invalid") + + def validate_length(self) -> None: + """Validates that the length of the value is within the allowed sizes.""" + if len(self.value) not in self.list_size: + raise ValidationError( + "The length must be in: %s" % self.list_size, code="invalid" + ) + + def validate_11proefnumber(self) -> None: + """Validates the value based on the 11-proof check.""" + total = 0 + for multiplier, char in enumerate(reversed(self.value), start=1): + if multiplier == 1: + total += -multiplier * int(char) + else: + total += multiplier * int(char) + + if total % 11 != 0: + raise ValidationError("Invalid code", code="invalid") + + def validate(self) -> None: + self.validate_isdigit() + self.validate_length() + if self.check_11proefnumber: + self.validate_11proefnumber() + + +class PartijIdentificatorValidator: + REGISTERS = { + PartijIdentificatorCodeRegister.brp: { + PartijIdentificatorCodeObjectType.natuurlijk_persoon: [ + PartijIdentificatorCodeSoortObjectId.bsn, + PartijIdentificatorCodeSoortObjectId.overige, + ], + PartijIdentificatorCodeObjectType.overige: [], + }, + PartijIdentificatorCodeRegister.hr: { + PartijIdentificatorCodeObjectType.vestiging: [ + PartijIdentificatorCodeSoortObjectId.vestigingsnummer, + PartijIdentificatorCodeSoortObjectId.overige, + ], + PartijIdentificatorCodeObjectType.niet_natuurlijk_persoon: [ + PartijIdentificatorCodeSoortObjectId.rsin, + PartijIdentificatorCodeSoortObjectId.kvknummer, + PartijIdentificatorCodeSoortObjectId.overige, + ], + PartijIdentificatorCodeObjectType.overige: [], + }, + PartijIdentificatorCodeRegister.overige: {}, + } + + def __init__( + self, + code_register: str, + code_objecttype: str, + code_soort_object_id: str, + object_id: str, + ) -> None: + """Initialize validator""" + self.code_register = code_register + self.code_objecttype = code_objecttype + self.code_soort_object_id = code_soort_object_id + self.object_id = object_id + + def validate(self) -> None: + """Run all validations""" + self.validate_code_objecttype() + self.validate_code_soort_object_id() + self.validate_object_id() + + def validate_code_objecttype(self) -> None: + """Validates the codeObjecttype based on the provided codeRegister""" + if not self.code_objecttype: + return + + if ( + not self.code_register + or self.code_register == PartijIdentificatorCodeRegister.overige + ): + return + + if self.code_objecttype not in self.REGISTERS.get(self.code_register, {}): + raise ValidationError( + { + "partij_identificator_code_objecttype": _( + "codeObjecttype keuzes zijn beperkt op basis van codeRegister." + ) + } + ) + + def validate_code_soort_object_id(self) -> None: + """Validates the codeSoortObjectId based on register and codeObjecttype""" + if not self.code_soort_object_id: + return + + if ( + not self.code_objecttype + or self.code_objecttype == PartijIdentificatorCodeObjectType.overige + ): + return + + if not any( + self.code_soort_object_id in d.get(self.code_objecttype, []) + for d in self.REGISTERS.values() + ): + raise ValidationError( + { + "partij_identificator_code_soort_object_id": _( + "codeSoortObjectId keuzes zijn beperkt op basis van codeObjecttype." + ) + } + ) + + def validate_object_id(self) -> None: + """Validates the object_id based on the codeSoortObjectId""" + if not self.object_id: + return + + if ( + not self.code_soort_object_id + or self.code_soort_object_id == PartijIdentificatorCodeSoortObjectId.overige + ): + return + + try: + getattr(self, f"_validate_{self.code_soort_object_id}")() + except ValidationError as error: + raise ValidationError( + { + "partij_identificator_object_id": _( + "ObjectId ongeldig, reden: %s" % (error.message) + ) + } + ) + + def _validate_bsn(self) -> None: + """Validate BSN""" + validator = ObjectIdValidator(self.object_id) + validator.list_size = [8, 9] + validator.check_11proefnumber = True + validator.validate() + + def _validate_vestigingsnummer(self) -> None: + """Validate Vestigingsnummer""" + validator = ObjectIdValidator(self.object_id) + validator.list_size = [12] + validator.validate() + + def _validate_rsin(self) -> None: + """Validate RSIN""" + validator = ObjectIdValidator(self.object_id) + validator.list_size = [8, 9] + validator.check_11proefnumber = True + validator.validate() + + def _validate_kvknummer(self) -> None: + """Validate Kvk_nummer""" + validator = ObjectIdValidator(self.object_id) + validator.list_size = [8] + validator.validate() diff --git a/src/openklant/components/klantinteracties/tests/test_validators.py b/src/openklant/components/klantinteracties/tests/test_validators.py new file mode 100644 index 00000000..e5eb702e --- /dev/null +++ b/src/openklant/components/klantinteracties/tests/test_validators.py @@ -0,0 +1,324 @@ +from django.core.exceptions import ValidationError +from django.test import TestCase + +from openklant.components.klantinteracties.models.constants import ( + PartijIdentificatorCodeObjectType, + PartijIdentificatorCodeRegister, + PartijIdentificatorCodeSoortObjectId, +) +from openklant.components.klantinteracties.models.validators import ( + ObjectIdValidator, + PartijIdentificatorValidator, +) + + +class ObjectIdValidatorTests(TestCase): + def test_valid(self): + validator = ObjectIdValidator("296648875") + validator.list_size = [8, 9] + validator.check_11proefnumber = True + validator.validate() + + def test_invalid_length(self): + validator = ObjectIdValidator("1234") + validator.list_size = [8, 9] + validator.check_11proefnumber = True + + with self.assertRaises(ValidationError) as error: + validator.validate() + self.assertTrue("The length must be in: [8, 9]" in str(error.exception)) + + def test_invalid_isdigit(self): + validator = ObjectIdValidator("1234TEST") + validator.list_size = [8, 9] + validator.check_11proefnumber = True + + with self.assertRaises(ValidationError) as error: + validator.validate() + self.assertTrue("Expected a numerical value" in str(error.exception)) + + def test_invalid_11proefnumber(self): + validator = ObjectIdValidator("123456789") + validator.list_size = [8, 9] + validator.check_11proefnumber = True + + with self.assertRaises(ValidationError) as error: + validator.validate() + self.assertTrue("Invalid code" in str(error.exception)) + + +class PartijIdentificatorValidatorTests(TestCase): + def test_valid(self): + data = { + "code_objecttype": PartijIdentificatorCodeObjectType.natuurlijk_persoon.value, + "code_soort_object_id": PartijIdentificatorCodeSoortObjectId.bsn.value, + "object_id": "296648875", + "code_register": PartijIdentificatorCodeRegister.brp.value, + } + validator = PartijIdentificatorValidator(**data) + validator.validate() + + # Start section validate_code_objecttype + + def test_valid_empty_code_objecttype(self): + data = { + "code_objecttype": "", + "code_soort_object_id": PartijIdentificatorCodeSoortObjectId.bsn.value, + "object_id": "296648875", + "code_register": PartijIdentificatorCodeRegister.brp.value, + } + validator = PartijIdentificatorValidator(**data) + validator.validate_code_objecttype() + + def test_empty_code_register_ok_code_objecttype(self): + data = { + "code_objecttype": PartijIdentificatorCodeObjectType.natuurlijk_persoon.value, + "code_soort_object_id": PartijIdentificatorCodeSoortObjectId.bsn.value, + "object_id": "296648875", + "code_register": "", + } + validator = PartijIdentificatorValidator(**data) + validator.validate_code_objecttype() + + def test_overige_code_register_ok_code_objecttype(self): + data = { + "code_objecttype": PartijIdentificatorCodeObjectType.natuurlijk_persoon.value, + "code_soort_object_id": PartijIdentificatorCodeSoortObjectId.bsn.value, + "object_id": "296648875", + "code_register": PartijIdentificatorCodeRegister.overige.value, + } + validator = PartijIdentificatorValidator(**data) + validator.validate_code_objecttype() + + def test_code_objecttype_not_found_in_code_register(self): + data = { + "code_objecttype": "niet_natuurlijk_persoon", + "code_soort_object_id": PartijIdentificatorCodeSoortObjectId.bsn.value, + "object_id": "296648875", + "code_register": PartijIdentificatorCodeRegister.brp.value, + } + with self.assertRaises(ValidationError) as error: + validator = PartijIdentificatorValidator(**data) + validator.validate_code_objecttype() + + details = error.exception.message_dict + self.assertEqual( + details["partij_identificator_code_objecttype"][0], + "codeObjecttype keuzes zijn beperkt op basis van codeRegister.", + ) + + # Start section validate_code_soort_object_id + + def test_valid_empty_code_soort_object_id(self): + data = { + "code_objecttype": PartijIdentificatorCodeObjectType.natuurlijk_persoon.value, + "code_soort_object_id": "", + "object_id": "12345678", + "code_register": PartijIdentificatorCodeRegister.brp.value, + } + validator = PartijIdentificatorValidator(**data) + validator.validate_code_soort_object_id() + + def test_empty_code_objecttype_ok_code_soort_object_id(self): + data = { + "code_objecttype": "", + "code_soort_object_id": PartijIdentificatorCodeSoortObjectId.bsn.value, + "object_id": "296648875", + "code_register": PartijIdentificatorCodeRegister.brp.value, + } + validator = PartijIdentificatorValidator(**data) + validator.validate_code_soort_object_id() + + def test_code_soort_object_id_not_found_in_code_objecttype(self): + data = { + "code_objecttype": PartijIdentificatorCodeObjectType.natuurlijk_persoon.value, + "code_soort_object_id": PartijIdentificatorCodeSoortObjectId.rsin.value, + "object_id": "296648875", + "code_register": PartijIdentificatorCodeRegister.brp.value, + } + with self.assertRaises(ValidationError) as error: + validator = PartijIdentificatorValidator(**data) + validator.validate_code_soort_object_id() + details = error.exception.message_dict + self.assertEqual( + details["partij_identificator_code_soort_object_id"][0], + "codeSoortObjectId keuzes zijn beperkt op basis van codeObjecttype.", + ) + + def test_oveirige_code_objecttype_ok_code_soort_object_id(self): + data = { + "code_objecttype": PartijIdentificatorCodeObjectType.overige.value, + "code_soort_object_id": PartijIdentificatorCodeSoortObjectId.bsn.value, + "object_id": "296648875", + "code_register": PartijIdentificatorCodeRegister.brp.value, + } + validator = PartijIdentificatorValidator(**data) + validator.validate_code_soort_object_id() + + # Start section validate_object_id + + def test_valid_empty_object_id(self): + data = { + "code_objecttype": PartijIdentificatorCodeObjectType.natuurlijk_persoon.value, + "code_soort_object_id": PartijIdentificatorCodeSoortObjectId.bsn.value, + "object_id": "", + "code_register": PartijIdentificatorCodeRegister.brp.value, + } + validator = PartijIdentificatorValidator(**data) + validator.validate_object_id() + + def test_empty_code_soort_object_id_ok_object_id(self): + data = { + "code_objecttype": PartijIdentificatorCodeObjectType.natuurlijk_persoon.value, + "code_soort_object_id": "", + "object_id": "1123", + "code_register": PartijIdentificatorCodeRegister.brp.value, + } + validator = PartijIdentificatorValidator(**data) + validator.validate_object_id() + + def test_overige_code_soort_object_id_ok_object_id(self): + data = { + "code_objecttype": PartijIdentificatorCodeObjectType.natuurlijk_persoon.value, + "code_soort_object_id": "overige", + "object_id": "1123", + "code_register": PartijIdentificatorCodeRegister.brp.value, + } + validator = PartijIdentificatorValidator(**data) + validator.validate_object_id() + + def test_object_id_valid_bsn(self): + data = { + "code_objecttype": PartijIdentificatorCodeObjectType.natuurlijk_persoon.value, + "code_soort_object_id": PartijIdentificatorCodeSoortObjectId.bsn.value, + "object_id": "296648875", + "code_register": PartijIdentificatorCodeRegister.brp.value, + } + validator = PartijIdentificatorValidator(**data) + validator.validate_object_id() + + def test_object_id_valid_vestigingsnummer(self): + data = { + "code_objecttype": PartijIdentificatorCodeObjectType.vestiging.value, + "code_soort_object_id": PartijIdentificatorCodeSoortObjectId.vestigingsnummer.value, + "object_id": "296648875154", + "code_register": PartijIdentificatorCodeRegister.brp.value, + } + validator = PartijIdentificatorValidator(**data) + validator.validate_object_id() + + def test_object_id_valid_rsin(self): + data = { + "code_objecttype": PartijIdentificatorCodeObjectType.niet_natuurlijk_persoon.value, + "code_soort_object_id": PartijIdentificatorCodeSoortObjectId.rsin.value, + "object_id": "296648875", + "code_register": PartijIdentificatorCodeRegister.brp.value, + } + validator = PartijIdentificatorValidator(**data) + validator.validate_object_id() + + def test_object_id_valid_kvknummer(self): + data = { + "code_objecttype": PartijIdentificatorCodeObjectType.niet_natuurlijk_persoon.value, + "code_soort_object_id": PartijIdentificatorCodeSoortObjectId.kvknummer.value, + "object_id": "12345678", + "code_register": PartijIdentificatorCodeRegister.brp.value, + } + validator = PartijIdentificatorValidator(**data) + validator.validate_object_id() + + def test_object_id_invalid_len_bsn(self): + data = { + "code_objecttype": PartijIdentificatorCodeObjectType.natuurlijk_persoon.value, + "code_soort_object_id": PartijIdentificatorCodeSoortObjectId.bsn.value, + "object_id": "123", + "code_register": PartijIdentificatorCodeRegister.brp.value, + } + with self.assertRaises(ValidationError) as error: + validator = PartijIdentificatorValidator(**data) + validator.validate_object_id() + details = error.exception.message_dict + self.assertEqual( + details["partij_identificator_object_id"][0], + "ObjectId ongeldig, reden: The length must be in: [8, 9]", + ) + + def test_object_id_invalid_digit_bsn(self): + data = { + "code_objecttype": PartijIdentificatorCodeObjectType.natuurlijk_persoon.value, + "code_soort_object_id": PartijIdentificatorCodeSoortObjectId.bsn.value, + "object_id": "123TEST123", + "code_register": PartijIdentificatorCodeRegister.brp.value, + } + with self.assertRaises(ValidationError) as error: + validator = PartijIdentificatorValidator(**data) + validator.validate_object_id() + details = error.exception.message_dict + self.assertEqual( + details["partij_identificator_object_id"][0], + "ObjectId ongeldig, reden: Expected a numerical value", + ) + + def test_object_id_invalid_proef11_bsn(self): + data = { + "code_objecttype": PartijIdentificatorCodeObjectType.natuurlijk_persoon.value, + "code_soort_object_id": PartijIdentificatorCodeSoortObjectId.bsn.value, + "object_id": "123456789", + "code_register": PartijIdentificatorCodeRegister.brp.value, + } + with self.assertRaises(ValidationError) as error: + validator = PartijIdentificatorValidator(**data) + validator.validate_object_id() + details = error.exception.message_dict + self.assertEqual( + details["partij_identificator_object_id"][0], + "ObjectId ongeldig, reden: Invalid code", + ) + + def test_object_id_invalid_vestigingsnummer(self): + data = { + "code_objecttype": PartijIdentificatorCodeObjectType.vestiging.value, + "code_soort_object_id": PartijIdentificatorCodeSoortObjectId.vestigingsnummer.value, + "object_id": "1234", + "code_register": PartijIdentificatorCodeRegister.brp.value, + } + with self.assertRaises(ValidationError) as error: + validator = PartijIdentificatorValidator(**data) + validator.validate_object_id() + details = error.exception.message_dict + self.assertEqual( + details["partij_identificator_object_id"][0], + "ObjectId ongeldig, reden: The length must be in: [12]", + ) + + def test_object_id_invalid_rsin(self): + data = { + "code_objecttype": PartijIdentificatorCodeObjectType.niet_natuurlijk_persoon.value, + "code_soort_object_id": PartijIdentificatorCodeSoortObjectId.rsin.value, + "object_id": "1234", + "code_register": PartijIdentificatorCodeRegister.brp.value, + } + with self.assertRaises(ValidationError) as error: + validator = PartijIdentificatorValidator(**data) + validator.validate_object_id() + details = error.exception.message_dict + self.assertEqual( + details["partij_identificator_object_id"][0], + "ObjectId ongeldig, reden: The length must be in: [8, 9]", + ) + + def test_object_id_invalid_kvknummer(self): + data = { + "code_objecttype": PartijIdentificatorCodeObjectType.niet_natuurlijk_persoon.value, + "code_soort_object_id": PartijIdentificatorCodeSoortObjectId.kvknummer.value, + "object_id": "1234", + "code_register": PartijIdentificatorCodeRegister.brp.value, + } + with self.assertRaises(ValidationError) as error: + validator = PartijIdentificatorValidator(**data) + validator.validate_object_id() + details = error.exception.message_dict + self.assertEqual( + details["partij_identificator_object_id"][0], + "ObjectId ongeldig, reden: The length must be in: [8]", + )