diff --git a/hrms/__init__.py b/hrms/__init__.py index 4fcf9b8bb2..897518d00d 100644 --- a/hrms/__init__.py +++ b/hrms/__init__.py @@ -1 +1 @@ -__version__ = "14.0.1" +__version__ = "14.0.2" diff --git a/hrms/hr/doctype/leave_allocation/test_earned_leaves.py b/hrms/hr/doctype/leave_allocation/test_earned_leaves.py index dc9a1b04a3..cdf166ea41 100644 --- a/hrms/hr/doctype/leave_allocation/test_earned_leaves.py +++ b/hrms/hr/doctype/leave_allocation/test_earned_leaves.py @@ -24,7 +24,7 @@ calculate_pro_rated_leaves, create_assignment_for_multiple_employees, ) -from hrms.hr.utils import allocate_earned_leaves +from hrms.hr.utils import allocate_earned_leaves, round_earned_leaves from hrms.payroll.doctype.salary_slip.test_salary_slip import make_holiday_list from hrms.tests.test_utils import get_first_sunday @@ -260,7 +260,9 @@ def test_allocate_on_date_of_joining(self): self.employee, allocate_on_day="Date of Joining", start_date=start_date ) leaves_allocated = get_allocated_leaves(leave_policy_assignments[0]) - pro_rated_leave = calculate_pro_rated_leaves(1, doj, start_date, end_date) + pro_rated_leave = round_earned_leaves( + calculate_pro_rated_leaves(1, doj, start_date, end_date), "0.5" + ) self.assertEqual(leaves_allocated, pro_rated_leave) # Case 2: Doesn't allocate before the current month's doj (via scheduler) @@ -308,7 +310,9 @@ def test_pro_rated_allocation_via_scheduler(self): frappe.flags.current_date = add_days(doj, -1) allocate_earned_leaves() leaves_allocated = get_allocated_leaves(leave_policy_assignments[0]) - pro_rated_leave = calculate_pro_rated_leaves(1, doj, start_date, get_last_day(start_date)) + pro_rated_leave = round_earned_leaves( + calculate_pro_rated_leaves(1, doj, start_date, get_last_day(start_date)), "0.5" + ) self.assertEqual(leaves_allocated, pro_rated_leave) @set_holiday_list("Salary Slip Test Holiday List", "_Test Company") diff --git a/hrms/hr/doctype/leave_policy_assignment/leave_policy_assignment.py b/hrms/hr/doctype/leave_policy_assignment/leave_policy_assignment.py index eaac1a4240..6cb0361904 100644 --- a/hrms/hr/doctype/leave_policy_assignment/leave_policy_assignment.py +++ b/hrms/hr/doctype/leave_policy_assignment/leave_policy_assignment.py @@ -136,10 +136,10 @@ def get_new_leaves(self, new_leaves_allocated, leave_details, date_of_joining): ) # Earned Leaves and Compensatory Leaves are allocated by scheduler, initially allocate 0 - if leave_details.is_compensatory == 1: + if leave_details.is_compensatory: new_leaves_allocated = 0 - elif leave_details.is_earned_leave == 1: + elif leave_details.is_earned_leave: if not self.assignment_based_on: new_leaves_allocated = 0 else: @@ -148,40 +148,71 @@ def get_new_leaves(self, new_leaves_allocated, leave_details, date_of_joining): new_leaves_allocated, leave_details, date_of_joining ) - new_leaves_allocated = self.get_pro_rated_leaves( - date_of_joining, leave_details, new_leaves_allocated - ) + else: + # calculate pro-rated leaves for other leave types + new_leaves_allocated = calculate_pro_rated_leaves( + new_leaves_allocated, + date_of_joining, + self.effective_from, + self.effective_to, + is_earned_leave=False, + ) return flt(new_leaves_allocated, precision) def get_leaves_for_passed_months(self, new_leaves_allocated, leave_details, date_of_joining): from hrms.hr.utils import get_monthly_earned_leave - current_date = frappe.flags.current_date or getdate() - if current_date > getdate(self.effective_to): - current_date = getdate(self.effective_to) + def _get_current_and_from_date(): + current_date = frappe.flags.current_date or getdate() + if current_date > getdate(self.effective_to): + current_date = getdate(self.effective_to) + + from_date = getdate(self.effective_from) + if getdate(date_of_joining) > from_date: + from_date = getdate(date_of_joining) + + return current_date, from_date - from_date = getdate(self.effective_from) - if getdate(date_of_joining) > from_date: - from_date = getdate(date_of_joining) + def _get_months_passed(current_date, from_date, consider_current_month): + months_passed = 0 + if current_date.year == from_date.year and current_date.month >= from_date.month: + months_passed = current_date.month - from_date.month + if consider_current_month: + months_passed += 1 - months_passed = 0 + elif current_date.year > from_date.year: + months_passed = (12 - from_date.month) + current_date.month + if consider_current_month: + months_passed += 1 - if current_date.year == from_date.year and current_date.month >= from_date.month: - months_passed = current_date.month - from_date.month - if is_earned_leave_applicable_for_current_month(date_of_joining, leave_details.allocate_on_day): - months_passed += 1 + return months_passed - elif current_date.year > from_date.year: - months_passed = (12 - from_date.month) + current_date.month - if is_earned_leave_applicable_for_current_month(date_of_joining, leave_details.allocate_on_day): - months_passed += 1 + def _get_pro_rata_period_end_date(consider_current_month): + # for earned leave, pro-rata period ends on the last day of the month + date = getdate(frappe.flags.current_date) or getdate() + if consider_current_month: + period_end_date = get_last_day(date) + else: + period_end_date = get_last_day(add_months(date, -1)) + + return period_end_date + + consider_current_month = is_earned_leave_applicable_for_current_month( + date_of_joining, leave_details.allocate_on_day + ) + current_date, from_date = _get_current_and_from_date() + months_passed = _get_months_passed(current_date, from_date, consider_current_month) if months_passed > 0: + period_end_date = _get_pro_rata_period_end_date(consider_current_month) monthly_earned_leave = get_monthly_earned_leave( + date_of_joining, new_leaves_allocated, leave_details.earned_leave_frequency, leave_details.rounding, + self.effective_from, + period_end_date, ) new_leaves_allocated = monthly_earned_leave * months_passed else: @@ -189,45 +220,22 @@ def get_leaves_for_passed_months(self, new_leaves_allocated, leave_details, date return new_leaves_allocated - def get_pro_rated_leaves(self, date_of_joining, leave_details, new_leaves_allocated): - """ - Calculates pro-rated leaves for the months passed - for employees joining after the beginning of the given leave period - """ - # no need to prorate if employee joined before the leave period - if not new_leaves_allocated or getdate(date_of_joining) <= getdate(self.effective_from): - return new_leaves_allocated - - # for earned leave, pro-rata period ends on the last day of the month - date = getdate(frappe.flags.current_date) or getdate() - - if leave_details.is_earned_leave: - if is_earned_leave_applicable_for_current_month(date_of_joining, leave_details.allocate_on_day): - period_end_date = get_last_day(date) - else: - period_end_date = get_last_day(add_months(date, -1)) - else: - period_end_date = self.effective_to - - new_leaves_allocated = calculate_pro_rated_leaves( - new_leaves_allocated, date_of_joining, self.effective_from, period_end_date - ) - - # don't round earned leaves - if not leave_details.is_earned_leave: - new_leaves_allocated = ceil(new_leaves_allocated) - - return new_leaves_allocated +def calculate_pro_rated_leaves( + leaves, date_of_joining, period_start_date, period_end_date, is_earned_leave=False +): + if not leaves or getdate(date_of_joining) <= getdate(period_start_date): + return leaves -def calculate_pro_rated_leaves(leaves, date_of_joining, period_start_date, period_end_date): precision = cint(frappe.db.get_single_value("System Settings", "float_precision", cache=True)) actual_period = date_diff(period_end_date, date_of_joining) + 1 complete_period = date_diff(period_end_date, period_start_date) + 1 leaves *= actual_period / complete_period - return flt(leaves, precision) + if is_earned_leave: + return flt(leaves, precision) + return ceil(leaves) def is_earned_leave_applicable_for_current_month(date_of_joining, allocate_on_day): diff --git a/hrms/hr/utils.py b/hrms/hr/utils.py index ce05b935ef..d884587bf9 100644 --- a/hrms/hr/utils.py +++ b/hrms/hr/utils.py @@ -334,21 +334,9 @@ def allocate_earned_leaves(): def update_previous_leave_allocation(allocation, annual_allocation, e_leave_type, date_of_joining): - def _calculate_pro_rated_leaves(earned_leaves): - today_date = frappe.flags.current_date or getdate() - period_end_date = get_last_day(today_date) - period_start_date = get_first_day(today_date) - - if period_start_date <= date_of_joining <= period_end_date: - return calculate_pro_rated_leaves( - earned_leaves, date_of_joining, period_start_date, period_end_date - ) - return earned_leaves - earned_leaves = get_monthly_earned_leave( - annual_allocation, e_leave_type.earned_leave_frequency, e_leave_type.rounding + date_of_joining, annual_allocation, e_leave_type.earned_leave_frequency, e_leave_type.rounding ) - earned_leaves = _calculate_pro_rated_leaves(earned_leaves) allocation = frappe.get_doc("Leave Allocation", allocation.name) new_allocation = flt(allocation.total_leaves_allocated) + flt(earned_leaves) @@ -372,18 +360,42 @@ def _calculate_pro_rated_leaves(earned_leaves): allocation.add_comment(comment_type="Info", text=text) -def get_monthly_earned_leave(annual_leaves, frequency, rounding): +def get_monthly_earned_leave( + date_of_joining, + annual_leaves, + frequency, + rounding, + period_start_date=None, + period_end_date=None, +): earned_leaves = 0.0 divide_by_frequency = {"Yearly": 1, "Half-Yearly": 2, "Quarterly": 4, "Monthly": 12} if annual_leaves: earned_leaves = flt(annual_leaves) / divide_by_frequency[frequency] - if rounding: - if rounding == "0.25": - earned_leaves = round(earned_leaves * 4) / 4 - elif rounding == "0.5": - earned_leaves = round(earned_leaves * 2) / 2 - else: - earned_leaves = round(earned_leaves) + + if not (period_start_date or period_end_date): + today_date = frappe.flags.current_date or getdate() + period_end_date = get_last_day(today_date) + period_start_date = get_first_day(today_date) + + earned_leaves = calculate_pro_rated_leaves( + earned_leaves, date_of_joining, period_start_date, period_end_date, is_earned_leave=True + ) + earned_leaves = round_earned_leaves(earned_leaves, rounding) + + return earned_leaves + + +def round_earned_leaves(earned_leaves, rounding): + if not rounding: + return earned_leaves + + if rounding == "0.25": + earned_leaves = round(earned_leaves * 4) / 4 + elif rounding == "0.5": + earned_leaves = round(earned_leaves * 2) / 2 + else: + earned_leaves = round(earned_leaves) return earned_leaves