From 8d66f1e45da049e86757fe4d54c0f7bfadae5275 Mon Sep 17 00:00:00 2001 From: Jamsheer Date: Tue, 12 Jun 2018 11:30:59 +0530 Subject: [PATCH] Employee benefit - Late employee benefit application (#14465) * HR Utils - get salary slip total benefit given for a payroll period * Late employee benefit application * Additional Salary - code refactor - get_amount * new line in salary detail json * Employee benefit late application - validation refactor * Codacy fix --- .../additional_salary/additional_salary.py | 10 +--- .../employee_benefit_application.js | 58 ++++++++++++++----- .../employee_benefit_application.py | 25 +++++--- .../employee_benefit_claim.py | 2 + .../doctype/salary_detail/salary_detail.json | 2 +- erpnext/hr/doctype/salary_slip/salary_slip.py | 3 +- erpnext/hr/utils.py | 28 +++++++++ 7 files changed, 95 insertions(+), 33 deletions(-) diff --git a/erpnext/hr/doctype/additional_salary/additional_salary.py b/erpnext/hr/doctype/additional_salary/additional_salary.py index 7482c8b0f8..dfa22d7d2f 100644 --- a/erpnext/hr/doctype/additional_salary/additional_salary.py +++ b/erpnext/hr/doctype/additional_salary/additional_salary.py @@ -25,11 +25,6 @@ class AdditionalSalary(Document): frappe.throw(_("To date can not greater than employee's relieving date")) def get_amount(self, sal_start_date, sal_end_date): - # If additional salary dates in between the salary slip dates - # then return complete additional salary amount - if getdate(sal_start_date) <= getdate(self.from_date) <= getdate(sal_end_date)\ - and getdate(sal_end_date) >= getdate(self.to_date) >= getdate(sal_start_date): - return self.amount start_date = getdate(sal_start_date) end_date = getdate(sal_end_date) total_days = date_diff(getdate(self.to_date), getdate(self.from_date)) + 1 @@ -38,12 +33,9 @@ class AdditionalSalary(Document): start_date = getdate(self.from_date) if getdate(sal_end_date) > getdate(self.to_date): end_date = getdate(self.to_date) - no_of_days = date_diff(getdate(end_date), getdate(start_date)) + no_of_days = date_diff(getdate(end_date), getdate(start_date)) + 1 return amount_per_day * no_of_days - - - @frappe.whitelist() def get_additional_salary_component(employee, start_date, end_date): additional_components = frappe.db.sql(""" 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 f96f2629c3..b63c76f4b3 100644 --- a/erpnext/hr/doctype/employee_benefit_application/employee_benefit_application.js +++ b/erpnext/hr/doctype/employee_benefit_application/employee_benefit_application.js @@ -11,25 +11,53 @@ frappe.ui.form.on('Employee Benefit Application', { }); }, employee: function(frm) { - if(frm.doc.employee && frm.doc.date){ - frappe.call({ - method: "erpnext.hr.doctype.employee_benefit_application.employee_benefit_application.get_max_benefits", - args:{ - employee: frm.doc.employee, - on_date: frm.doc.date - }, - callback: function (data) { - if(!data.exc){ - if(data.message){ - frm.set_value("max_benefits", data.message); - } - } - } - }); + var method, args; + if(frm.doc.employee && frm.doc.date && frm.doc.payroll_period){ + method = "erpnext.hr.doctype.employee_benefit_application.employee_benefit_application.get_max_benefits_remaining"; + args = { + employee: frm.doc.employee, + on_date: frm.doc.date, + payroll_period: frm.doc.payroll_period + }; + get_max_benefits(frm, method, args); + } + else if(frm.doc.employee && frm.doc.date){ + method = "erpnext.hr.doctype.employee_benefit_application.employee_benefit_application.get_max_benefits"; + args = { + employee: frm.doc.employee, + on_date: frm.doc.date + }; + get_max_benefits(frm, method, args); + } + }, + payroll_period: function(frm) { + var method, args; + if(frm.doc.employee && frm.doc.date && frm.doc.payroll_period){ + method = "erpnext.hr.doctype.employee_benefit_application.employee_benefit_application.get_max_benefits_remaining"; + args = { + employee: frm.doc.employee, + on_date: frm.doc.date, + payroll_period: frm.doc.payroll_period + }; + get_max_benefits(frm, method, args); } } }); +var get_max_benefits=function(frm, method, args) { + frappe.call({ + method: method, + args: args, + callback: function (data) { + if(!data.exc){ + if(data.message){ + frm.set_value("max_benefits", data.message); + } + } + } + }); +}; + frappe.ui.form.on("Employee Benefit Application Detail",{ amount: 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 2d33ce843a..ed54fb828f 100644 --- a/erpnext/hr/doctype/employee_benefit_application/employee_benefit_application.py +++ b/erpnext/hr/doctype/employee_benefit_application/employee_benefit_application.py @@ -5,10 +5,11 @@ from __future__ import unicode_literals import frappe from frappe import _ -from frappe.utils import date_diff, getdate +from frappe.utils import date_diff, getdate, rounded 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 class EmployeeBenefitApplication(Document): def validate(self): @@ -41,9 +42,10 @@ class EmployeeBenefitApplication(Document): pro_rata_amount += max_benefit_amount else: non_pro_rata_amount += max_benefit_amount + if pro_rata_amount == 0 and non_pro_rata_amount == 0: frappe.throw(_("Please add the remainig benefits {0} to any of the existing component").format(self.remainig_benefits)) - elif non_pro_rata_amount > 0 and non_pro_rata_amount < self.remainig_benefits: + elif non_pro_rata_amount > 0 and non_pro_rata_amount < rounded(self.remainig_benefits): frappe.throw(_("You can claim only an amount of {0}, the rest amount {1} should be in the application \ as pro-rata component").format(non_pro_rata_amount, self.remainig_benefits - non_pro_rata_amount)) elif non_pro_rata_amount == 0: @@ -65,7 +67,9 @@ class EmployeeBenefitApplication(Document): for employee_benefit in self.employee_benefits: if employee_benefit.earning_component == earning_component_name: benefit_amount += employee_benefit.amount - if benefit_amount > max_benefit_amount: + prev_sal_slip_flexi_amount = get_sal_slip_total_benefit_given(self.employee, frappe.get_doc("Payroll Period", self.payroll_period), earning_component_name) + benefit_amount += prev_sal_slip_flexi_amount + if rounded(benefit_amount, 2) > max_benefit_amount: frappe.throw(_("Maximum benefit amount of component {0} exceeds {1}").format(earning_component_name, max_benefit_amount)) def validate_duplicate_on_payroll_period(self): @@ -87,10 +91,17 @@ def get_max_benefits(employee, on_date): max_benefits = frappe.db.get_value("Salary Structure", sal_struct, "max_benefits") if max_benefits > 0: return max_benefits - else: - frappe.throw(_("Employee {0} has no max benefits in salary structure {1}").format(employee, sal_struct[0][0])) - else: - frappe.throw(_("Employee {0} has no salary structure assigned").format(employee)) + return False + +@frappe.whitelist() +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: + 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) + 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): # Considering there is only one application for an year diff --git a/erpnext/hr/doctype/employee_benefit_claim/employee_benefit_claim.py b/erpnext/hr/doctype/employee_benefit_claim/employee_benefit_claim.py index 1aed7ce1f8..50295996c6 100644 --- a/erpnext/hr/doctype/employee_benefit_claim/employee_benefit_claim.py +++ b/erpnext/hr/doctype/employee_benefit_claim/employee_benefit_claim.py @@ -13,6 +13,8 @@ from erpnext.hr.doctype.salary_structure_assignment.salary_structure_assignment class EmployeeBenefitClaim(Document): def validate(self): max_benefits = get_max_benefits(self.employee, self.claim_date) + if not max_benefits or max_benefits <= 0: + frappe.throw(_("Employee {0} has no maximum benefit amount").format(self.employee)) payroll_period = get_payroll_period(self.claim_date, self.claim_date, frappe.db.get_value("Employee", self.employee, "company")) self.validate_max_benefit_for_component(payroll_period) self.validate_max_benefit_for_sal_struct(max_benefits) diff --git a/erpnext/hr/doctype/salary_detail/salary_detail.json b/erpnext/hr/doctype/salary_detail/salary_detail.json index a8f389b87d..4212f4a125 100644 --- a/erpnext/hr/doctype/salary_detail/salary_detail.json +++ b/erpnext/hr/doctype/salary_detail/salary_detail.json @@ -606,4 +606,4 @@ "sort_order": "DESC", "track_changes": 0, "track_seen": 0 -} \ No newline at end of file +} diff --git a/erpnext/hr/doctype/salary_slip/salary_slip.py b/erpnext/hr/doctype/salary_slip/salary_slip.py index dc2446ba35..dbd73b5602 100644 --- a/erpnext/hr/doctype/salary_slip/salary_slip.py +++ b/erpnext/hr/doctype/salary_slip/salary_slip.py @@ -107,7 +107,8 @@ class SalarySlip(TransactionBase): 'depends_on_lwp' : struct_row.depends_on_lwp, 'salary_component' : struct_row.salary_component, 'abbr' : struct_row.abbr, - 'do_not_include_in_total' : struct_row.do_not_include_in_total + 'do_not_include_in_total' : struct_row.do_not_include_in_total, + 'is_flexible_benefit': struct_row.is_flexible_benefit }) else: component_row.amount = amount diff --git a/erpnext/hr/utils.py b/erpnext/hr/utils.py index 11dbdf25d9..f4733176ac 100644 --- a/erpnext/hr/utils.py +++ b/erpnext/hr/utils.py @@ -350,3 +350,31 @@ def get_annual_component_pay(frequency, amount): return amount * 12 elif frequency == "Bimonthly": return amount * 6 + +def get_sal_slip_total_benefit_given(employee, payroll_period, component=False): + total_given_benefit_amount = 0 + query = """ + select sum(sd.amount) as 'total_amount' + from `tabSalary Slip` ss, `tabSalary Detail` sd + where ss.employee=%(employee)s + and ss.docstatus = 1 and ss.name = sd.parent + and sd.is_flexible_benefit = 1 and sd.parentfield = "earnings" + and sd.parenttype = "Salary Slip" + and (ss.start_date between %(start_date)s and %(end_date)s + or ss.end_date between %(start_date)s and %(end_date)s + or (ss.start_date < %(start_date)s and ss.end_date > %(end_date)s)) + """ + + if component: + query += "and sd.salary_component = %(component)s" + + sum_of_given_benefit = frappe.db.sql(query, { + 'employee': employee, + 'start_date': payroll_period.start_date, + 'end_date': payroll_period.end_date, + 'component': component + }, as_dict=True) + + 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