test: employee leave balance report

- fix expired leaves calculation when filters span across 2 different allocation periods
This commit is contained in:
Rucha Mahabal 2022-02-28 09:58:12 +05:30
parent dbfa463738
commit c050ce49c2
5 changed files with 208 additions and 13 deletions

View File

@ -2,6 +2,7 @@
# License: GNU General Public License v3. See license.txt
import unittest
from contextlib import contextmanager
from datetime import timedelta
import frappe
@ -30,3 +31,24 @@ def make_holiday_list(name, from_date=getdate()-timedelta(days=10), to_date=getd
"holidays" : holiday_dates
}).insert()
return doc
@contextmanager
def set_holiday_list(holiday_list, company_name):
"""
Context manager for setting holiday list in tests
"""
try:
company = frappe.get_doc('Company', company_name)
previous_holiday_list = company.default_holiday_list
company.default_holiday_list = holiday_list
company.save()
yield
finally:
# restore holiday list setup
company = frappe.get_doc('Company', company_name)
company.default_holiday_list = previous_holiday_list
company.save()

View File

@ -501,7 +501,7 @@ class TestLeaveApplication(unittest.TestCase):
leave_type_name="_Test_CF_leave_expiry",
is_carry_forward=1,
expire_carry_forwarded_leaves_after_days=90)
leave_type.submit()
leave_type.insert()
create_carry_forwarded_allocation(employee, leave_type)
@ -723,19 +723,22 @@ def create_carry_forwarded_allocation(employee, leave_type):
carry_forward=1)
leave_allocation.submit()
def make_allocation_record(employee=None, leave_type=None, from_date=None, to_date=None):
def make_allocation_record(employee=None, leave_type=None, from_date=None, to_date=None, carry_forward=False, leaves=None):
allocation = frappe.get_doc({
"doctype": "Leave Allocation",
"employee": employee or "_T-Employee-00001",
"leave_type": leave_type or "_Test Leave Type",
"from_date": from_date or "2013-01-01",
"to_date": to_date or "2019-12-31",
"new_leaves_allocated": 30
"new_leaves_allocated": leaves or 30,
"carry_forward": carry_forward
})
allocation.insert(ignore_permissions=True)
allocation.submit()
return allocation
def get_employee():
return frappe.get_doc("Employee", "_T-Employee-00001")
@ -780,9 +783,10 @@ def allocate_leaves(employee, leave_period, leave_type, new_leaves_allocated, el
allocate_leave.submit()
def get_first_sunday(holiday_list):
month_start_date = get_first_day(nowdate())
month_end_date = get_last_day(nowdate())
def get_first_sunday(holiday_list, for_date=None):
date = for_date or getdate()
month_start_date = get_first_day(date)
month_end_date = get_last_day(date)
first_sunday = frappe.db.sql("""
select holiday_date from `tabHoliday`
where parent = %s

View File

@ -16,6 +16,8 @@ from erpnext.hr.doctype.leave_application.leave_application import (
def execute(filters=None):
filters = frappe._dict(filters or {})
if filters.to_date <= filters.from_date:
frappe.throw(_('"From Date" can not be greater than or equal to "To Date"'))
@ -103,7 +105,7 @@ def get_data(filters):
or ("HR Manager" in frappe.get_roles(user)):
if len(active_employees) > 1:
row = frappe._dict()
row.employee = employee.name,
row.employee = employee.name
row.employee_name = employee.employee_name
leaves_taken = get_leaves_for_period(employee.name, leave_type,
@ -114,7 +116,7 @@ def get_data(filters):
opening = get_opening_balance(employee.name, leave_type, filters, carry_forwarded_leaves)
row.leaves_allocated = new_allocation
row.leaves_expired = expired_leaves - leaves_taken if expired_leaves - leaves_taken > 0 else 0
row.leaves_expired = expired_leaves
row.opening_balance = opening
row.leaves_taken = leaves_taken
@ -202,7 +204,11 @@ def get_allocated_and_expired_leaves(from_date, to_date, employee, leave_type):
continue
if record.to_date < getdate(to_date):
# leave allocations ending before to_date, reduce leaves taken within that period
# since they are already used, they won't expire
expired_leaves += record.leaves
expired_leaves += get_leaves_for_period(employee, leave_type,
record.from_date, record.to_date)
if record.from_date >= getdate(from_date):
if record.is_carry_forward:

View File

@ -0,0 +1,162 @@
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
import unittest
import frappe
from frappe.utils import (
add_days,
add_months,
get_year_ending,
get_year_start,
getdate,
flt,
)
from erpnext.hr.doctype.employee.test_employee import make_employee
from erpnext.hr.report.employee_leave_balance.employee_leave_balance import execute
from erpnext.hr.doctype.holiday_list.test_holiday_list import set_holiday_list
from erpnext.hr.doctype.leave_type.test_leave_type import create_leave_type
from erpnext.hr.doctype.leave_application.test_leave_application import get_first_sunday, make_allocation_record
from erpnext.payroll.doctype.salary_slip.test_salary_slip import make_holiday_list, make_leave_application
from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import process_expired_allocation
test_records = frappe.get_test_records('Leave Type')
class TestEmployeeLeaveBalance(unittest.TestCase):
def setUp(self):
for dt in ['Leave Application', 'Leave Allocation', 'Salary Slip', 'Leave Ledger Entry', 'Leave Type']:
frappe.db.delete(dt)
frappe.set_user('Administrator')
self.employee_id = make_employee('test_emp_leave_balance@example.com', company='_Test Company')
self.holiday_list = make_holiday_list('_Test Emp Balance Holiday List', get_year_start(getdate()), get_year_ending(getdate()))
self.date = getdate()
self.year_start = getdate(get_year_start(self.date))
self.mid_year = add_months(self.year_start, 6)
self.year_end = getdate(get_year_ending(self.date))
def tearDown(self):
frappe.db.rollback()
@set_holiday_list('_Test Emp Balance Holiday List', '_Test Company')
def test_employee_leave_balance(self):
frappe.get_doc(test_records[0]).insert()
# 5 leaves
allocation1 = make_allocation_record(employee=self.employee_id, from_date=add_days(self.year_start, -11),
to_date=add_days(self.year_start, -1), leaves=5)
# 30 leaves
allocation2 = make_allocation_record(employee=self.employee_id, from_date=self.year_start, to_date=self.year_end)
# expires 5 leaves
process_expired_allocation()
# 4 days leave
first_sunday = get_first_sunday(self.holiday_list, for_date=self.year_start)
leave_application = make_leave_application(self.employee_id, first_sunday, add_days(first_sunday, 3), '_Test Leave Type')
leave_application.reload()
filters = {
'from_date': allocation1.from_date,
'to_date': allocation2.to_date,
'employee': self.employee_id
}
report = execute(filters)
expected_data = [{
'leave_type': '_Test Leave Type',
'employee': self.employee_id,
'employee_name': 'test_emp_leave_balance@example.com',
'leaves_allocated': flt(allocation1.new_leaves_allocated + allocation2.new_leaves_allocated),
'leaves_expired': flt(allocation1.new_leaves_allocated),
'opening_balance': flt(0),
'leaves_taken': flt(leave_application.total_leave_days),
'closing_balance': flt(allocation2.new_leaves_allocated - leave_application.total_leave_days),
'indent': 1
}]
self.assertEqual(report[1], expected_data)
@set_holiday_list('_Test Emp Balance Holiday List', '_Test Company')
def test_opening_balance_on_alloc_boundary_dates(self):
frappe.get_doc(test_records[0]).insert()
# 30 leaves allocated
allocation1 = make_allocation_record(employee=self.employee_id, from_date=self.year_start, to_date=self.year_end)
# 4 days leave application in the first allocation
first_sunday = get_first_sunday(self.holiday_list, for_date=self.year_start)
leave_application = make_leave_application(self.employee_id, first_sunday, add_days(first_sunday, 3), '_Test Leave Type')
leave_application.reload()
# Case 1: opening balance for first alloc boundary
filters = {
'from_date': self.year_start,
'to_date': self.year_end,
'employee': self.employee_id
}
report = execute(filters)
self.assertEqual(report[1][0].opening_balance, 0)
# Case 2: opening balance after leave application date
filters = {
'from_date': add_days(leave_application.to_date, 1),
'to_date': self.year_end,
'employee': self.employee_id
}
report = execute(filters)
self.assertEqual(report[1][0].opening_balance, (allocation1.new_leaves_allocated - leave_application.total_leave_days))
# Case 3: leave balance shows actual balance and not consumption balance as per remaining days near alloc end date
# eg: 3 days left for alloc to end, leave balance should still be 26 and not 3
filters = {
'from_date': add_days(self.year_end, -3),
'to_date': self.year_end,
'employee': self.employee_id
}
report = execute(filters)
self.assertEqual(report[1][0].opening_balance, (allocation1.new_leaves_allocated - leave_application.total_leave_days))
@set_holiday_list('_Test Emp Balance Holiday List', '_Test Company')
def test_opening_balance_considers_carry_forwarded_leaves(self):
leave_type = create_leave_type(
leave_type_name="_Test_CF_leave_expiry",
is_carry_forward=1)
leave_type.insert()
# 30 leaves allocated for first half of the year
allocation1 = make_allocation_record(employee=self.employee_id, from_date=self.year_start,
to_date=self.mid_year, leave_type=leave_type.name)
# 4 days leave application in the first allocation
first_sunday = get_first_sunday(self.holiday_list, for_date=self.year_start)
leave_application = make_leave_application(self.employee_id, first_sunday, add_days(first_sunday, 3), leave_type.name)
leave_application.reload()
# 30 leaves allocated for second half of the year + carry forward leaves (26) from the previous allocation
allocation2 = make_allocation_record(employee=self.employee_id, from_date=add_days(self.mid_year, 1), to_date=self.year_end,
carry_forward=True, leave_type=leave_type.name)
# Case 1: carry forwarded leaves considered in opening balance for second alloc
filters = {
'from_date': add_days(self.mid_year, 1),
'to_date': self.year_end,
'employee': self.employee_id
}
report = execute(filters)
# available leaves from old alloc
opening_balance = allocation1.new_leaves_allocated - leave_application.total_leave_days
self.assertEqual(report[1][0].opening_balance, opening_balance)
# Case 2: opening balance one day after alloc boundary = carry forwarded leaves + new leaves alloc
filters = {
'from_date': add_days(self.mid_year, 2),
'to_date': self.year_end,
'employee': self.employee_id
}
report = execute(filters)
# available leaves from old alloc
opening_balance = allocation2.new_leaves_allocated + (allocation1.new_leaves_allocated - leave_application.total_leave_days)
self.assertEqual(report[1][0].opening_balance, opening_balance)

View File

@ -1010,15 +1010,16 @@ def setup_test():
frappe.db.set_value('HR Settings', None, 'leave_status_notification_template', None)
frappe.db.set_value('HR Settings', None, 'leave_approval_notification_template', None)
def make_holiday_list():
def make_holiday_list(list_name=None, from_date=None, to_date=None):
fiscal_year = get_fiscal_year(nowdate(), company=erpnext.get_default_company())
holiday_list = frappe.db.exists("Holiday List", "Salary Slip Test Holiday List")
name = list_name or "Salary Slip Test Holiday List"
holiday_list = frappe.db.exists("Holiday List", name)
if not holiday_list:
holiday_list = frappe.get_doc({
"doctype": "Holiday List",
"holiday_list_name": "Salary Slip Test Holiday List",
"from_date": fiscal_year[1],
"to_date": fiscal_year[2],
"holiday_list_name": name,
"from_date": from_date or fiscal_year[1],
"to_date": to_date or fiscal_year[2],
"weekly_off": "Sunday"
}).insert()
holiday_list.get_weekly_off_dates()