Skip to content

Commit

Permalink
Merge pull request #440 from City-of-Helsinki/address_api_application
Browse files Browse the repository at this point in the history
Add API for latest application's applicant
  • Loading branch information
rbreve authored Oct 29, 2024
2 parents edcbe17 + de83db1 commit d2ac255
Show file tree
Hide file tree
Showing 8 changed files with 110 additions and 49 deletions.
6 changes: 5 additions & 1 deletion application_form/api/sales/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,17 +38,21 @@ class ProjectUUIDSerializer(serializers.Serializer):


class SalesApplicantSerializer(ApplicantSerializerBase):
class Meta(ApplicantSerializerBase.Meta):
fields = ApplicantSerializerBase.Meta.fields + ["date_of_birth", "ssn_suffix"]

pass


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")
Expand Down
46 changes: 30 additions & 16 deletions application_form/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -36,8 +36,6 @@


class ApplicantSerializerBase(serializers.ModelSerializer):
date_of_birth = serializers.DateField(write_only=True)

class Meta:
model = Applicant
fields = [
Expand All @@ -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")
Expand All @@ -71,6 +72,13 @@ def validate(self, attrs):
)
return attrs

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 ApplicationApartmentSerializer(serializers.Serializer):
priority = IntegerField(min_value=0, max_value=5)
Expand All @@ -81,15 +89,13 @@ 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:
model = Application
fields = (
"application_uuid",
"application_type",
"ssn_suffix",
"has_children",
"additional_applicant",
"right_of_residence",
Expand Down Expand Up @@ -125,9 +131,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
Expand Down Expand Up @@ -169,17 +182,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()
Expand Down
22 changes: 22 additions & 0 deletions application_form/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from apartment.elastic.queries import get_apartment_uuids
from application_form.api.serializers import (
ApartmentReservationSerializer,
ApplicantSerializerBase,
ApplicationSerializer,
)
from application_form.models import ApartmentReservation, Application
Expand Down Expand Up @@ -40,3 +41,24 @@ 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 = ApplicantSerializerBase
http_method_names = ["get"]

def get(self, request, customer_id):
application = Application.objects.filter(customer__id=customer_id).latest(
"created_at"
)
if application:
applicant = application.applicants.filter(is_primary_applicant=True).first()
if applicant:
serializer = self.get_serializer(applicant)
return Response(serializer.data)

return Response({})
29 changes: 16 additions & 13 deletions application_form/services/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
)
Expand All @@ -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,
)
Expand Down Expand Up @@ -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"])
Expand Down
29 changes: 19 additions & 10 deletions application_form/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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),
}
Expand Down
14 changes: 7 additions & 7 deletions application_form/tests/test_application_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -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
)

Expand Down Expand Up @@ -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)}"
)
Expand Down
2 changes: 1 addition & 1 deletion application_form/tests/test_sales_application_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
11 changes: 10 additions & 1 deletion application_form/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -70,6 +74,11 @@
apartment_states,
name="apartment_states",
),
path(
r"sales/applicant/latest/<int:customer_id>/",
LatestApplicantInfo.as_view(),
name="applicant-by-customer",
),
path("", include(router.urls)),
]
urlpatterns += public_urlpatterns

0 comments on commit d2ac255

Please sign in to comment.