Skip to content

Commit

Permalink
Merge pull request #394 from codescalers/development_1.1_request_with…
Browse files Browse the repository at this point in the history
…_time

Update: Enhanced the vacation request, Changed the start, end dates to be datetime fields.
  • Loading branch information
maayarosama authored Apr 3, 2024
2 parents 7e402e8 + 8dd7baf commit c61451b
Show file tree
Hide file tree
Showing 8 changed files with 118 additions and 73 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Generated by Django 4.2.9 on 2024-04-01 21:40

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("cshr", "0005_alter_user_social_insurance_number"),
]

operations = [
migrations.AlterField(
model_name="vacation",
name="end_date",
field=models.DateTimeField(auto_now=True),
),
migrations.AlterField(
model_name="vacation",
name="from_date",
field=models.DateTimeField(auto_now=True),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Generated by Django 4.2.9 on 2024-04-01 22:37

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("cshr", "0006_alter_vacation_end_date_alter_vacation_from_date"),
]

operations = [
migrations.AlterField(
model_name="vacation",
name="end_date",
field=models.DateTimeField(),
),
migrations.AlterField(
model_name="vacation",
name="from_date",
field=models.DateTimeField(),
),
]
18 changes: 18 additions & 0 deletions server/cshr/migrations/0008_alter_vacation_actual_days.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 4.2.9 on 2024-04-01 22:52

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("cshr", "0007_alter_vacation_end_date_alter_vacation_from_date"),
]

operations = [
migrations.AlterField(
model_name="vacation",
name="actual_days",
field=models.FloatField(default=0),
),
]
8 changes: 5 additions & 3 deletions server/cshr/models/vacations.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,12 @@ class Vacation(Requests):
choices=REASON_CHOICES.choices,
default=REASON_CHOICES.ANNUAL_LEAVES,
)
from_date = models.DateField()
end_date = models.DateField()

from_date = models.DateTimeField()
end_date = models.DateTimeField()

change_log = models.JSONField(default=list)
actual_days = models.IntegerField(default=0)
actual_days = models.FloatField(default=0)

def ___str__(self):
return self.reason
Expand Down
2 changes: 1 addition & 1 deletion server/cshr/serializers/vacations.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
class VacationsSerializer(ModelSerializer):
class Meta:
model = Vacation
fields = "__all__"
fields = ["id", "reason", "from_date", "end_date", "applying_user", "approval_user", "type", "status", "created_at" ]
read_only_fields = ("applying_user", "approval_user", "type", "status")


Expand Down
17 changes: 14 additions & 3 deletions server/cshr/utils/vacation_balance_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,9 @@ def create_new_balance(self):
year=datetime.datetime.now().year, location=self.user.location
)[0]

annual_leaves: int = round(office_balance.annual_leaves / 12 * month)
leave_excuses: int = round(office_balance.leave_excuses / 12 * month)
emergency_leaves: int = round(office_balance.emergency_leaves / 12 * month)
annual_leaves: int = office_balance.annual_leaves / 12 * month
leave_excuses: int = office_balance.leave_excuses / 12 * month
emergency_leaves: int = office_balance.emergency_leaves / 12 * month

