From 34e238ce4f1dcd32de72851e44613c0b67018db7 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 23 May 2022 10:13:49 +0530 Subject: [PATCH] fix: Leave Encashment calculations (#31062) --- .../leave_encashment/leave_encashment.py | 34 ++++--- .../leave_encashment/test_leave_encashment.py | 89 +++++++++++++------ 2 files changed, 86 insertions(+), 37 deletions(-) diff --git a/erpnext/hr/doctype/leave_encashment/leave_encashment.py b/erpnext/hr/doctype/leave_encashment/leave_encashment.py index 0f655e3e0f..7c0f0db197 100644 --- a/erpnext/hr/doctype/leave_encashment/leave_encashment.py +++ b/erpnext/hr/doctype/leave_encashment/leave_encashment.py @@ -7,7 +7,7 @@ from frappe import _ from frappe.model.document import Document from frappe.utils import getdate, nowdate -from erpnext.hr.doctype.leave_allocation.leave_allocation import get_unused_leaves +from erpnext.hr.doctype.leave_application.leave_application import get_leaves_for_period from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import create_leave_ledger_entry from erpnext.hr.utils import set_employee_name, validate_active_employee from erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment import ( @@ -107,7 +107,10 @@ class LeaveEncashment(Document): self.leave_balance = ( allocation.total_leaves_allocated - allocation.carry_forwarded_leaves_count - - get_unused_leaves(self.employee, self.leave_type, allocation.from_date, self.encashment_date) + # adding this because the function returns a -ve number + + get_leaves_for_period( + self.employee, self.leave_type, allocation.from_date, self.encashment_date + ) ) encashable_days = self.leave_balance - frappe.db.get_value( @@ -126,14 +129,25 @@ class LeaveEncashment(Document): return True def get_leave_allocation(self): - leave_allocation = frappe.db.sql( - """select name, to_date, total_leaves_allocated, carry_forwarded_leaves_count from `tabLeave Allocation` where '{0}' - between from_date and to_date and docstatus=1 and leave_type='{1}' - and employee= '{2}'""".format( - self.encashment_date or getdate(nowdate()), self.leave_type, self.employee - ), - as_dict=1, - ) # nosec + date = self.encashment_date or getdate() + + LeaveAllocation = frappe.qb.DocType("Leave Allocation") + leave_allocation = ( + frappe.qb.from_(LeaveAllocation) + .select( + LeaveAllocation.name, + LeaveAllocation.from_date, + LeaveAllocation.to_date, + LeaveAllocation.total_leaves_allocated, + LeaveAllocation.carry_forwarded_leaves_count, + ) + .where( + ((LeaveAllocation.from_date <= date) & (date <= LeaveAllocation.to_date)) + & (LeaveAllocation.docstatus == 1) + & (LeaveAllocation.leave_type == self.leave_type) + & (LeaveAllocation.employee == self.employee) + ) + ).run(as_dict=True) return leave_allocation[0] if leave_allocation else None diff --git a/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py b/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py index 83eb969feb..d06b6a3764 100644 --- a/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py +++ b/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py @@ -4,26 +4,42 @@ import unittest import frappe -from frappe.utils import add_months, today +from frappe.tests.utils import FrappeTestCase +from frappe.utils import add_days, get_year_ending, get_year_start, getdate from erpnext.hr.doctype.employee.test_employee import make_employee +from erpnext.hr.doctype.holiday_list.test_holiday_list import set_holiday_list from erpnext.hr.doctype.leave_period.test_leave_period import create_leave_period from erpnext.hr.doctype.leave_policy.test_leave_policy import create_leave_policy from erpnext.hr.doctype.leave_policy_assignment.leave_policy_assignment import ( create_assignment_for_multiple_employees, ) +from erpnext.payroll.doctype.salary_slip.test_salary_slip import ( + make_holiday_list, + make_leave_application, +) from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure -test_dependencies = ["Leave Type"] +test_records = frappe.get_test_records("Leave Type") -class TestLeaveEncashment(unittest.TestCase): +class TestLeaveEncashment(FrappeTestCase): def setUp(self): - frappe.db.sql("""delete from `tabLeave Period`""") - frappe.db.sql("""delete from `tabLeave Policy Assignment`""") - frappe.db.sql("""delete from `tabLeave Allocation`""") - frappe.db.sql("""delete from `tabLeave Ledger Entry`""") - frappe.db.sql("""delete from `tabAdditional Salary`""") + frappe.db.delete("Leave Period") + frappe.db.delete("Leave Policy Assignment") + frappe.db.delete("Leave Allocation") + frappe.db.delete("Leave Ledger Entry") + frappe.db.delete("Additional Salary") + frappe.db.delete("Leave Encashment") + + if not frappe.db.exists("Leave Type", "_Test Leave Type Encashment"): + frappe.get_doc(test_records[2]).insert() + + date = getdate() + year_start = getdate(get_year_start(date)) + year_end = getdate(get_year_ending(date)) + + make_holiday_list("_Test Leave Encashment", year_start, year_end) # create the leave policy leave_policy = create_leave_policy( @@ -32,9 +48,9 @@ class TestLeaveEncashment(unittest.TestCase): leave_policy.submit() # create employee, salary structure and assignment - self.employee = make_employee("test_employee_encashment@example.com") + self.employee = make_employee("test_employee_encashment@example.com", company="_Test Company") - self.leave_period = create_leave_period(add_months(today(), -3), add_months(today(), 3)) + self.leave_period = create_leave_period(year_start, year_end, "_Test Company") data = { "assignment_based_on": "Leave Period", @@ -53,27 +69,15 @@ class TestLeaveEncashment(unittest.TestCase): other_details={"leave_encashment_amount_per_day": 50}, ) - def tearDown(self): - for dt in [ - "Leave Period", - "Leave Allocation", - "Leave Ledger Entry", - "Additional Salary", - "Leave Encashment", - "Salary Structure", - "Leave Policy", - ]: - frappe.db.sql("delete from `tab%s`" % dt) - + @set_holiday_list("_Test Leave Encashment", "_Test Company") def test_leave_balance_value_and_amount(self): - frappe.db.sql("""delete from `tabLeave Encashment`""") leave_encashment = frappe.get_doc( dict( doctype="Leave Encashment", employee=self.employee, leave_type="_Test Leave Type Encashment", leave_period=self.leave_period.name, - payroll_date=today(), + encashment_date=self.leave_period.to_date, currency="INR", ) ).insert() @@ -88,15 +92,46 @@ class TestLeaveEncashment(unittest.TestCase): add_sal = frappe.get_all("Additional Salary", filters={"ref_docname": leave_encashment.name})[0] self.assertTrue(add_sal) - def test_creation_of_leave_ledger_entry_on_submit(self): - frappe.db.sql("""delete from `tabLeave Encashment`""") + @set_holiday_list("_Test Leave Encashment", "_Test Company") + def test_leave_balance_value_with_leaves_and_amount(self): + date = self.leave_period.from_date + leave_application = make_leave_application( + self.employee, date, add_days(date, 3), "_Test Leave Type Encashment" + ) + leave_application.reload() + leave_encashment = frappe.get_doc( dict( doctype="Leave Encashment", employee=self.employee, leave_type="_Test Leave Type Encashment", leave_period=self.leave_period.name, - payroll_date=today(), + encashment_date=self.leave_period.to_date, + currency="INR", + ) + ).insert() + + self.assertEqual(leave_encashment.leave_balance, 10 - leave_application.total_leave_days) + # encashable days threshold is 5, total leaves are 6, so encashable days = 6-5 = 1 + # with charge of 50 per day + self.assertEqual(leave_encashment.encashable_days, leave_encashment.leave_balance - 5) + self.assertEqual(leave_encashment.encashment_amount, 50) + + leave_encashment.submit() + + # assert links + add_sal = frappe.get_all("Additional Salary", filters={"ref_docname": leave_encashment.name})[0] + self.assertTrue(add_sal) + + @set_holiday_list("_Test Leave Encashment", "_Test Company") + def test_creation_of_leave_ledger_entry_on_submit(self): + leave_encashment = frappe.get_doc( + dict( + doctype="Leave Encashment", + employee=self.employee, + leave_type="_Test Leave Type Encashment", + leave_period=self.leave_period.name, + encashment_date=self.leave_period.to_date, currency="INR", ) ).insert()