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
|
# License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
|
from contextlib import contextmanager
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
@ -30,3 +31,24 @@ def make_holiday_list(name, from_date=getdate()-timedelta(days=10), to_date=getd
|
|||||||
"holidays" : holiday_dates
|
"holidays" : holiday_dates
|
||||||
}).insert()
|
}).insert()
|
||||||
return doc
|
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",
|
leave_type_name="_Test_CF_leave_expiry",
|
||||||
is_carry_forward=1,
|
is_carry_forward=1,
|
||||||
expire_carry_forwarded_leaves_after_days=90)
|
expire_carry_forwarded_leaves_after_days=90)
|
||||||
leave_type.submit()
|
leave_type.insert()
|
||||||
|
|
||||||
create_carry_forwarded_allocation(employee, leave_type)
|
create_carry_forwarded_allocation(employee, leave_type)
|
||||||
|
|
||||||
@ -723,19 +723,22 @@ def create_carry_forwarded_allocation(employee, leave_type):
|
|||||||
carry_forward=1)
|
carry_forward=1)
|
||||||
leave_allocation.submit()
|
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({
|
allocation = frappe.get_doc({
|
||||||
"doctype": "Leave Allocation",
|
"doctype": "Leave Allocation",
|
||||||
"employee": employee or "_T-Employee-00001",
|
"employee": employee or "_T-Employee-00001",
|
||||||
"leave_type": leave_type or "_Test Leave Type",
|
"leave_type": leave_type or "_Test Leave Type",
|
||||||
"from_date": from_date or "2013-01-01",
|
"from_date": from_date or "2013-01-01",
|
||||||
"to_date": to_date or "2019-12-31",
|
"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.insert(ignore_permissions=True)
|
||||||
allocation.submit()
|
allocation.submit()
|
||||||
|
|
||||||
|
return allocation
|
||||||
|
|
||||||
def get_employee():
|
def get_employee():
|
||||||
return frappe.get_doc("Employee", "_T-Employee-00001")
|
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()
|
allocate_leave.submit()
|
||||||
|
|
||||||
|
|
||||||
def get_first_sunday(holiday_list):
|
def get_first_sunday(holiday_list, for_date=None):
|
||||||
month_start_date = get_first_day(nowdate())
|
date = for_date or getdate()
|
||||||
month_end_date = get_last_day(nowdate())
|
month_start_date = get_first_day(date)
|
||||||
|
month_end_date = get_last_day(date)
|
||||||
first_sunday = frappe.db.sql("""
|
first_sunday = frappe.db.sql("""
|
||||||
select holiday_date from `tabHoliday`
|
select holiday_date from `tabHoliday`
|
||||||
where parent = %s
|
where parent = %s
|
||||||
|
@ -16,6 +16,8 @@ from erpnext.hr.doctype.leave_application.leave_application import (
|
|||||||
|
|
||||||
|
|
||||||
def execute(filters=None):
|
def execute(filters=None):
|
||||||
|
filters = frappe._dict(filters or {})
|
||||||
|
|
||||||
if filters.to_date <= filters.from_date:
|
if filters.to_date <= filters.from_date:
|
||||||
frappe.throw(_('"From Date" can not be greater than or equal to "To 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)):
|
or ("HR Manager" in frappe.get_roles(user)):
|
||||||
if len(active_employees) > 1:
|
if len(active_employees) > 1:
|
||||||
row = frappe._dict()
|
row = frappe._dict()
|
||||||
row.employee = employee.name,
|
row.employee = employee.name
|
||||||
row.employee_name = employee.employee_name
|
row.employee_name = employee.employee_name
|
||||||
|
|
||||||
leaves_taken = get_leaves_for_period(employee.name, leave_type,
|
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)
|
opening = get_opening_balance(employee.name, leave_type, filters, carry_forwarded_leaves)
|
||||||
|
|
||||||
row.leaves_allocated = new_allocation
|
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.opening_balance = opening
|
||||||
row.leaves_taken = leaves_taken
|
row.leaves_taken = leaves_taken
|
||||||
|
|
||||||
@ -202,7 +204,11 @@ def get_allocated_and_expired_leaves(from_date, to_date, employee, leave_type):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
if record.to_date < getdate(to_date):
|
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 += 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.from_date >= getdate(from_date):
|
||||||
if record.is_carry_forward:
|
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_status_notification_template', None)
|
||||||
frappe.db.set_value('HR Settings', None, 'leave_approval_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())
|
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:
|
if not holiday_list:
|
||||||
holiday_list = frappe.get_doc({
|
holiday_list = frappe.get_doc({
|
||||||
"doctype": "Holiday List",
|
"doctype": "Holiday List",
|
||||||
"holiday_list_name": "Salary Slip Test Holiday List",
|
"holiday_list_name": name,
|
||||||
"from_date": fiscal_year[1],
|
"from_date": from_date or fiscal_year[1],
|
||||||
"to_date": fiscal_year[2],
|
"to_date": to_date or fiscal_year[2],
|
||||||
"weekly_off": "Sunday"
|
"weekly_off": "Sunday"
|
||||||
}).insert()
|
}).insert()
|
||||||
holiday_list.get_weekly_off_dates()
|
holiday_list.get_weekly_off_dates()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user