From c1bc70bb0e85271ae92053d37fba867b499cca33 Mon Sep 17 00:00:00 2001 From: amchangama Date: Tue, 18 Jul 2023 15:00:34 +0200 Subject: [PATCH] Added Employment change model, refactoring working calculation --- timed/employment/models.py | 114 ++++++++++++++++++++--------------- timed/employment/scheduls.py | 70 +++++++++++++++++++++ 2 files changed, 136 insertions(+), 48 deletions(-) create mode 100644 timed/employment/scheduls.py diff --git a/timed/employment/models.py b/timed/employment/models.py index 8e54db54..18953b5f 100644 --- a/timed/employment/models.py +++ b/timed/employment/models.py @@ -1,6 +1,7 @@ """Models for the employment app.""" from datetime import date, timedelta +from turtle import mode from dateutil import rrule from django.conf import settings @@ -13,7 +14,7 @@ from timed.models import WeekdaysField from timed.projects.models import CustomerAssignee, ProjectAssignee, TaskAssignee from timed.tracking.models import Absence - +from timed.employment.scheduls import get_schedule class Location(models.Model): """Location model. @@ -259,54 +260,11 @@ def calculate_worktime(self, start, end): :returns: tuple of 3 values reported, expected and delta in given time frame """ - from timed.tracking.models import Absence, Report - - # shorten time frame to employment - start = max(start, self.start_date) - end = min(self.end_date or date.today(), end) - - # workdays is in isoweekday, byweekday expects Monday to be zero - week_workdays = [int(day) - 1 for day in self.location.workdays] - workdays = rrule.rrule( - rrule.DAILY, dtstart=start, until=end, byweekday=week_workdays - ).count() - - # converting workdays as db expects 1 (Sunday) to 7 (Saturday) - workdays_db = [ - # special case for Sunday - int(day) == 7 and 1 or int(day) + 1 - for day in self.location.workdays - ] - holidays = PublicHoliday.objects.filter( - location=self.location, - date__gte=start, - date__lte=end, - date__week_day__in=workdays_db, - ).count() - - expected_worktime = self.worktime_per_day * (workdays - holidays) - - overtime_credit_data = OvertimeCredit.objects.filter( - user=self.user_id, date__gte=start, date__lte=end - ).aggregate(total_duration=Sum("duration")) - overtime_credit = overtime_credit_data["total_duration"] or timedelta() - - reported_worktime_data = Report.objects.filter( - user=self.user_id, date__gte=start, date__lte=end - ).aggregate(duration_total=Sum("duration")) - reported_worktime = reported_worktime_data["duration_total"] or timedelta() - - absences = sum( - [ - absence.calculate_duration(self) - for absence in Absence.objects.filter( - user=self.user_id, date__gte=start, date__lte=end - ).select_related("absence_type") - ], - timedelta(), - ) - reported = reported_worktime + absences + overtime_credit + schedule = get_schedule(start,end,self.employment) + + expected_worktime = self.worktime_per_day * (schedule(0) - schedule(1)) + reported = schedule(3) + schedule(4) + schedule(2) return (reported, expected_worktime, reported - expected_worktime) @@ -414,3 +372,63 @@ def get_active_employment(self): return current_employment except Employment.DoesNotExist: return None + +class EmploymentChange(models.Model): + + """ Employment working time percentage change, It can be + 1- Increasing the working time percentage + 2- Decreasing the working time percentage + """ + + change_choices = [ + ('increase','Increase'), + ('decrease','Decrease') + ] + + employment = models.ForeignKey(Employment,on_delete=models.PROTECT,related_name="employment_change") + start_date = models.DateField() + end_date = models.DateField() + change_type = models.CharField(max_length=15, choices=change_choices) + change_percentage = models.FloatField() + + + + def calculate_employment_change(self, start, end): + """ + Calculate employment working time change + """ + + #Get employment schedule + schedule = get_schedule(start,end,self.employment) + + # new working time percentage + if self.change_type == 'increase': + new_percentage = self.employment.percentage + self.change_percentage + else: + new_percentage = self.employment.percentage - self.change_percentage + + new_worktime = (self.employment.worktime_per_day * new_percentage)/100 + expected_worktime = new_worktime * (schedule(0) - schedule(1)) + reported = schedule(3) + schedule(4) + schedule(2) + + return (reported, expected_worktime, reported - expected_worktime) + + + + + + + + + + + + + + + + + + + + diff --git a/timed/employment/scheduls.py b/timed/employment/scheduls.py new file mode 100644 index 00000000..ac89f0ce --- /dev/null +++ b/timed/employment/scheduls.py @@ -0,0 +1,70 @@ +from datetime import date, timedelta +from dateutil import rrule +from django.db.models import Sum +from typing import Union + +from timed.employment.models import Employment, EmploymentChange, PublicHoliday,OvertimeCredit +from timed.tracking.models import Absence, Report + + +def get_schedule(start,end,employment: Union[Employment, EmploymentChange]): + + """ + Obtaining weekly working days, holidays, overtime credit, + reported working time and absences + + :params start: working starting time on a given day + :params end : working end time of a given day + :employment : Employement or EmploymentChange object + """ + + if isinstance(employment, Employment): + location = employment.location + user_id = employment.user.id + elif isinstance(employment, EmploymentChange): + location = employment.employment.location + user_id = employment.employment.user.id + + # shorten time frame to employment + start = max(start, employment.start_date) + end = min(employment.end_date or date.today(), end) + + week_workdays = [int(day) - 1 for day in employment.location.workdays] + workdays = rrule.rrule( + rrule.DAILY, dtstart=start, until=end, byweekday=week_workdays + ).count() + + # converting workdays as db expects 1 (Sunday) to 7 (Saturday) + workdays_db = [ + # special case for Sunday + int(day) == 7 and 1 or int(day) + 1 + for day in location.workdays + ] + holidays = PublicHoliday.objects.filter( + location=location, + date__gte=start, + date__lte=end, + date__week_day__in=workdays_db, + ).count() + + overtime_credit_data = OvertimeCredit.objects.filter( + user=user_id, date__gte=start, date__lte=end + ).aggregate(total_duration=Sum("duration")) + overtime_credit = overtime_credit_data["total_duration"] or timedelta() + + reported_worktime_data = Report.objects.filter( + user=user_id, date__gte=start, date__lte=end + ).aggregate(duration_total=Sum("duration")) + reported_worktime = reported_worktime_data["duration_total"] or timedelta() + + absences = sum( + [ + absence.calculate_duration(employment) + for absence in Absence.objects.filter( + user=user_id, date__gte=start, date__lte=end + ).select_related("absence_type") + ], + timedelta(), + ) + + return (workdays,holidays,overtime_credit,reported_worktime,absences) \ No newline at end of file