diff --git a/src/openklant/components/klanten/api/serializers.py b/src/openklant/components/klanten/api/serializers.py index 911aa04d..1738fa8b 100644 --- a/src/openklant/components/klanten/api/serializers.py +++ b/src/openklant/components/klanten/api/serializers.py @@ -297,6 +297,7 @@ class Meta: "url": {"lookup_field": "uuid"}, "subject": {"required": False, "validators": [URLValidator()]}, "subject_type": {"validators": [IsImmutableValidator()]}, + "klantnummer": {"required": False}, # Disabled for now, should return once logic is implemented # "geverifieerd": {"validators": [IsImmutableValidator()]}, # Disabled for now, see https://github.com/maykinmedia/open-klant/pull/11#pullrequestreview-805051480 @@ -331,7 +332,6 @@ def validate(self, attrs): _("subject or subjectIdentificatie must be provided"), code="invalid-subject", ) - return validated_attrs def to_internal_value(self, data): diff --git a/src/openklant/components/klanten/api/tests/test_klant.py b/src/openklant/components/klanten/api/tests/test_klant.py index d1c5a3f7..e1814d31 100644 --- a/src/openklant/components/klanten/api/tests/test_klant.py +++ b/src/openklant/components/klanten/api/tests/test_klant.py @@ -348,6 +348,110 @@ def test_create_klant_website_url_optional(self): self.assertEqual(klant.bronorganisatie, "950428139") self.assertEqual(klant.website_url, "") + def test_create_klant_website_url_optional_klantnummer(self): + list_url = reverse(Klant) + data = { + "bronorganisatie": "950428139", + "voornaam": "Xavier", + "achternaam": "Jackson", + "emailadres": "test@gmail.com", + "adres": { + "straatnaam": "Keizersgracht", + "huisnummer": "117", + "huisletter": "A", + "postcode": "1015CJ", + "woonplaatsnaam": "test", + "landcode": "1234", + }, + "subjectType": KlantType.natuurlijk_persoon, + "subject": SUBJECT, + } + + with requests_mock.Mocker() as m: + m.get(SUBJECT, json={}) + response = self.client.post(list_url, data) + + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + + klant = Klant.objects.get() + + self.assertEqual(klant.bronorganisatie, "950428139") + self.assertEqual(klant.website_url, "") + self.assertEqual(klant.klantnummer, "1") + + with requests_mock.Mocker() as m: + m.get(SUBJECT, json={}) + response = self.client.post(list_url, data) + + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + + klant = Klant.objects.get(klantnummer__gt=1) + self.assertEqual(klant.klantnummer, "2") + + def test_create_klant_website_url_duplicate_klantnummer(self): + list_url = reverse(Klant) + data = { + "bronorganisatie": "950428139", + "subjectType": KlantType.natuurlijk_persoon, + "klantnummer": "123", + "subject": SUBJECT, + } + + with requests_mock.Mocker() as m: + m.get(SUBJECT, json={}) + response = self.client.post(list_url, data) + + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + + klant = Klant.objects.get() + + self.assertEqual(klant.bronorganisatie, "950428139") + self.assertEqual(klant.website_url, "") + self.assertEqual(klant.klantnummer, "123") + + with requests_mock.Mocker() as m: + m.get(SUBJECT, json={}) + response = self.client.post(list_url, data) + + self.assertEqual(response.status_code, 409) + klant = Klant.objects.get() + + def test_create_klant_website_url_invalid_klantnummer(self): + list_url = reverse(Klant) + data = { + "bronorganisatie": "950428139", + "subjectType": KlantType.natuurlijk_persoon, + "klantnummer": "123456789", + "subject": SUBJECT, + } + + with requests_mock.Mocker() as m: + m.get(SUBJECT, json={}) + response = self.client.post(list_url, data) + + self.assertEqual(response.status_code, 400) + + klanten = Klant.objects.all() + + self.assertFalse(klanten) + + data = { + "bronorganisatie": "950428139", + "subjectType": KlantType.natuurlijk_persoon, + "klantnummer": "KLANT1", + "subject": SUBJECT, + } + + with requests_mock.Mocker() as m: + m.get(SUBJECT, json={}) + response = self.client.post(list_url, data) + + self.assertEqual(response.status_code, 400) + + klanten = Klant.objects.all() + + self.assertFalse(klanten) + def test_create_klant_natuurlijkpersoon(self): list_url = reverse(Klant) data = { diff --git a/src/openklant/components/klanten/migrations/0005_auto_20230905_1215.py b/src/openklant/components/klanten/migrations/0005_auto_20230905_1215.py new file mode 100644 index 00000000..cdf7e472 --- /dev/null +++ b/src/openklant/components/klanten/migrations/0005_auto_20230905_1215.py @@ -0,0 +1,32 @@ +# Generated by Django 3.2.18 on 2023-09-05 12:15 + +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("klanten", "0004_klant_geverifieerd"), + ] + + operations = [ + migrations.AlterField( + model_name="klant", + name="klantnummer", + field=models.CharField( + help_text="De unieke identificatie van de klant binnen de bronorganisatie.", + max_length=8, + validators=[ + django.core.validators.RegexValidator( + code="only-digits", + message="Waarde moet numeriek zijn.", + regex="^[0-9]+$", + ) + ], + ), + ), + migrations.AlterUniqueTogether( + name="klant", + unique_together=set(), + ), + ] diff --git a/src/openklant/components/klanten/models/klanten.py b/src/openklant/components/klanten/models/klanten.py index 5b4a0ff4..5f5c0a3b 100644 --- a/src/openklant/components/klanten/models/klanten.py +++ b/src/openklant/components/klanten/models/klanten.py @@ -6,12 +6,22 @@ from django.db import models from django.utils.translation import gettext_lazy as _ +from vng_api_common.exceptions import Conflict from vng_api_common.fields import BSNField, RSINField from vng_api_common.models import APIMixin +from vng_api_common.validators import validate_digits from .constants import GeslachtsAanduiding, KlantType, SoortRechtsvorm +class KlantManager(models.Manager): + def get_next_klantnummer(self): + id_max = Klant.objects.all().aggregate(models.Max("klantnummer"))[ + "klantnummer__max" + ] + return int(id_max) + 1 if id_max else 1 + + class Klant(APIMixin, models.Model): uuid = models.UUIDField( unique=True, @@ -29,6 +39,7 @@ class Klant(APIMixin, models.Model): klantnummer = models.CharField( max_length=8, help_text=_("De unieke identificatie van de klant binnen de bronorganisatie."), + validators=[validate_digits], ) bedrijfsnaam = models.CharField( max_length=200, @@ -88,10 +99,19 @@ class Klant(APIMixin, models.Model): default=False, help_text=_("Geeft aan of de KLANT wel of niet geverifieerd is.") ) + objects = KlantManager() + class Meta: verbose_name = "klant" verbose_name_plural = "klanten" - unique_together = ("bronorganisatie", "klantnummer") + + def save(self, *args, **kwargs): + if not self.klantnummer: + self.klantnummer = Klant.objects.get_next_klantnummer() + if not self.pk: + if Klant.objects.filter(klantnummer=self.klantnummer): + raise Conflict("Klantnummer bestaat al") + return super().save(*args, **kwargs) @property def subject_identificatie(self):