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):
"""Returns document properties of previous allocation"""
return frappe.db.get_value(
"Leave Allocation",
filters={
"to_date": ("<", from_date),
"leave_type": leave_type,
"employee": employee,
"docstatus": 1,
},
order_by="to_date DESC",
fieldname=["name", "from_date", "to_date", "employee", "leave_type"],
as_dict=1,
)
Allocation = frappe.qb.DocType("Leave Allocation")
allocations = (
frappe.qb.from_(Allocation)
.select(
Allocation.name,
Allocation.from_date,
Allocation.to_date,
Allocation.employee,
Allocation.leave_type,
)
.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(

View File

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

View File

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

View File

@@ -225,3 +225,7 @@ def expire_carried_forward_allocation(allocation):
to_date=allocation.to_date,
)
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,
"apply_user_permissions": 1,
"creation": "2013-02-22 15:29:34",
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"idx": 3,
"is_standard": "Yes",
"modified": "2017-02-24 20:18:04.317397",
"modified_by": "Administrator",
"module": "HR",
"name": "Employee Leave Balance",
"owner": "Administrator",
"ref_doctype": "Employee",
"report_name": "Employee Leave Balance",
"report_type": "Script Report",
"add_total_row": 0,
"columns": [],
"creation": "2013-02-22 15:29:34",
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"filters": [],
"idx": 3,
"is_standard": "Yes",
"letterhead": null,
"modified": "2023-11-17 13:28:40.669200",
"modified_by": "Administrator",
"module": "HR",
"name": "Employee Leave Balance",
"owner": "Administrator",
"prepared_report": 0,
"ref_doctype": "Employee",
"report_name": "Employee Leave Balance",
"report_type": "Script Report",
"roles": [
{
"role": "HR User"
},
},
{
"role": "HR Manager"
},
{
"role": "Employee"
}
]
}

View File

@@ -85,19 +85,10 @@ def get_columns() -> List[Dict]:
def get_data(filters: Filters) -> List:
leave_types = frappe.db.get_list("Leave Type", pluck="name", order_by="name")
conditions = get_conditions(filters)
leave_types = get_leave_types()
active_employees = get_employees(filters)
user = frappe.session.user
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))
precision = cint(frappe.db.get_single_value("System Settings", "float_precision"))
consolidate_leave_types = len(active_employees) > 1 and filters.consolidate_leave_types
row = None
@@ -110,10 +101,6 @@ def get_data(filters: Filters) -> List:
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 consolidate_leave_types:
row = frappe._dict()
else:
@@ -144,6 +131,35 @@ def get_data(filters: Filters) -> List:
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(
employee: str, leave_type: str, filters: Filters, carry_forwarded_leaves: float
) -> float:
@@ -168,48 +184,6 @@ def get_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(
from_date: str, to_date: str, employee: str, leave_type: str
) -> Tuple[float, float, float]:
@@ -244,7 +218,7 @@ def get_leave_ledger_entries(
from_date: str, to_date: str, employee: str, leave_type: str
) -> List[Dict]:
ledger = frappe.qb.DocType("Leave Ledger Entry")
records = (
return (
frappe.qb.from_(ledger)
.select(
ledger.employee,
@@ -270,8 +244,6 @@ def get_leave_ledger_entries(
)
).run(as_dict=True)
return records
def get_chart_data(data: List, filters: Filters) -> Dict:
labels = []

View File

@@ -6,9 +6,6 @@ import frappe
from frappe import _
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):
@@ -54,17 +51,11 @@ def get_data(filters, leave_types):
active_employees = frappe.get_all(
"Employee",
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 = []
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]
available_leave = get_leave_details(employee.name, filters.date)
for leave_type in leave_types: