Skip to content

Commit

Permalink
Remove seasonal rents feature
Browse files Browse the repository at this point in the history
Users want to get rid of it. This feature is not used at all currently
or in the future, and historically was used only in one (1) lease.
  • Loading branch information
juho-kettunen-nc committed Nov 22, 2024
1 parent d772e49 commit f15b78a
Showing 10 changed files with 54 additions and 551 deletions.
4 changes: 0 additions & 4 deletions .sanitizerconfig
Original file line number Diff line number Diff line change
@@ -907,10 +907,6 @@ strategy:
manual_ratio_previous: null
modified_at: null
note: null
seasonal_end_day: null
seasonal_end_month: null
seasonal_start_day: null
seasonal_start_month: null
start_date: null
type: null
x_value: null
33 changes: 33 additions & 0 deletions leasing/migrations/0082_remove_rent_seasonal_end_day_and_more.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Generated by Django 4.2.16 on 2024-11-22 07:38

from django.db import migrations


class Migration(migrations.Migration):
"""
The Seasonal Rent (kausivuokra) feature is not desired by the users anymore.
In the past it has only been used in a single lease, which has already expired.
"""

dependencies = [
("leasing", "0081_serviceunit_use_rent_override_receivable_type"),
]

operations = [
migrations.RemoveField(
model_name="rent",
name="seasonal_end_day",
),
migrations.RemoveField(
model_name="rent",
name="seasonal_end_month",
),
migrations.RemoveField(
model_name="rent",
name="seasonal_start_day",
),
migrations.RemoveField(
model_name="rent",
name="seasonal_start_month",
),
]
134 changes: 19 additions & 115 deletions leasing/models/rent.py
Original file line number Diff line number Diff line change
@@ -43,7 +43,6 @@
get_range_overlap_and_remainder,
group_items_in_period_by_date_range,
is_date_on_first_quarter,
split_date_range,
subtract_range_from_range,
subtract_ranges_from_ranges,
)
@@ -188,32 +187,6 @@ class Rent(TimeStampedSafeDeleteModel):
# In Finnish: Loppupvm
end_date = models.DateField(verbose_name=_("End date"), null=True, blank=True)

# In Finnish: Kausilaskutus
seasonal_start_day = models.PositiveIntegerField(
verbose_name=_("Seasonal start day"),
null=True,
blank=True,
validators=[MinValueValidator(1), MaxValueValidator(31)],
)
seasonal_start_month = models.PositiveIntegerField(
verbose_name=_("Seasonal start month"),
null=True,
blank=True,
validators=[MinValueValidator(1), MaxValueValidator(12)],
)
seasonal_end_day = models.PositiveIntegerField(
verbose_name=_("Seasonal end day"),
null=True,
blank=True,
validators=[MinValueValidator(1), MaxValueValidator(31)],
)
seasonal_end_month = models.PositiveIntegerField(
verbose_name=_("Seasonal end month"),
null=True,
blank=True,
validators=[MinValueValidator(1), MaxValueValidator(12)],
)

# These ratios are used if the rent type is MANUAL
# manual_ratio is used for the whole year if the RentCycle is
# JANUARY_TO_DECEMBER. If the Rent Cycle is APRIL_TO_MARCH, this is used for 1.4. - 31.12.
@@ -305,14 +278,6 @@ def calculate_payable_rent(self):

self.save()

def is_seasonal(self):
return (
self.seasonal_start_day
and self.seasonal_start_month
and self.seasonal_end_day
and self.seasonal_end_month
)

def get_intended_uses_for_date_range(self, date_range_start, date_range_end):
intended_uses = set()

@@ -337,39 +302,15 @@ def clamp_date_range(self, date_range_start, date_range_end):
clamped_date_range_start = date_range_start
clamped_date_range_end = date_range_end

if self.is_seasonal():
seasonal_period_start = datetime.date(
year=date_range_start.year,
month=self.seasonal_start_month,
day=self.seasonal_start_day,
)
seasonal_period_end = datetime.date(
year=date_range_start.year,
month=self.seasonal_end_month,
day=self.seasonal_end_day,
)

if (
date_range_start < seasonal_period_start
and date_range_start < seasonal_period_end
):
clamped_date_range_start = seasonal_period_start

if (
date_range_end > seasonal_period_end
and date_range_end > seasonal_period_start
):
clamped_date_range_end = seasonal_period_end
else:
if (self.start_date and date_range_start < self.start_date) and (
not self.end_date or date_range_start < self.end_date
):
clamped_date_range_start = self.start_date
if (self.start_date and date_range_start < self.start_date) and (
not self.end_date or date_range_start < self.end_date
):
clamped_date_range_start = self.start_date

if (self.end_date and date_range_end > self.end_date) and (
self.start_date and date_range_end > self.start_date
):
clamped_date_range_end = self.end_date
if (self.end_date and date_range_end > self.end_date) and (
self.start_date and date_range_end > self.start_date
):
clamped_date_range_end = self.end_date

return clamped_date_range_start, clamped_date_range_end