balance: VacationBalance = VacationBalance.objects.get_or_create(
user=self.user,
Expand Down Expand Up @@ -79,6 +79,17 @@ def update_user_balance(
return True
return f"There is no filed or attrbute named {reason} inside VacationBalance model."

def calculate_times(self, start_hour: str, end_hour: str, CORE_HOURS=8) -> float:
"""Calculate the hours with the CORE_HOURS"""
return (end_hour - start_hour) / CORE_HOURS

def is_valid_times(self, times: float, start_hour: str, end_hour: str,) -> bool:
"""
Calculate the times and get the actual values, e.g. the start date is 11:0 AM, and the end date is 01:00 PM then the actual time should be 2 hours.
"""
_times = self.calculate_times(start_hour=start_hour, end_hour=end_hour)
return _times == times

def get_actual_days(
self, user: User, start_date: datetime, end_date: datetime
) -> int:
Expand Down
30 changes: 15 additions & 15 deletions server/cshr/utils/wrappers.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,43 +19,43 @@ class LandingPageTypeEnum(Enum):
EVENT = "event"


"""
Wrap the vacation request with [type: string] field, to be ready to be sent to the calendar as the `type` field is required there.
"""
def wrap_vacation_request(vacation: Vacation) -> LandingPageVacationsSerializer : # type: ignore
"""
Wrap the vacation request with [type: string] field, to be ready to be sent to the calendar as the `type` field is required there.
"""
vacation_data = LandingPageVacationsSerializer(vacation).data
vacation_data["type"] = LandingPageTypeEnum.VACATION.value
vacation_data["applying_user_full_name"] = vacation.applying_user.full_name
return vacation_data

"""
Wrap the meeting request with [type: string] field, to be ready to be sent to the calendar as the `type` field is required there.
"""
def wrap_meeting_request(meeting: Meetings) -> MeetingsSerializer : # type: ignore
"""
Wrap the meeting request with [type: string] field, to be ready to be sent to the calendar as the `type` field is required there.
"""
meeting_data = MeetingsSerializer(meeting).data
meeting_data["type"] = LandingPageTypeEnum.MEETING.value
return meeting_data

"""
Wrap the event request with [type: string] field, to be ready to be sent to the calendar as the `type` field is required there.
"""
def wrap_event_request(event: Event) -> EventSerializer : # type: ignore
"""
Wrap the event request with [type: string] field, to be ready to be sent to the calendar as the `type` field is required there.
"""
event_data = EventSerializer(event).data
event_data["type"] = LandingPageTypeEnum.EVENT.value
return event_data

"""
Wrap the event request with [type: string] field, to be ready to be sent to the calendar as the `type` field is required there.
"""
def wrap_holiday_request(holiday: PublicHoliday) -> PublicHolidaySerializer : # type: ignore
"""
Wrap the event request with [type: string] field, to be ready to be sent to the calendar as the `type` field is required there.
"""
holiday_data = PublicHolidaySerializer(holiday).data
holiday_data["type"] = LandingPageTypeEnum.PUBLIC_HOLIDAY.value
return holiday_data

"""
Wrap the birthday request with [type: string] field, to be ready to be sent to the calendar as the `type` field is required there.
"""
def wrap_birthday_event(birthday: User) -> BaseUserSerializer : # type: ignore
"""
Wrap the birthday request with [type: string] field, to be ready to be sent to the calendar as the `type` field is required there.
"""
today = datetime.datetime.now()
birthday_data = BaseUserSerializer(birthday).data
birthday_data["type"] = LandingPageTypeEnum.BIRTHDAY.value
Expand Down
70 changes: 19 additions & 51 deletions server/cshr/views/vacations.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
)

# from cshr.celery.send_email import send_email_for_request
from cshr.celery.send_email import send_email_for_reply, send_email_for_request
from cshr.celery.send_email import send_email_for_reply
from cshr.models.vacations import (
REASON_CHOICES,
OfficeVacationBalance,
Expand Down Expand Up @@ -167,51 +167,6 @@ class BaseVacationsApiView(ListAPIView, GenericAPIView):

def post(self, request: Request) -> Response:
"""Method to create a new vacation request"""
if (
request.data.get("end_date")
and type(request.data["end_date"]) is str
and request.data.get("from_date")
and type(request.data["from_date"]) is str
):
start_date: List[str] = request.data.get("from_date").split(
"-"
) # Year, month, day

end_date: List[str] = request.data.get("end_date").split(
"-"
) # Year, month, day

try:
converted_start_date: datetime = datetime(
year=int(start_date[0]),
month=int(start_date[1]),
day=int(start_date[2]),
).date()
except Exception:
return CustomResponse.bad_request(
message="Invalid start date format, it must match the following pattern 'yyyy-mm-dd'.",
error=start_date,
)

try:
converted_end_date: datetime = datetime(
year=int(end_date[0]), month=int(end_date[1]), day=int(end_date[2])
).date()
except Exception:
return CustomResponse.bad_request(
message="Invalid end date format, it must match the following pattern 'yyyy-mm-dd'.",
error=start_date,
)

# Check if end date is lower than start date
if converted_end_date < converted_start_date:
return CustomResponse.bad_request(
message="The end date must be later than the start date."
)

request.data["from_date"] = converted_start_date
request.data["end_date"] = converted_end_date

serializer = self.get_serializer(data=request.data)
if serializer.is_valid():
start_date = serializer.validated_data.get("from_date")
Expand All @@ -236,8 +191,21 @@ def post(self, request: Request) -> Response:

reason: str = serializer.validated_data.get("reason")
user_reason_balance = applying_user.vacationbalance

vacation_days = v.get_actual_days(applying_user, start_date, end_date)

if start_date.day == end_date.day:
# The request is the same day
start_hour = start_date.hour
end_hour = end_date.hour
times = v.calculate_times(start_hour=start_hour, end_hour=end_hour)
if times < 1:
if not v.is_valid_times(times=times, start_hour=start_hour, end_hour=end_hour):
return CustomResponse.bad_request(
message=f"You've sent an invalid times, The days should match the {times}"
)
vacation_days = times

if reason == REASON_CHOICES.PUBLIC_HOLIDAYS:
return CustomResponse.bad_request(
message=f"You have sent an invalid reason {reason}",
Expand All @@ -253,6 +221,11 @@ def post(self, request: Request) -> Response:
else:
curr_balance = getattr(user_reason_balance, reason)

if curr_balance < vacation_days:
return CustomResponse.bad_request(
message=f"You only have {curr_balance} days left of reason '{reason.capitalize().replace('_', ' ')}'"
)

pending_vacations = Vacation.objects.filter(
status=STATUS_CHOICES.PENDING,
applying_user=applying_user,
Expand All @@ -261,11 +234,6 @@ def post(self, request: Request) -> Response:

chcked_balance = curr_balance - sum(pending_vacations)

if curr_balance < vacation_days:
return CustomResponse.bad_request(
message=f"You only have {curr_balance} days left of reason '{reason.capitalize().replace('_', ' ')}'"
)

if chcked_balance < vacation_days:
return CustomResponse.bad_request(
message=f"You have an additional pending request that deducts {sum(pending_vacations)} days from your balance even though the current balance for the '{reason.capitalize().replace('_', ' ')}' category is only {curr_balance} days."
Expand Down

0 comments on commit c61451b

Please sign in to comment.