Merge pull request #38158 from ruchamahabal/perf-leave-balance-report-v13

perf: faster Employee Leave Balance report
This commit is contained in:
Rucha Mahabal
2023-11-17 22:57:49 +05:30
committed by GitHub
7 changed files with 106 additions and 118 deletions

View File

@@ -296,18 +296,27 @@ class LeaveAllocation(Document):
def get_previous_allocation(from_date, leave_type, employee): def get_previous_allocation(from_date, leave_type, employee):
"""Returns document properties of previous allocation""" """Returns document properties of previous allocation"""
return frappe.db.get_value( Allocation = frappe.qb.DocType("Leave Allocation")
"Leave Allocation", allocations = (
filters={ frappe.qb.from_(Allocation)
"to_date": ("<", from_date), .select(
"leave_type": leave_type, Allocation.name,
"employee": employee, Allocation.from_date,
"docstatus": 1, Allocation.to_date,
}, Allocation.employee,
order_by="to_date DESC", Allocation.leave_type,
fieldname=["name", "from_date", "to_date", "employee", "leave_type"], )
as_dict=1, .where(
) (Allocation.employee == employee)
& (Allocation.leave_type == leave_type)
& (Allocation.to_date < from_date)
& (Allocation.docstatus == 1)
)
.orderby(Allocation.to_date, order=frappe.qb.desc)
.limit(1)
).run(as_dict=True)
return allocations[0] if allocations else None
def get_leave_allocation_for_period( def get_leave_allocation_for_period(

View File

@@ -706,19 +706,22 @@ def get_allocation_expiry_for_cf_leaves(
employee: str, leave_type: str, to_date: str, from_date: str employee: str, leave_type: str, to_date: str, from_date: str
) -> str: ) -> str:
"""Returns expiry of carry forward allocation in leave ledger entry""" """Returns expiry of carry forward allocation in leave ledger entry"""
expiry = frappe.get_all( Ledger = frappe.qb.DocType("Leave Ledger Entry")
"Leave Ledger Entry", expiry = (
filters={ frappe.qb.from_(Ledger)
"employee": employee, .select(Ledger.to_date)
"leave_type": leave_type, .where(
"is_carry_forward": 1, (Ledger.employee == employee)
"transaction_type": "Leave Allocation", & (Ledger.leave_type == leave_type)
"to_date": ["between", (from_date, to_date)], & (Ledger.is_carry_forward == 1)
"docstatus": 1, & (Ledger.transaction_type == "Leave Allocation")
}, & (Ledger.to_date.between(from_date, to_date))
fields=["to_date"], & (Ledger.docstatus == 1)
) )
return expiry[0]["to_date"] if expiry else "" .limit(1)
).run()
return expiry[0][0] if expiry else ""
@frappe.whitelist() @frappe.whitelist()
@@ -1017,7 +1020,7 @@ def get_leaves_for_period(
if leave_entry.leaves % 1: if leave_entry.leaves % 1:
half_day = 1 half_day = 1
half_day_date = frappe.db.get_value( half_day_date = frappe.db.get_value(
"Leave Application", {"name": leave_entry.transaction_name}, ["half_day_date"] "Leave Application", leave_entry.transaction_name, "half_day_date"
) )
leave_days += ( leave_days += (

View File

@@ -27,7 +27,8 @@
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 1, "in_standard_filter": 1,
"label": "Employee", "label": "Employee",
"options": "Employee" "options": "Employee",
"search_index": 1
}, },
{ {
"fetch_from": "employee.employee_name", "fetch_from": "employee.employee_name",
@@ -57,13 +58,15 @@
"fieldtype": "Link", "fieldtype": "Link",
"in_standard_filter": 1, "in_standard_filter": 1,
"label": "Transaction Type", "label": "Transaction Type",
"options": "DocType" "options": "DocType",
"search_index": 1
}, },
{ {
"fieldname": "transaction_name", "fieldname": "transaction_name",
"fieldtype": "Dynamic Link", "fieldtype": "Dynamic Link",
"label": "Transaction Name", "label": "Transaction Name",
"options": "transaction_type" "options": "transaction_type",
"search_index": 1
}, },
{ {
"fieldname": "leaves", "fieldname": "leaves",
@@ -123,7 +126,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2021-01-04 18:47:45.146652", "modified": "2023-11-17 12:36:36.963697",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Leave Ledger Entry", "name": "Leave Ledger Entry",

View File

@@ -225,3 +225,7 @@ def expire_carried_forward_allocation(allocation):
to_date=allocation.to_date, to_date=allocation.to_date,
) )
create_leave_ledger_entry(allocation, args) create_leave_ledger_entry(allocation, args)
def on_doctype_update():
frappe.db.add_index("Leave Ledger Entry", ["transaction_type", "transaction_name"])

View File

@@ -1,26 +1,32 @@
{ {
"add_total_row": 0, "add_total_row": 0,
"apply_user_permissions": 1, "columns": [],
"creation": "2013-02-22 15:29:34", "creation": "2013-02-22 15:29:34",
"disabled": 0, "disabled": 0,
"docstatus": 0, "docstatus": 0,
"doctype": "Report", "doctype": "Report",
"idx": 3, "filters": [],
"is_standard": "Yes", "idx": 3,
"modified": "2017-02-24 20:18:04.317397", "is_standard": "Yes",
"modified_by": "Administrator", "letterhead": null,
"module": "HR", "modified": "2023-11-17 13:28:40.669200",
"name": "Employee Leave Balance", "modified_by": "Administrator",
"owner": "Administrator", "module": "HR",
"ref_doctype": "Employee", "name": "Employee Leave Balance",
"report_name": "Employee Leave Balance", "owner": "Administrator",
"report_type": "Script Report", "prepared_report": 0,
"ref_doctype": "Employee",
"report_name": "Employee Leave Balance",
"report_type": "Script Report",
"roles": [ "roles": [
{ {
"role": "HR User" "role": "HR User"
}, },
{ {
"role": "HR Manager" "role": "HR Manager"
},
{
"role": "Employee"
} }
] ]
} }

View File

@@ -85,19 +85,10 @@ def get_columns() -> List[Dict]:
def get_data(filters: Filters) -> List: def get_data(filters: Filters) -> List:
leave_types = frappe.db.get_list("Leave Type", pluck="name", order_by="name") leave_types = get_leave_types()
conditions = get_conditions(filters) active_employees = get_employees(filters)
user = frappe.session.user precision = cint(frappe.db.get_single_value("System Settings", "float_precision"))
department_approver_map = get_department_leave_approver_map(filters.department)
active_employees = frappe.get_list(
"Employee",
filters=conditions,
fields=["name", "employee_name", "department", "user_id", "leave_approver"],
)
precision = cint(frappe.db.get_single_value("System Settings", "float_precision", cache=True))
consolidate_leave_types = len(active_employees) > 1 and filters.consolidate_leave_types consolidate_leave_types = len(active_employees) > 1 and filters.consolidate_leave_types
row = None row = None
@@ -110,10 +101,6 @@ def get_data(filters: Filters) -> List:
row = frappe._dict({"leave_type": leave_type}) row = frappe._dict({"leave_type": leave_type})
for employee in active_employees: for employee in active_employees:
leave_approvers = department_approver_map.get(employee.department_name, []).append(
employee.leave_approver
)
if consolidate_leave_types: if consolidate_leave_types:
row = frappe._dict() row = frappe._dict()
else: else:
@@ -144,6 +131,35 @@ def get_data(filters: Filters) -> List:
return data return data
def get_leave_types() -> List[str]:
LeaveType = frappe.qb.DocType("Leave Type")
leave_types = (frappe.qb.from_(LeaveType).select(LeaveType.name).orderby(LeaveType.name)).run(
as_dict=True
)
return [leave_type.name for leave_type in leave_types]
def get_employees(filters: Filters) -> List[Dict]:
Employee = frappe.qb.DocType("Employee")
query = frappe.qb.from_(Employee).select(
Employee.name,
Employee.employee_name,
Employee.department,
)
for field in ["company", "department"]:
if filters.get(field):
query = query.where((getattr(Employee, field) == filters.get(field)))
if filters.get("employee"):
query = query.where(Employee.name == filters.get("employee"))
if filters.get("employee_status"):
query = query.where(Employee.status == filters.get("employee_status"))
return query.run(as_dict=True)
def get_opening_balance( def get_opening_balance(
employee: str, leave_type: str, filters: Filters, carry_forwarded_leaves: float employee: str, leave_type: str, filters: Filters, carry_forwarded_leaves: float
) -> float: ) -> float:
@@ -168,48 +184,6 @@ def get_opening_balance(
return opening_balance return opening_balance
def get_conditions(filters: Filters) -> Dict:
conditions = {}
if filters.employee:
conditions["name"] = filters.employee
if filters.company:
conditions["company"] = filters.company
if filters.department:
conditions["department"] = filters.department
if filters.employee_status:
conditions["status"] = filters.employee_status
return conditions
def get_department_leave_approver_map(department: Optional[str] = None):
# get current department and all its child
department_list = frappe.get_list(
"Department",
filters={"disabled": 0},
or_filters={"name": department, "parent_department": department},
pluck="name",
)
# 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=True,
)
approvers = {}
for k, v in approver_list:
approvers.setdefault(k, []).append(v)
return approvers
def get_allocated_and_expired_leaves( def get_allocated_and_expired_leaves(
from_date: str, to_date: str, employee: str, leave_type: str from_date: str, to_date: str, employee: str, leave_type: str
) -> Tuple[float, float, float]: ) -> Tuple[float, float, float]:
@@ -244,7 +218,7 @@ def get_leave_ledger_entries(
from_date: str, to_date: str, employee: str, leave_type: str from_date: str, to_date: str, employee: str, leave_type: str
) -> List[Dict]: ) -> List[Dict]:
ledger = frappe.qb.DocType("Leave Ledger Entry") ledger = frappe.qb.DocType("Leave Ledger Entry")
records = ( return (
frappe.qb.from_(ledger) frappe.qb.from_(ledger)
.select( .select(
ledger.employee, ledger.employee,
@@ -270,8 +244,6 @@ def get_leave_ledger_entries(
) )
).run(as_dict=True) ).run(as_dict=True)
return records
def get_chart_data(data: List, filters: Filters) -> Dict: def get_chart_data(data: List, filters: Filters) -> Dict:
labels = [] labels = []

View File

@@ -6,9 +6,6 @@ import frappe
from frappe import _ from frappe import _
from erpnext.hr.doctype.leave_application.leave_application import get_leave_details from erpnext.hr.doctype.leave_application.leave_application import get_leave_details
from erpnext.hr.report.employee_leave_balance.employee_leave_balance import (
get_department_leave_approver_map,
)
def execute(filters=None): def execute(filters=None):
@@ -54,17 +51,11 @@ def get_data(filters, leave_types):
active_employees = frappe.get_all( active_employees = frappe.get_all(
"Employee", "Employee",
filters=conditions, filters=conditions,
fields=["name", "employee_name", "department", "user_id", "leave_approver"], fields=["name", "employee_name", "department", "user_id"],
) )
department_approver_map = get_department_leave_approver_map(filters.get("department"))
data = [] data = []
for employee in active_employees: for employee in active_employees:
leave_approvers = department_approver_map.get(employee.department_name, [])
if employee.leave_approver:
leave_approvers.append(employee.leave_approver)
row = [employee.name, employee.employee_name, employee.department] row = [employee.name, employee.employee_name, employee.department]
available_leave = get_leave_details(employee.name, filters.date) available_leave = get_leave_details(employee.name, filters.date)
for leave_type in leave_types: for leave_type in leave_types: