diff --git a/erpnext/hr/doctype/employee_benefit_application/employee_benefit_application.js b/erpnext/hr/doctype/employee_benefit_application/employee_benefit_application.js index b63c76f4b3b..2a8ce915759 100644 --- a/erpnext/hr/doctype/employee_benefit_application/employee_benefit_application.js +++ b/erpnext/hr/doctype/employee_benefit_application/employee_benefit_application.js @@ -41,6 +41,9 @@ frappe.ui.form.on('Employee Benefit Application', { }; get_max_benefits(frm, method, args); } + }, + max_benefits: function(frm) { + calculate_all(frm.doc); } }); diff --git a/erpnext/hr/doctype/employee_benefit_application/employee_benefit_application.py b/erpnext/hr/doctype/employee_benefit_application/employee_benefit_application.py index ed54fb828fa..5b2a5910b52 100644 --- a/erpnext/hr/doctype/employee_benefit_application/employee_benefit_application.py +++ b/erpnext/hr/doctype/employee_benefit_application/employee_benefit_application.py @@ -5,11 +5,11 @@ from __future__ import unicode_literals import frappe from frappe import _ -from frappe.utils import date_diff, getdate, rounded +from frappe.utils import date_diff, getdate, rounded, add_days, cstr, cint from frappe.model.document import Document from erpnext.hr.doctype.payroll_period.payroll_period import get_payroll_period_days from erpnext.hr.doctype.salary_structure_assignment.salary_structure_assignment import get_assigned_salary_structure -from erpnext.hr.utils import get_sal_slip_total_benefit_given +from erpnext.hr.utils import get_sal_slip_total_benefit_given, get_holidays_for_employee class EmployeeBenefitApplication(Document): def validate(self): @@ -97,13 +97,60 @@ def get_max_benefits(employee, on_date): def get_max_benefits_remaining(employee, on_date, payroll_period): max_benefits = get_max_benefits(employee, on_date) if max_benefits and max_benefits > 0: + have_depends_on_lwp = False + per_day_amount_total = 0 + payroll_period_days = get_payroll_period_days(on_date, on_date, employee) payroll_period_obj = frappe.get_doc("Payroll Period", payroll_period) + # Get all salary slip flexi amount in the payroll period prev_sal_slip_flexi_total = get_sal_slip_total_benefit_given(employee, payroll_period_obj) + + # Check salary structure hold depends_on_lwp component + # If yes then find the amount per day of each component and find the sum + sal_struct_name = get_assigned_salary_structure(employee, on_date) + if sal_struct_name: + sal_struct = frappe.get_doc("Salary Structure", sal_struct_name) + for sal_struct_row in sal_struct.get("earnings"): + salary_component = frappe.get_doc("Salary Component", sal_struct_row.salary_component) + if salary_component.depends_on_lwp == 1 and salary_component.is_pro_rata_applicable == 1: + have_depends_on_lwp = True + benefit_amount = get_benefit_pro_rata_ratio_amount(sal_struct, salary_component.max_benefit_amount) + amount_per_day = benefit_amount / payroll_period_days + per_day_amount_total += amount_per_day + + # Then the sum multiply with the no of lwp in that period + # Include that amount to the prev_sal_slip_flexi_total to get the actual + if have_depends_on_lwp and per_day_amount_total > 0: + holidays = get_holidays_for_employee(employee, payroll_period_obj.start_date, on_date) + working_days = date_diff(on_date, payroll_period_obj.start_date) + 1 + leave_days = calculate_lwp(employee, payroll_period_obj.start_date, holidays, working_days) + leave_days_amount = leave_days * per_day_amount_total + prev_sal_slip_flexi_total += leave_days_amount + return max_benefits - prev_sal_slip_flexi_total return max_benefits -def get_benefit_component_amount(employee, start_date, end_date, struct_row, sal_struct): +def calculate_lwp(employee, start_date, holidays, working_days): + lwp = 0 + holidays = "','".join(holidays) + for d in range(working_days): + dt = add_days(cstr(getdate(start_date)), d) + leave = frappe.db.sql(""" + select t1.name, t1.half_day + from `tabLeave Application` t1, `tabLeave Type` t2 + where t2.name = t1.leave_type + and t2.is_lwp = 1 + and t1.docstatus = 1 + and t1.employee = %(employee)s + and CASE WHEN t2.include_holiday != 1 THEN %(dt)s not in ('{0}') and %(dt)s between from_date and to_date + WHEN t2.include_holiday THEN %(dt)s between from_date and to_date + END + """.format(holidays), {"employee": employee, "dt": dt}) + if leave: + lwp = cint(leave[0][1]) and (lwp + 0.5) or (lwp + 1) + return lwp + +def get_benefit_component_amount(employee, start_date, end_date, struct_row, sal_struct, payment_days, working_days): # Considering there is only one application for an year benefit_application_name = frappe.db.sql(""" select name from `tabEmployee Benefit Application` @@ -116,23 +163,29 @@ def get_benefit_component_amount(employee, start_date, end_date, struct_row, sal 'end_date': end_date }) - payroll_period_days = get_payroll_period_days(start_date, end_date, frappe.db.get_value("Employee", employee, "company")) + payroll_period_days = get_payroll_period_days(start_date, end_date, employee) if payroll_period_days: - # If there is application for benefit claim then fetch the amount from it. + depends_on_lwp = frappe.db.get_value("Salary Component", struct_row.salary_component, "depends_on_lwp") + if depends_on_lwp != 1: + payment_days = working_days + + # If there is application for benefit then fetch the amount from the application. + # else Split the max benefits to the pro-rata components with the ratio of thier max_benefit_amount if benefit_application_name: benefit_application = frappe.get_doc("Employee Benefit Application", benefit_application_name[0][0]) - return get_benefit_amount(benefit_application, start_date, end_date, struct_row, payroll_period_days) + return get_benefit_amount(benefit_application, struct_row, payroll_period_days, payment_days) # TODO: Check if there is benefit claim for employee then pro-rata devid the rest of amount (Late Benefit Application) - # else Split the max benefits to the pro-rata components with the ratio of thier max_benefit_amount else: component_max = frappe.db.get_value("Salary Component", struct_row.salary_component, "max_benefit_amount") if component_max > 0: - return get_benefit_pro_rata_ratio_amount(sal_struct, component_max, payroll_period_days, start_date, end_date) + benefit_amount = get_benefit_pro_rata_ratio_amount(sal_struct, component_max) + return get_amount(payroll_period_days, benefit_amount, payment_days) return False -def get_benefit_pro_rata_ratio_amount(sal_struct, component_max, payroll_period_days, start_date, end_date): +def get_benefit_pro_rata_ratio_amount(sal_struct, component_max): total_pro_rata_max = 0 + benefit_amount = 0 for sal_struct_row in sal_struct.get("earnings"): is_pro_rata_applicable, max_benefit_amount = frappe.db.get_value("Salary Component", sal_struct_row.salary_component, ["is_pro_rata_applicable", "max_benefit_amount"]) if sal_struct_row.is_flexible_benefit == 1 and is_pro_rata_applicable == 1: @@ -141,20 +194,18 @@ def get_benefit_pro_rata_ratio_amount(sal_struct, component_max, payroll_period_ benefit_amount = component_max * sal_struct.max_benefits / total_pro_rata_max if benefit_amount > component_max: benefit_amount = component_max - return get_amount(payroll_period_days, start_date, end_date, benefit_amount) - return False + return benefit_amount -def get_benefit_amount(application, start_date, end_date, struct_row, payroll_period_days): +def get_benefit_amount(application, struct_row, payroll_period_days, payment_days): amount = 0 for employee_benefit in application.employee_benefits: if employee_benefit.earning_component == struct_row.salary_component: - amount += get_amount(payroll_period_days, start_date, end_date, employee_benefit.amount) + amount += get_amount(payroll_period_days, employee_benefit.amount, payment_days) return amount if amount > 0 else False -def get_amount(payroll_period_days, start_date, end_date, amount): - salary_slip_days = date_diff(getdate(end_date), getdate(start_date)) + 1 +def get_amount(payroll_period_days, amount, payment_days): amount_per_day = amount / payroll_period_days - total_amount = amount_per_day * salary_slip_days + total_amount = amount_per_day * payment_days return total_amount def get_earning_components(doctype, txt, searchfield, start, page_len, filters): diff --git a/erpnext/hr/doctype/payroll_period/payroll_period.py b/erpnext/hr/doctype/payroll_period/payroll_period.py index 66d6a457d6f..e570f71ed40 100644 --- a/erpnext/hr/doctype/payroll_period/payroll_period.py +++ b/erpnext/hr/doctype/payroll_period/payroll_period.py @@ -5,8 +5,9 @@ from __future__ import unicode_literals import frappe from frappe import _ -from frappe.utils import date_diff, getdate, formatdate +from frappe.utils import date_diff, getdate, formatdate, cint from frappe.model.document import Document +from erpnext.hr.utils import get_holidays_for_employee class PayrollPeriod(Document): def validate(self): @@ -44,7 +45,8 @@ class PayrollPeriod(Document): + _(") for {0}").format(self.company) frappe.throw(msg) -def get_payroll_period_days(start_date, end_date, company): +def get_payroll_period_days(start_date, end_date, employee): + company = frappe.db.get_value("Employee", employee, "company") payroll_period_dates = frappe.db.sql(""" select start_date, end_date from `tabPayroll Period` where company=%(company)s @@ -59,4 +61,9 @@ def get_payroll_period_days(start_date, end_date, company): }) if len(payroll_period_dates) > 0: - return date_diff(getdate(payroll_period_dates[0][1]), getdate(payroll_period_dates[0][0])) + 1 + working_days = date_diff(getdate(payroll_period_dates[0][1]), getdate(payroll_period_dates[0][0])) + 1 + if not cint(frappe.db.get_value("HR Settings", None, "include_holidays_in_total_working_days")): + holidays = get_holidays_for_employee(employee, getdate(payroll_period_dates[0][0]), getdate(payroll_period_dates[0][1])) + working_days -= len(holidays) + return working_days + return False diff --git a/erpnext/hr/doctype/salary_slip/salary_slip.py b/erpnext/hr/doctype/salary_slip/salary_slip.py index 80e2ac4b8ab..297a1f54981 100644 --- a/erpnext/hr/doctype/salary_slip/salary_slip.py +++ b/erpnext/hr/doctype/salary_slip/salary_slip.py @@ -82,7 +82,7 @@ class SalarySlip(TransactionBase): def add_employee_flexi_benefits(self, struct_row): if frappe.db.get_value("Salary Component", struct_row.salary_component, "is_pro_rata_applicable") == 1: - benefit_component_amount = get_benefit_component_amount(self.employee, self.start_date, self.end_date, struct_row, self._salary_structure_doc) + benefit_component_amount = get_benefit_component_amount(self.employee, self.start_date, self.end_date, struct_row, self._salary_structure_doc, self.payment_days, self.total_working_days) if benefit_component_amount: self.update_component_row(struct_row, benefit_component_amount, "earnings") else: diff --git a/erpnext/hr/utils.py b/erpnext/hr/utils.py index f4733176acf..9713f40d0de 100644 --- a/erpnext/hr/utils.py +++ b/erpnext/hr/utils.py @@ -8,6 +8,7 @@ from frappe.utils import formatdate, format_datetime, getdate, get_datetime, now from frappe.model.document import Document from frappe.desk.form import assign_to from erpnext.hr.doctype.salary_structure.salary_structure import make_salary_slip +from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee class EmployeeBoardingController(Document): ''' @@ -378,3 +379,19 @@ def get_sal_slip_total_benefit_given(employee, payroll_period, component=False): if sum_of_given_benefit and sum_of_given_benefit[0].total_amount > 0: total_given_benefit_amount = sum_of_given_benefit[0].total_amount return total_given_benefit_amount + +def get_holidays_for_employee(employee, start_date, end_date): + holiday_list = get_holiday_list_for_employee(employee) + holidays = frappe.db.sql_list('''select holiday_date from `tabHoliday` + where + parent=%(holiday_list)s + and holiday_date >= %(start_date)s + and holiday_date <= %(end_date)s''', { + "holiday_list": holiday_list, + "start_date": start_date, + "end_date": end_date + }) + + holidays = [cstr(i) for i in holidays] + + return holidays