Skip to content

Commit

Permalink
Add validation for override receivabletype update
Browse files Browse the repository at this point in the history
  • Loading branch information
juho-kettunen-nc committed Nov 15, 2024
1 parent 62d1c2d commit 151fb8e
Show file tree
Hide file tree
Showing 4 changed files with 188 additions and 22 deletions.
10 changes: 10 additions & 0 deletions leasing/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -659,6 +659,16 @@ class Labels:
KUVA_NUP = "KuVa / Nuorisopalvelut"


def is_akv_or_kuva_service_unit_id(service_unit_id: int) -> bool:
akv_kuva_service_unit_ids = [
ServiceUnitId.AKV,
ServiceUnitId.KUVA_LIPA,
ServiceUnitId.KUVA_UPA,
ServiceUnitId.KUVA_NUP,
]
return service_unit_id in akv_kuva_service_unit_ids


class SapSalesOfficeNumber(Enum):
"""
In Finnish: SAP myyntitoimiston numero
Expand Down
114 changes: 93 additions & 21 deletions leasing/serializers/rent.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,32 +5,39 @@
from rest_framework.serializers import ListSerializer

from field_permissions.serializers import FieldPermissionsSerializerMixin
from leasing.enums import DueDatesType, RentAdjustmentAmountType, RentCycle, RentType
from leasing.models import Index, ReceivableType
from leasing.models.rent import (
EqualizedRent,
LeaseBasisOfRentManagementSubvention,
LeaseBasisOfRentTemporarySubvention,
ManagementSubvention,
ManagementSubventionFormOfManagement,
TemporarySubvention,
from leasing.enums import (
DueDatesType,
RentAdjustmentAmountType,
RentCycle,
RentType,
is_akv_or_kuva_service_unit_id,
)
from leasing.serializers.receivable_type import ReceivableTypeSerializer
from leasing.serializers.utils import validate_seasonal_day_for_month
from users.serializers import UserSerializer

from ..models import (
from leasing.models import (
ContractRent,
Decision,
FixedInitialYearRent,
Index,
IndexAdjustedRent,
LeaseBasisOfRent,
PayableRent,
ReceivableType,
Rent,
RentAdjustment,
RentDueDate,
RentIntendedUse,
)
from leasing.models.rent import (
EqualizedRent,
LeaseBasisOfRentManagementSubvention,
LeaseBasisOfRentTemporarySubvention,
ManagementSubvention,
ManagementSubventionFormOfManagement,
TemporarySubvention,
)
from leasing.serializers.receivable_type import ReceivableTypeSerializer
from leasing.serializers.utils import validate_seasonal_day_for_month
from users.serializers import UserSerializer

from .decision import DecisionSerializer
from .utils import (
DayMonthField,
Expand Down Expand Up @@ -538,12 +545,18 @@ class Meta:
"override_receivable_type",
)

def validate(self, data):
def validate(self, rent_data: dict):
self.validate_seasonal_values(rent_data)
self.validate_override_receivable_type_value(rent_data)
return rent_data

def validate_seasonal_values(self, rent_data: dict) -> None:
"""Raises: serializers.ValidationError"""
seasonal_values = [
data.get("seasonal_start_day"),
data.get("seasonal_start_month"),
data.get("seasonal_end_day"),
data.get("seasonal_end_month"),
rent_data.get("seasonal_start_day"),
rent_data.get("seasonal_start_month"),
rent_data.get("seasonal_end_day"),
rent_data.get("seasonal_end_month"),
]

if not all(v is None for v in seasonal_values) and any(
Expand All @@ -553,7 +566,10 @@ def validate(self, data):
_("All seasonal values are required if one is set")
)

if all(seasonal_values) and data.get("due_dates_type") != DueDatesType.CUSTOM:
if (
all(seasonal_values)
and rent_data.get("due_dates_type") != DueDatesType.CUSTOM
):
raise serializers.ValidationError(
_("Due dates type must be custom if seasonal dates are set")
)
Expand All @@ -562,7 +578,63 @@ def validate(self, data):
validate_seasonal_day_for_month(start_day, start_month)
validate_seasonal_day_for_month(end_day, end_month)

return data
def validate_override_receivable_type_value(self, rent_data: dict) -> None:
"""
Override receivabletype is mandatory for AKV and KuVa leases, and not
used by MaKe/Tontit.
It is only used in index rents, fixed rents, and manual rents, because
these rent types can generate automatic invoices.
Raises: serializers.ValidationError
"""
override_receivable_type = rent_data.get("override_receivable_type")

if not override_receivable_type:
# Empty override receivabletype input should be allowed, because it
# is always empty for all MaKe rents, and those must be allowed.
return

elif not is_akv_or_kuva_service_unit_id(
override_receivable_type.service_unit_id
):
# All MaKe receivabletypes should be rejected regardless of rent
# type, because MaKe doesn't use the override feature, and if any
# other service unit would use MaKe's receivabletypes, it would be
# a mistake.
raise serializers.ValidationError(
_(
f'Override receivabletype "{override_receivable_type.name}" was unexpected. '
"Override receivabletype is not used by MaKe/Tontit."
)
)

elif is_akv_or_kuva_service_unit_id(override_receivable_type.service_unit_id):
rent_type = rent_data.get("type", {})

if rent_type in [
RentType.INDEX,
RentType.INDEX2022,
RentType.FIXED,
RentType.MANUAL,
]:
# These rent types can generate automatic invoices, so they can
# utilize the override receivabletype.
return
else:
raise serializers.ValidationError(
_(
f'Override receivabletype "{override_receivable_type.name}" was unexpected. '
f'Rent type "{rent_type.name}" does not generate automatic invoices.'
)
)

else:
raise serializers.ValidationError(
_(
"Unhandled case in override receivabletype validation. Rejecting just in case."
)
)


class LeaseBasisOfRentManagementSubventionSerializer(serializers.ModelSerializer):
Expand Down
3 changes: 2 additions & 1 deletion leasing/serializers/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,8 @@ def get_file_filename(self, obj):
return os.path.basename(obj.file.name)


def validate_seasonal_day_for_month(day: int | None, month: int | None):
def validate_seasonal_day_for_month(day: int | None, month: int | None) -> None:
"""Raises: serializers.ValidationError"""
if day is None and month is None:
return

Expand Down
83 changes: 83 additions & 0 deletions leasing/tests/serializers/test_rent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import pytest
from rest_framework.exceptions import ValidationError

from leasing.enums import RentType, ServiceUnitId
from leasing.models import ServiceUnit
from leasing.serializers.rent import RentCreateUpdateSerializer


@pytest.mark.django_db
def test_is_valid_override_receivable_type(django_db_setup, receivable_type_factory):
"""
Test that the requirements described in the target function docstring hold.
"""
make = ServiceUnit.objects.get(pk=ServiceUnitId.MAKE)
akv = ServiceUnit.objects.get(pk=ServiceUnitId.AKV)
kuva_lipa = ServiceUnit.objects.get(pk=ServiceUnitId.KUVA_LIPA)
kuva_upa = ServiceUnit.objects.get(pk=ServiceUnitId.KUVA_UPA)
kuva_nup = ServiceUnit.objects.get(pk=ServiceUnitId.KUVA_NUP)

serializer = RentCreateUpdateSerializer()

# Validator should reject all MaKe receivable types regardless of rent type.
rent_datas_make = [
{
"type": rent_type,
"override_receivable_type": receivable_type_factory(service_unit=make),
}
for rent_type in RentType
]
for data in rent_datas_make:
with pytest.raises(ValidationError):
serializer.validate(data)

# Validator should allow empty override receivable type input.
rent_datas_empty = [
{
"type": rent_type,
"override_receivable_type": None,
}
for rent_type in RentType
]
for data in rent_datas_empty:
assert serializer.validate(data)

# Validator should allow AKV and KuVa receivable types, if rent type is valid.
rent_datas_akv_and_kuva = [
{
"type": RentType.INDEX2022,
"override_receivable_type": receivable_type_factory(service_unit=unit),
}
for unit in [akv, kuva_lipa, kuva_upa, kuva_nup]
]
for data in rent_datas_akv_and_kuva:
assert serializer.validate(data)

# Validator should allow rent types that can generate automatic invoices.
rent_datas_valid_types = [
{
"type": rent_type,
"override_receivable_type": receivable_type_factory(service_unit=akv),
}
for rent_type in [
RentType.FIXED,
RentType.INDEX,
RentType.INDEX2022,
RentType.MANUAL,
]
]
for data in rent_datas_valid_types:
assert serializer.validate(data)

# Validator should reject the receivable type input for rent types that
# don't generate automatic invoices.
rent_datas_invalid_types = [
{
"type": rent_type,
"override_receivable_type": receivable_type_factory(service_unit=akv),
}
for rent_type in [RentType.FREE, RentType.ONE_TIME]
]
for data in rent_datas_invalid_types:
with pytest.raises(ValidationError):
serializer.validate(data)

0 comments on commit 151fb8e

Please sign in to comment.