feat: employee leave balance reports (#20754)
* feat: Employee leave balance summary report new design * feat: Employee leave balance report new design * fix: leave based on multiple holiday list Co-authored-by: Nabin Hait <nabinhait@gmail.com>
This commit is contained in:
parent
ba70e7e8bc
commit
ded418e31d
@ -374,7 +374,8 @@ class LeaveApplication(Document):
|
||||
leaves=self.total_leave_days * -1,
|
||||
from_date=self.from_date,
|
||||
to_date=self.to_date,
|
||||
is_lwp=lwp
|
||||
is_lwp=lwp,
|
||||
holiday_list=get_holiday_list_for_employee(self.employee)
|
||||
)
|
||||
create_leave_ledger_entry(self, args, submit)
|
||||
|
||||
@ -384,7 +385,9 @@ class LeaveApplication(Document):
|
||||
from_date=self.from_date,
|
||||
to_date=expiry_date,
|
||||
leaves=(date_diff(expiry_date, self.from_date) + 1) * -1,
|
||||
is_lwp=lwp
|
||||
is_lwp=lwp,
|
||||
holiday_list=get_holiday_list_for_employee(self.employee),
|
||||
|
||||
)
|
||||
create_leave_ledger_entry(self, args, submit)
|
||||
|
||||
@ -410,7 +413,7 @@ def get_allocation_expiry(employee, leave_type, to_date, from_date):
|
||||
return expiry[0]['to_date'] if expiry else None
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_number_of_leave_days(employee, leave_type, from_date, to_date, half_day = None, half_day_date = None):
|
||||
def get_number_of_leave_days(employee, leave_type, from_date, to_date, half_day = None, half_day_date = None, holiday_list = None):
|
||||
number_of_days = 0
|
||||
if cint(half_day) == 1:
|
||||
if from_date == to_date:
|
||||
@ -424,7 +427,7 @@ def get_number_of_leave_days(employee, leave_type, from_date, to_date, half_day
|
||||
number_of_days = date_diff(to_date, from_date) + 1
|
||||
|
||||
if not frappe.db.get_value("Leave Type", leave_type, "include_holiday"):
|
||||
number_of_days = flt(number_of_days) - flt(get_holidays(employee, from_date, to_date))
|
||||
number_of_days = flt(number_of_days) - flt(get_holidays(employee, from_date, to_date, holiday_list=holiday_list))
|
||||
return number_of_days
|
||||
|
||||
@frappe.whitelist()
|
||||
@ -575,7 +578,7 @@ def get_leaves_for_period(employee, leave_type, from_date, to_date):
|
||||
{'name': leave_entry.transaction_name}, ['half_day_date'])
|
||||
|
||||
leave_days += get_number_of_leave_days(employee, leave_type,
|
||||
leave_entry.from_date, leave_entry.to_date, half_day, half_day_date) * -1
|
||||
leave_entry.from_date, leave_entry.to_date, half_day, half_day_date, holiday_list=leave_entry.holiday_list) * -1
|
||||
|
||||
return leave_days
|
||||
|
||||
@ -589,7 +592,7 @@ def get_leave_entries(employee, leave_type, from_date, to_date):
|
||||
''' Returns leave entries between from_date and to_date. '''
|
||||
return frappe.db.sql("""
|
||||
SELECT
|
||||
employee, leave_type, from_date, to_date, leaves, transaction_name, transaction_type,
|
||||
employee, leave_type, from_date, to_date, leaves, transaction_name, transaction_type, holiday_list,
|
||||
is_carry_forward, is_expired
|
||||
FROM `tabLeave Ledger Entry`
|
||||
WHERE employee=%(employee)s AND leave_type=%(leave_type)s
|
||||
@ -607,9 +610,10 @@ def get_leave_entries(employee, leave_type, from_date, to_date):
|
||||
}, as_dict=1)
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_holidays(employee, from_date, to_date):
|
||||
def get_holidays(employee, from_date, to_date, holiday_list = None):
|
||||
'''get holidays between two dates for the given employee'''
|
||||
holiday_list = get_holiday_list_for_employee(employee)
|
||||
if not holiday_list:
|
||||
holiday_list = get_holiday_list_for_employee(employee)
|
||||
|
||||
holidays = frappe.db.sql("""select count(distinct holiday_date) from `tabHoliday` h1, `tabHoliday List` h2
|
||||
where h1.parent = h2.name and h1.holiday_date between %s and %s
|
||||
|
@ -1,4 +1,5 @@
|
||||
{
|
||||
"actions": [],
|
||||
"creation": "2019-05-09 15:47:39.760406",
|
||||
"doctype": "DocType",
|
||||
"engine": "InnoDB",
|
||||
@ -12,6 +13,7 @@
|
||||
"column_break_7",
|
||||
"from_date",
|
||||
"to_date",
|
||||
"holiday_list",
|
||||
"is_carry_forward",
|
||||
"is_expired",
|
||||
"is_lwp",
|
||||
@ -98,11 +100,18 @@
|
||||
"fieldname": "is_lwp",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Leave Without Pay"
|
||||
},
|
||||
{
|
||||
"fieldname": "holiday_list",
|
||||
"fieldtype": "Link",
|
||||
"label": "Holiday List",
|
||||
"options": "Holiday List"
|
||||
}
|
||||
],
|
||||
"in_create": 1,
|
||||
"is_submittable": 1,
|
||||
"modified": "2019-08-20 14:40:04.130799",
|
||||
"links": [],
|
||||
"modified": "2020-02-27 14:40:10.502605",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "Leave Ledger Entry",
|
||||
|
@ -1,85 +1,186 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import flt
|
||||
from erpnext.hr.doctype.leave_application.leave_application \
|
||||
import get_leave_balance_on, get_leaves_for_period
|
||||
|
||||
from erpnext.hr.report.employee_leave_balance_summary.employee_leave_balance_summary \
|
||||
import get_department_leave_approver_map
|
||||
from frappe import _
|
||||
from erpnext.hr.doctype.leave_application.leave_application import get_leaves_for_period, get_leave_balance_on, get_leave_allocation_records
|
||||
|
||||
def execute(filters=None):
|
||||
leave_types = frappe.db.sql_list("select name from `tabLeave Type` order by name asc")
|
||||
if filters.to_date <= filters.from_date:
|
||||
frappe.throw(_('From date can not be greater than than To date'))
|
||||
|
||||
columns = get_columns(leave_types)
|
||||
data = get_data(filters, leave_types)
|
||||
columns = get_columns()
|
||||
data = get_data(filters)
|
||||
|
||||
return columns, data
|
||||
|
||||
def get_columns(leave_types):
|
||||
columns = [
|
||||
_("Employee") + ":Link.Employee:150",
|
||||
_("Employee Name") + "::200",
|
||||
_("Department") +"::150"
|
||||
]
|
||||
|
||||
for leave_type in leave_types:
|
||||
columns.append(_(leave_type) + " " + _("Opening") + ":Float:160")
|
||||
columns.append(_(leave_type) + " " + _("Taken") + ":Float:160")
|
||||
columns.append(_(leave_type) + " " + _("Balance") + ":Float:160")
|
||||
def get_columns():
|
||||
columns = [{
|
||||
'label': _('Leave Type'),
|
||||
'fieldtype': 'Link',
|
||||
'fieldname': 'leave_type',
|
||||
'width': 200,
|
||||
'options': 'Leave Type'
|
||||
}, {
|
||||
'label': _('Employee'),
|
||||
'fieldtype': 'Link',
|
||||
'fieldname': 'employee',
|
||||
'width': 100,
|
||||
'options': 'Employee'
|
||||
}, {
|
||||
'label': _('Employee Name'),
|
||||
'fieldtype': 'Data',
|
||||
'fieldname': 'employee_name',
|
||||
'width': 100,
|
||||
}, {
|
||||
'label': _('Opening Balance'),
|
||||
'fieldtype': 'float',
|
||||
'fieldname': 'opening_balance',
|
||||
'width': 130,
|
||||
}, {
|
||||
'label': _('Leaves Allocated'),
|
||||
'fieldtype': 'float',
|
||||
'fieldname': 'leaves_allocated',
|
||||
'width': 130,
|
||||
}, {
|
||||
'label': _('Leaves Taken'),
|
||||
'fieldtype': 'float',
|
||||
'fieldname': 'leaves_taken',
|
||||
'width': 130,
|
||||
}, {
|
||||
'label': _('Leaves Expired'),
|
||||
'fieldtype': 'float',
|
||||
'fieldname': 'leaves_expired',
|
||||
'width': 130,
|
||||
}, {
|
||||
'label': _('Closing Balance'),
|
||||
'fieldtype': 'float',
|
||||
'fieldname': 'closing_balance',
|
||||
'width': 130,
|
||||
}]
|
||||
|
||||
return columns
|
||||
|
||||
def get_conditions(filters):
|
||||
conditions = {
|
||||
"status": "Active",
|
||||
"company": filters.company,
|
||||
}
|
||||
if filters.get("department"):
|
||||
conditions.update({"department": filters.get("department")})
|
||||
if filters.get("employee"):
|
||||
conditions.update({"employee": filters.get("employee")})
|
||||
def get_data(filters):
|
||||
leave_types = frappe.db.sql_list("SELECT `name` FROM `tabLeave Type` ORDER BY `name` ASC")
|
||||
|
||||
return conditions
|
||||
|
||||
def get_data(filters, leave_types):
|
||||
user = frappe.session.user
|
||||
conditions = get_conditions(filters)
|
||||
|
||||
if filters.to_date <= filters.from_date:
|
||||
frappe.throw(_("From date can not be greater than than To date"))
|
||||
|
||||
active_employees = frappe.get_all("Employee",
|
||||
filters=conditions,
|
||||
fields=["name", "employee_name", "department", "user_id", "leave_approver"])
|
||||
|
||||
user = frappe.session.user
|
||||
department_approver_map = get_department_leave_approver_map(filters.get('department'))
|
||||
|
||||
active_employees = frappe.get_list('Employee',
|
||||
filters=conditions,
|
||||
fields=['name', 'employee_name', 'department', 'user_id', 'leave_approver'])
|
||||
|
||||
data = []
|
||||
for employee in active_employees:
|
||||
leave_approvers = department_approver_map.get(employee.department_name, [])
|
||||
if employee.leave_approver:
|
||||
leave_approvers.append(employee.leave_approver)
|
||||
|
||||
if (len(leave_approvers) and user in leave_approvers) or (user in ["Administrator", employee.user_id]) or ("HR Manager" in frappe.get_roles(user)):
|
||||
row = [employee.name, employee.employee_name, employee.department]
|
||||
for leave_type in leave_types:
|
||||
if len(active_employees) > 1:
|
||||
data.append({
|
||||
'leave_type': leave_type
|
||||
})
|
||||
else:
|
||||
row = frappe._dict({
|
||||
'leave_type': leave_type
|
||||
})
|
||||
|
||||
for employee in active_employees:
|
||||
|
||||
leave_approvers = department_approver_map.get(employee.department_name, []).append(employee.leave_approver)
|
||||
|
||||
if (leave_approvers and len(leave_approvers) and user in leave_approvers) or (user in ["Administrator", employee.user_id]) \
|
||||
or ("HR Manager" in frappe.get_roles(user)):
|
||||
if len(active_employees) > 1:
|
||||
row = frappe._dict()
|
||||
row.employee = employee.name,
|
||||
row.employee_name = employee.employee_name
|
||||
|
||||
for leave_type in leave_types:
|
||||
# leaves taken
|
||||
leaves_taken = get_leaves_for_period(employee.name, leave_type,
|
||||
filters.from_date, filters.to_date) * -1
|
||||
|
||||
# opening balance
|
||||
new_allocation, expired_leaves = get_allocated_and_expired_leaves(filters.from_date, filters.to_date, employee.name, leave_type)
|
||||
|
||||
|
||||
opening = get_leave_balance_on(employee.name, leave_type, filters.from_date)
|
||||
closing = get_leave_balance_on(employee.name, leave_type, filters.to_date)
|
||||
|
||||
# closing balance
|
||||
closing = max(opening - leaves_taken, 0)
|
||||
row.leaves_allocated = new_allocation
|
||||
row.leaves_expired = expired_leaves - leaves_taken if expired_leaves - leaves_taken > 0 else 0
|
||||
row.opening_balance = opening
|
||||
row.leaves_taken = leaves_taken
|
||||
row.closing_balance = closing
|
||||
row.indent = 1
|
||||
data.append(row)
|
||||
new_leaves_allocated = 0
|
||||
|
||||
row += [opening, leaves_taken, closing]
|
||||
|
||||
data.append(row)
|
||||
return data
|
||||
|
||||
return data
|
||||
def get_conditions(filters):
|
||||
conditions={
|
||||
'status': 'Active',
|
||||
}
|
||||
if filters.get('employee'):
|
||||
conditions['name'] = filters.get('employee')
|
||||
|
||||
if filters.get('employee'):
|
||||
conditions['name'] = filters.get('employee')
|
||||
|
||||
return conditions
|
||||
|
||||
def get_department_leave_approver_map(department=None):
|
||||
conditions=''
|
||||
if department:
|
||||
conditions="and (department_name = '%(department)s' or parent_department = '%(department)s')"%{'department': department}
|
||||
|
||||
# get current department and all its child
|
||||
department_list = frappe.db.sql_list(""" SELECT name FROM `tabDepartment` WHERE disabled=0 {0}""".format(conditions)) #nosec
|
||||
|
||||
# retrieve approvers list from current department and from its subsequent child departments
|
||||
approver_list = frappe.get_all('Department Approver', filters={
|
||||
'parentfield': 'leave_approvers',
|
||||
'parent': ('in', department_list)
|
||||
}, fields=['parent', 'approver'], as_list=1)
|
||||
|
||||
approvers = {}
|
||||
|
||||
for k, v in approver_list:
|
||||
approvers.setdefault(k, []).append(v)
|
||||
|
||||
return approvers
|
||||
|
||||
def get_allocated_and_expired_leaves(from_date, to_date, employee, leave_type):
|
||||
|
||||
from frappe.utils import getdate
|
||||
|
||||
new_allocation = 0
|
||||
expired_leaves = 0
|
||||
|
||||
records= frappe.db.sql("""
|
||||
SELECT
|
||||
employee, leave_type, from_date, to_date, leaves, transaction_name,
|
||||
is_carry_forward, is_expired
|
||||
FROM `tabLeave Ledger Entry`
|
||||
WHERE employee=%(employee)s AND leave_type=%(leave_type)s
|
||||
AND docstatus=1 AND leaves>0
|
||||
AND (from_date between %(from_date)s AND %(to_date)s
|
||||
OR to_date between %(from_date)s AND %(to_date)s
|
||||
OR (from_date < %(from_date)s AND to_date > %(to_date)s))
|
||||
""", {
|
||||
"from_date": from_date,
|
||||
"to_date": to_date,
|
||||
"employee": employee,
|
||||
"leave_type": leave_type
|
||||
}, as_dict=1)
|
||||
|
||||
for record in records:
|
||||
if record.to_date <= getdate(to_date):
|
||||
expired_leaves += record.leaves
|
||||
|
||||
if record.from_date >= getdate(from_date):
|
||||
new_allocation += record.leaves
|
||||
|
||||
return new_allocation, expired_leaves
|
||||
|
@ -5,18 +5,11 @@
|
||||
frappe.query_reports['Employee Leave Balance Summary'] = {
|
||||
filters: [
|
||||
{
|
||||
fieldname:'from_date',
|
||||
label: __('From Date'),
|
||||
fieldname:'date',
|
||||
label: __('Date'),
|
||||
fieldtype: 'Date',
|
||||
reqd: 1,
|
||||
default: frappe.defaults.get_default('year_start_date')
|
||||
},
|
||||
{
|
||||
fieldname:'to_date',
|
||||
label: __('To Date'),
|
||||
fieldtype: 'Date',
|
||||
reqd: 1,
|
||||
default: frappe.defaults.get_default('year_end_date')
|
||||
default: frappe.datetime.now_date()
|
||||
},
|
||||
{
|
||||
fieldname:'company',
|
||||
|
@ -1,130 +1,75 @@
|
||||
# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe.utils import flt
|
||||
from frappe import _
|
||||
from erpnext.hr.doctype.leave_application.leave_application import get_leaves_for_period, get_leave_balance_on
|
||||
from frappe.utils import flt
|
||||
from erpnext.hr.doctype.leave_application.leave_application \
|
||||
import get_leave_balance_on, get_leaves_for_period
|
||||
|
||||
from erpnext.hr.report.employee_leave_balance.employee_leave_balance \
|
||||
import get_department_leave_approver_map
|
||||
|
||||
def execute(filters=None):
|
||||
if filters.to_date <= filters.from_date:
|
||||
frappe.throw(_('From date can not be greater than than To date'))
|
||||
leave_types = frappe.db.sql_list("select name from `tabLeave Type` order by name asc")
|
||||
|
||||
columns = get_columns()
|
||||
data = get_data(filters)
|
||||
columns = get_columns(leave_types)
|
||||
data = get_data(filters, leave_types)
|
||||
|
||||
return columns, data
|
||||
|
||||
def get_columns():
|
||||
columns = [{
|
||||
'label': _('Leave Type'),
|
||||
'fieldtype': 'Link',
|
||||
'fieldname': 'leave_type',
|
||||
'width': 300,
|
||||
'options': 'Leave Type'
|
||||
}, {
|
||||
'label': _('Employee'),
|
||||
'fieldtype': 'Link',
|
||||
'fieldname': 'employee',
|
||||
'width': 100,
|
||||
'options': 'Employee'
|
||||
}, {
|
||||
'label': _('Employee Name'),
|
||||
'fieldtype': 'Data',
|
||||
'fieldname': 'employee_name',
|
||||
'width': 100,
|
||||
}, {
|
||||
'label': _('Opening Balance'),
|
||||
'fieldtype': 'float',
|
||||
'fieldname': 'opening_balance',
|
||||
'width': 160,
|
||||
}, {
|
||||
'label': _('Leaves Taken'),
|
||||
'fieldtype': 'float',
|
||||
'fieldname': 'leaves_taken',
|
||||
'width': 160,
|
||||
}, {
|
||||
'label': _('Closing Balance'),
|
||||
'fieldtype': 'float',
|
||||
'fieldname': 'closing_balance',
|
||||
'width': 160,
|
||||
}]
|
||||
def get_columns(leave_types):
|
||||
columns = [
|
||||
_("Employee") + ":Link.Employee:150",
|
||||
_("Employee Name") + "::200",
|
||||
_("Department") +"::150"
|
||||
]
|
||||
|
||||
for leave_type in leave_types:
|
||||
columns.append(_(leave_type) + ":Float:160")
|
||||
|
||||
return columns
|
||||
|
||||
def get_data(filters):
|
||||
leave_types = frappe.db.sql_list("SELECT `name` FROM `tabLeave Type` ORDER BY `name` ASC")
|
||||
|
||||
conditions = get_conditions(filters)
|
||||
|
||||
user = frappe.session.user
|
||||
department_approver_map = get_department_leave_approver_map(filters.get('department'))
|
||||
|
||||
active_employees = frappe.get_list('Employee',
|
||||
filters=conditions,
|
||||
fields=['name', 'employee_name', 'department', 'user_id', 'leave_approver'])
|
||||
|
||||
data = []
|
||||
|
||||
for leave_type in leave_types:
|
||||
data.append({
|
||||
'leave_type': leave_type
|
||||
})
|
||||
for employee in active_employees:
|
||||
|
||||
leave_approvers = department_approver_map.get(employee.department_name, []).append(employee.leave_approver)
|
||||
|
||||
if (leave_approvers and len(leave_approvers) and user in leave_approvers) or (user in ["Administrator", employee.user_id]) \
|
||||
or ("HR Manager" in frappe.get_roles(user)):
|
||||
row = frappe._dict({
|
||||
'employee': employee.name,
|
||||
'employee_name': employee.employee_name
|
||||
})
|
||||
|
||||
leaves_taken = get_leaves_for_period(employee.name, leave_type,
|
||||
filters.from_date, filters.to_date) * -1
|
||||
|
||||
opening = get_leave_balance_on(employee.name, leave_type, filters.from_date)
|
||||
closing = get_leave_balance_on(employee.name, leave_type, filters.to_date)
|
||||
|
||||
row.opening_balance = opening
|
||||
row.leaves_taken = leaves_taken
|
||||
row.closing_balance = closing
|
||||
row.indent = 1
|
||||
data.append(row)
|
||||
|
||||
return data
|
||||
|
||||
def get_conditions(filters):
|
||||
conditions={
|
||||
'status': 'Active',
|
||||
conditions = {
|
||||
"status": "Active",
|
||||
"company": filters.company,
|
||||
}
|
||||
if filters.get('employee'):
|
||||
conditions['name'] = filters.get('employee')
|
||||
|
||||
if filters.get('employee'):
|
||||
conditions['name'] = filters.get('employee')
|
||||
if filters.get("department"):
|
||||
conditions.update({"department": filters.get("department")})
|
||||
if filters.get("employee"):
|
||||
conditions.update({"employee": filters.get("employee")})
|
||||
|
||||
return conditions
|
||||
|
||||
def get_department_leave_approver_map(department=None):
|
||||
conditions=''
|
||||
if department:
|
||||
conditions="and (department_name = '%(department)s' or parent_department = '%(department)s')"%{'department': department}
|
||||
def get_data(filters, leave_types):
|
||||
user = frappe.session.user
|
||||
conditions = get_conditions(filters)
|
||||
|
||||
# get current department and all its child
|
||||
department_list = frappe.db.sql_list(""" SELECT name FROM `tabDepartment` WHERE disabled=0 {0}""".format(conditions)) #nosec
|
||||
active_employees = frappe.get_all("Employee",
|
||||
filters=conditions,
|
||||
fields=["name", "employee_name", "department", "user_id", "leave_approver"])
|
||||
|
||||
# retrieve approvers list from current department and from its subsequent child departments
|
||||
approver_list = frappe.get_all('Department Approver', filters={
|
||||
'parentfield': 'leave_approvers',
|
||||
'parent': ('in', department_list)
|
||||
}, fields=['parent', 'approver'], as_list=1)
|
||||
department_approver_map = get_department_leave_approver_map(filters.get('department'))
|
||||
|
||||
approvers = {}
|
||||
data = []
|
||||
for employee in active_employees:
|
||||
leave_approvers = department_approver_map.get(employee.department_name, [])
|
||||
if employee.leave_approver:
|
||||
leave_approvers.append(employee.leave_approver)
|
||||
|
||||
for k, v in approver_list:
|
||||
approvers.setdefault(k, []).append(v)
|
||||
if (len(leave_approvers) and user in leave_approvers) or (user in ["Administrator", employee.user_id]) or ("HR Manager" in frappe.get_roles(user)):
|
||||
row = [employee.name, employee.employee_name, employee.department]
|
||||
|
||||
return approvers
|
||||
for leave_type in leave_types:
|
||||
|
||||
# opening balance
|
||||
opening = get_leave_balance_on(employee.name, leave_type, filters.date)
|
||||
|
||||
|
||||
row += [opening]
|
||||
|
||||
data.append(row)
|
||||
|
||||
return data
|
Loading…
x
Reference in New Issue
Block a user