@@ -587,8 +528,7 @@ def get_amount_for_date_range(
# ONE_TIME rents are calculated manually
return calculation_result

# Limit the date range by season dates if the rent is seasonal or
# by the rent start and end dates if not
# Limit the date range based on the rent start and end dates
(clamped_date_range_start, clamped_date_range_end) = self.clamp_date_range(
date_range_start, date_range_end
)
@@ -714,56 +654,20 @@ def get_billing_period_from_due_date(
if not due_date:
return None

# Non-seasonal rent
if not self.is_seasonal():
due_dates_per_year = self.get_due_dates_for_period(
datetime.date(year=due_date.year, month=1, day=1),
datetime.date(year=due_date.year, month=12, day=31),
)

try:
due_date_index = due_dates_per_year.index(due_date)

return get_billing_periods_for_year(
due_date.year, len(due_dates_per_year)
)[due_date_index]
except (ValueError, IndexError):
# TODO: better error handling
return None

# Seasonal rent
seasonal_period_start = datetime.date(
year=due_date.year,
month=self.seasonal_start_month,
day=self.seasonal_start_day,
)
seasonal_period_end = datetime.date(
year=due_date.year, month=self.seasonal_end_month, day=self.seasonal_end_day
due_dates_per_year = self.get_due_dates_for_period(
datetime.date(year=due_date.year, month=1, day=1),
datetime.date(year=due_date.year, month=12, day=31),
)

if seasonal_period_start > due_date or seasonal_period_end < due_date:
return None
try:
due_date_index = due_dates_per_year.index(due_date)

due_dates_in_period = self.get_due_dates_for_period(
seasonal_period_start, seasonal_period_end
)
if not due_dates_in_period:
return get_billing_periods_for_year(due_date.year, len(due_dates_per_year))[
due_date_index
]
except (ValueError, IndexError):
# TODO: better error handling
return None
elif len(due_dates_in_period) == 1 and due_dates_in_period[0] == due_date:
return seasonal_period_start, seasonal_period_end
else:
try:
due_date_index = due_dates_in_period.index(due_date)
except ValueError:
return None

return split_date_range(
(
seasonal_period_start,
seasonal_period_end,
),
len(due_dates_in_period),
)[due_date_index]

def get_all_billing_periods_for_year(self, year: int) -> list[BillingPeriod | None]:
date_range_start = datetime.date(year, 1, 1)
48 changes: 1 addition & 47 deletions leasing/serializers/rent.py
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@
from rest_framework.serializers import ListSerializer

from field_permissions.serializers import FieldPermissionsSerializerMixin
from leasing.enums import DueDatesType, RentAdjustmentAmountType, RentCycle, RentType
from leasing.enums import RentAdjustmentAmountType, RentCycle, RentType
from leasing.models import (
ContractRent,
Decision,
@@ -32,7 +32,6 @@
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
@@ -59,10 +58,6 @@ class Meta:
model = RentDueDate
fields = ("id", "day", "month")

def validate(self, data):
validate_seasonal_day_for_month(data.get("day"), data.get("month"))
return data


class FixedInitialYearRentSerializer(
FieldPermissionsSerializerMixin, serializers.ModelSerializer
@@ -421,10 +416,6 @@ class Meta:
"start_date",
"end_date",
"yearly_due_dates",
"seasonal_start_day",
"seasonal_start_month",
"seasonal_end_day",
"seasonal_end_month",
"manual_ratio",
"manual_ratio_previous",
"override_receivable_type",
@@ -464,10 +455,6 @@ class Meta:
"note",
"start_date",
"end_date",
"seasonal_start_day",
"seasonal_start_month",
"seasonal_end_day",
"seasonal_end_month",
"manual_ratio",
"manual_ratio_previous",
"override_receivable_type",
@@ -533,48 +520,15 @@ class Meta:
"equalized_rents",
"start_date",
"end_date",
"seasonal_start_day",
"seasonal_start_month",
"seasonal_end_day",
"seasonal_end_month",
"manual_ratio",
"manual_ratio_previous",
"override_receivable_type",
)

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 = [
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(
v is None for v in seasonal_values
):
raise serializers.ValidationError(
_("All seasonal values are required if one is set")
)

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")
)

start_day, start_month, end_day, end_month = seasonal_values
validate_seasonal_day_for_month(start_day, start_month)
validate_seasonal_day_for_month(end_day, end_month)

def validate_override_receivable_type_value(
self, rent_data: dict[str, Any]
) -> None:
44 changes: 0 additions & 44 deletions leasing/serializers/utils.py
Original file line number Diff line number Diff line change
@@ -4,7 +4,6 @@
from django.core.exceptions import ObjectDoesNotExist
from django.db.models import OneToOneRel
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from rest_framework.exceptions import ValidationError

@@ -306,46 +305,3 @@ def get_file_url(self, obj):

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) -> None:
"""Raises: serializers.ValidationError"""
if day is None and month is None:
return

if day is None and month is not None:
raise ValidationError({"day": _("Both day and month must be provided")})

if day is not None and month is None:
raise ValidationError({"month": _("Both day and month must be provided")})

if not isinstance(day, int):
raise ValidationError({"day": _("Day must be an integer")})

if not isinstance(month, int):
raise ValidationError({"month": _("Month must be an integer")})

max_days_in_month = {
1: 31, # January
# Since this a generic date and not a calendar date with year, accept only 28 days for February
2: 28, # February (non-leap year)
3: 31, # March
4: 30, # April
5: 31, # May
6: 30, # June
7: 31, # July
8: 31, # August
9: 30, # September
10: 31, # October
11: 30, # November
12: 31, # December
}

if month < 1 or month > 12:
raise ValidationError(
{"month": _(f"Invalid month: {month}. Month must be between 1 and 12.")}
)

max_day = max_days_in_month.get(month)
if day < 1 or day > max_day:
raise ValidationError({"day": _(f"Invalid day: {day} for month: {month}")})
Loading

0 comments on commit f15b78a

Please sign in to comment.