test: employee leave balance report
- fix expired leaves calculation when filters span across 2 different allocation periods
This commit is contained in:
parent
dbfa463738
commit
c050ce49c2
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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)
|
@ -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()
|
||||
|
Loading…
x
Reference in New Issue
Block a user