diff --git a/application_form/api/sales/serializers.py b/application_form/api/sales/serializers.py index 699fcb9d..afa0e38f 100644 --- a/application_form/api/sales/serializers.py +++ b/application_form/api/sales/serializers.py @@ -38,6 +38,9 @@ class ProjectUUIDSerializer(serializers.Serializer): class SalesApplicantSerializer(ApplicantSerializerBase): + class Meta(ApplicantSerializerBase.Meta): + fields = ApplicantSerializerBase.Meta.fields + ["date_of_birth", "ssn_suffix"] + pass @@ -45,10 +48,11 @@ class SalesApplicationSerializer(ApplicationSerializerBase): profile = serializers.PrimaryKeyRelatedField( queryset=Profile.objects.all(), write_only=True ) + applicant = SalesApplicantSerializer(write_only=True) additional_applicant = SalesApplicantSerializer(write_only=True, allow_null=True) class Meta(ApplicationSerializerBase.Meta): - fields = ApplicationSerializerBase.Meta.fields + ("profile",) + fields = ApplicationSerializerBase.Meta.fields + ("profile", "applicant") def _get_senders_name_from_applicants_data(self, validated_data): additional_applicant = validated_data.get("additional_applicant") diff --git a/application_form/api/serializers.py b/application_form/api/serializers.py index b7433b83..5f24b36d 100644 --- a/application_form/api/serializers.py +++ b/application_form/api/serializers.py @@ -4,7 +4,7 @@ from enumfields.drf import EnumField, EnumSupportSerializerMixin from rest_framework import serializers from rest_framework.exceptions import ValidationError -from rest_framework.fields import CharField, IntegerField, UUIDField +from rest_framework.fields import IntegerField, UUIDField from apartment_application_service.settings import ( METADATA_HANDLER_INFORMATION, @@ -36,8 +36,6 @@ class ApplicantSerializerBase(serializers.ModelSerializer): - date_of_birth = serializers.DateField(write_only=True) - class Meta: model = Applicant fields = [ @@ -49,13 +47,16 @@ class Meta: "city", "postal_code", "age", - "date_of_birth", - "ssn_suffix", ] extra_kwargs = {"age": {"read_only": True}} class ApplicantSerializer(ApplicantSerializerBase): + date_of_birth = serializers.DateField(write_only=True) + + class Meta(ApplicantSerializerBase.Meta): + fields = ApplicantSerializerBase.Meta.fields + ["date_of_birth", "ssn_suffix"] + def validate(self, attrs): super().validate(attrs) date_of_birth = attrs.get("date_of_birth") @@ -81,7 +82,6 @@ class ApplicationSerializerBase(serializers.ModelSerializer): application_uuid = UUIDField(source="external_uuid") application_type = EnumField(ApplicationType, source="type", write_only=True) project_id = UUIDField(write_only=True) - ssn_suffix = CharField(write_only=True, min_length=5, max_length=5) apartments = ApplicationApartmentSerializer(write_only=True, many=True) class Meta: @@ -89,7 +89,6 @@ class Meta: fields = ( "application_uuid", "application_type", - "ssn_suffix", "has_children", "additional_applicant", "right_of_residence", @@ -125,9 +124,16 @@ def prepare_metadata(self, validated_data): class ApplicationSerializer(ApplicationSerializerBase): + applicant = ApplicantSerializer(write_only=True) additional_applicant = ApplicantSerializer(write_only=True, allow_null=True) project_id = UUIDField(write_only=True) + class Meta(ApplicationSerializerBase.Meta): + fields = ApplicationSerializerBase.Meta.fields + ( + "applicant", + "additional_applicant", + ) + def _get_senders_name_from_applicants_data(self, validated_data): additional_applicant = validated_data.get("additional_applicant") sender_names = validated_data["profile"].full_name @@ -169,17 +175,18 @@ def validate_ssn_suffix(self, value): def validate(self, attrs): project_uuid = attrs["project_id"] applicants = [] - profile = self.context["request"].user.profile - applicants.append((profile.date_of_birth, attrs["ssn_suffix"])) - is_additional_applicant = ( - "additional_applicant" in attrs - and attrs["additional_applicant"] is not None - ) - if is_additional_applicant: + applicant_data = attrs.get("applicant") + if applicant_data: + applicants.append( + (applicant_data["date_of_birth"], applicant_data["ssn_suffix"]) + ) + + additional_applicant = attrs.get("additional_applicant") + if additional_applicant: applicants.append( ( - attrs["additional_applicant"]["date_of_birth"], - attrs["additional_applicant"]["ssn_suffix"], + additional_applicant["date_of_birth"], + additional_applicant["ssn_suffix"], ) ) validator = ProjectApplicantValidator() diff --git a/application_form/api/views.py b/application_form/api/views.py index f78cbd88..0ee487a3 100644 --- a/application_form/api/views.py +++ b/application_form/api/views.py @@ -4,10 +4,11 @@ from rest_framework_simplejwt.authentication import JWTAuthentication from apartment.elastic.queries import get_apartment_uuids +from application_form.api.serializers import ApartmentReservationSerializer from application_form.api.serializers import ( - ApartmentReservationSerializer, - ApplicationSerializer, + ApplicantSerializerBase as ApplicantSerializer, ) +from application_form.api.serializers import ApplicationSerializer from application_form.models import ApartmentReservation, Application from audit_log.viewsets import AuditLoggingModelViewSet @@ -40,3 +41,25 @@ def get(self, request, project_uuid): ) serializer = self.get_serializer(reservations, many=True) return Response(serializer.data) + + +class LatestApplicantInfo(GenericAPIView): + """ + Returns the primary applicant from the latest application. + """ + + serializer_class = ApplicantSerializer + http_method_names = ["get"] + + def get(self, request, customer_id): + application = Application.objects.filter(customer__id=customer_id).latest( + "created_at" + ) + applicant = application.applicants.filter(is_primary_applicant=True).first() + if application: + applicant = application.applicants.filter(is_primary_applicant=True).first() + if applicant: + serializer = self.get_serializer(applicant) + return Response(serializer.data) + + return Response({}) diff --git a/application_form/services/application.py b/application_form/services/application.py index 94b94ad5..7c8b23a1 100644 --- a/application_form/services/application.py +++ b/application_form/services/application.py @@ -91,7 +91,8 @@ def create_application( ) data = application_data.copy() profile = data.pop("profile") - additional_applicant_data = data.pop("additional_applicant") + applicant_data = data.pop("applicant") + additional_applicant_data = data.pop("additional_applicant", None) customer = get_or_create_customer_from_profiles( profile, additional_applicant_data, data.get("has_children") ) @@ -116,17 +117,19 @@ def create_application( sender_names=data.pop("sender_names"), ) Applicant.objects.create( - first_name=profile.first_name, - last_name=profile.last_name, - email=profile.email, - phone_number=profile.phone_number, - street_address=profile.street_address, - city=profile.city, - postal_code=profile.postal_code, - age=_calculate_age(profile.date_of_birth), - date_of_birth=profile.date_of_birth, - ssn_suffix=application_data["ssn_suffix"], - contact_language=profile.contact_language, + first_name=applicant_data["first_name"], + last_name=applicant_data["last_name"], + email=applicant_data["email"], + phone_number=applicant_data["phone_number"], + street_address=applicant_data["street_address"], + city=applicant_data["city"], + postal_code=applicant_data["postal_code"], + age=_calculate_age(applicant_data["date_of_birth"]), + date_of_birth=applicant_data["date_of_birth"], + ssn_suffix=applicant_data["ssn_suffix"], + contact_language=applicant_data.get( + "contact_language", profile.contact_language + ), is_primary_applicant=True, application=application, ) @@ -166,7 +169,7 @@ def update_profile_from_application_data(profile: Profile, data: dict) -> None: Currently only fills the ssn_suffix field if it's empty in the profile. """ - ssn_suffix = data.get("ssn_suffix") + ssn_suffix = data.get("applicant", {}).get("ssn_suffix") if not profile.ssn_suffix and ssn_suffix: profile.ssn_suffix = ssn_suffix profile.save(update_fields=["ssn_suffix"]) diff --git a/application_form/tests/conftest.py b/application_form/tests/conftest.py index 736d237c..98b85295 100644 --- a/application_form/tests/conftest.py +++ b/application_form/tests/conftest.py @@ -243,15 +243,24 @@ def create_application_data( for index, apartment_uuid in enumerate(apartment_uuids[0:5]) ] right_of_residence = 123456 if application_type == ApplicationType.HASO else None - # Build application request data application_data = { "application_uuid": str(uuid.uuid4()), "application_type": application_type.value, - "ssn_suffix": profile.ssn_suffix, "has_children": True, "right_of_residence": right_of_residence, "additional_applicant": None, + "applicant": { + "first_name": profile.first_name, + "last_name": profile.last_name, + "email": profile.email, + "street_address": profile.street_address, + "postal_code": profile.postal_code, + "city": profile.city, + "phone_number": profile.phone_number, + "date_of_birth": profile.date_of_birth, + "ssn_suffix": profile.ssn_suffix, + }, "project_id": str(project_uuid), "apartments": apartments_data, "has_hitas_ownership": True, @@ -260,15 +269,15 @@ def create_application_data( # Add a second applicant if needed if num_applicants == 2: date_of_birth = faker.Faker().date_of_birth(minimum_age=18) - applicant = ApplicantFactory.build() + additional_applicant = ApplicantFactory.build() application_data["additional_applicant"] = { - "first_name": applicant.first_name, - "last_name": applicant.last_name, - "email": applicant.email, - "street_address": applicant.street_address, - "postal_code": applicant.postal_code, - "city": applicant.city, - "phone_number": applicant.phone_number, + "first_name": additional_applicant.first_name, + "last_name": additional_applicant.last_name, + "email": additional_applicant.email, + "street_address": additional_applicant.street_address, + "postal_code": additional_applicant.postal_code, + "city": additional_applicant.city, + "phone_number": additional_applicant.phone_number, "date_of_birth": f"{date_of_birth:%Y-%m-%d}", "ssn_suffix": calculate_ssn_suffix(date_of_birth), } diff --git a/application_form/tests/test_application_api.py b/application_form/tests/test_application_api.py index 03987cc7..55ff9953 100644 --- a/application_form/tests/test_application_api.py +++ b/application_form/tests/test_application_api.py @@ -70,7 +70,7 @@ def test_application_post_sets_nin(api_client, elastic_single_project_with_apart assert response.status_code == 201 assert response.data == {"application_uuid": data["application_uuid"]} profile.refresh_from_db() - assert profile.ssn_suffix == data["ssn_suffix"] + assert profile.ssn_suffix == data["applicant"]["ssn_suffix"] assert len(profile.national_identification_number) == 11 @@ -212,14 +212,14 @@ def test_application_post_fails_if_incorrect_ssn_suffix( profile = ProfileFactory() api_client.credentials(HTTP_AUTHORIZATION=f"Bearer {_create_token(profile)}") data = create_application_data(profile) - data["ssn_suffix"] = "-000$" + data["applicant"]["ssn_suffix"] = "-000$" response = api_client.post( reverse("application_form:application-list"), data, format="json" ) assert response.status_code == status.HTTP_400_BAD_REQUEST - assert len(response.data["ssn_suffix"][0]["message"]) > 0 + assert len(response.data["applicant"]["ssn_suffix"][0]["message"]) > 0 assert ( - response.data["ssn_suffix"][0]["code"] + response.data["applicant"]["ssn_suffix"][0]["code"] == error_codes.E1000_SSN_SUFFIX_IS_NOT_VALID ) @@ -350,9 +350,9 @@ def test_application_post_fails_if_partner_profile_have_already_applied_to_proje ).date(), ) partner_application_data = create_application_data(partner_profile) - partner_application_data["ssn_suffix"] = application_data["additional_applicant"][ - "ssn_suffix" - ] + partner_application_data["applicant"]["ssn_suffix"] = application_data[ + "additional_applicant" + ]["ssn_suffix"] api_client.credentials( HTTP_AUTHORIZATION=f"Bearer {_create_token(partner_profile)}" ) diff --git a/application_form/tests/test_sales_application_api.py b/application_form/tests/test_sales_application_api.py index 68f74e47..4c1a9d58 100644 --- a/application_form/tests/test_sales_application_api.py +++ b/application_form/tests/test_sales_application_api.py @@ -48,7 +48,7 @@ def test_sales_application_post( ) data = create_application_data(customer_profile) data["profile"] = customer_profile.id - data["ssn_suffix"] = "XXXXX" # ssn suffix should not be validated + data["applicant"]["ssn_suffix"] = "XXXXX" # ssn suffix should not be validated data["additional_applicant"]["ssn_suffix"] = "XXXXX" response = drupal_salesperson_api_client.post( reverse("application_form:sales-application-list"), data, format="json" diff --git a/application_form/urls.py b/application_form/urls.py index 3fa9494b..bfff1a1e 100644 --- a/application_form/urls.py +++ b/application_form/urls.py @@ -8,7 +8,11 @@ OfferViewSet, SalesApplicationViewSet, ) -from application_form.api.views import ApplicationViewSet, ListProjectReservations +from application_form.api.views import ( + ApplicationViewSet, + LatestApplicantInfo, + ListProjectReservations, +) from invoicing.api.views import ( ApartmentInstallmentAddToSapAPIView, ApartmentInstallmentAPIView, @@ -70,6 +74,11 @@ apartment_states, name="apartment_states", ), + path( + r"sales/applicant/latest//", + LatestApplicantInfo.as_view(), + name="applicant-by-customer", + ), path("", include(router.urls)), ] urlpatterns += public_urlpatterns diff --git a/asko_import/serializers.py b/asko_import/serializers.py index 4808d280..ce1d5e0d 100644 --- a/asko_import/serializers.py +++ b/asko_import/serializers.py @@ -124,6 +124,13 @@ class Meta: "street_address", ) + def __init__(self, *args, **kwargs): + exclude_fields = kwargs.pop("exclude_fields", None) + super().__init__(*args, **kwargs) + if exclude_fields: + for field_name in exclude_fields: + self.fields.pop(field_name, None) + class ApplicationSerializer(EnumSupportSerializerMixin, serializers.ModelSerializer): customer = CustomPrimaryKeyRelatedField(queryset=Customer.objects.all())