From 8eec40cb8a6aa0c736c00e6ca2754fdab366b4ed Mon Sep 17 00:00:00 2001 From: Jamsheer Date: Fri, 25 May 2018 11:06:42 +0530 Subject: [PATCH] Employee Benefits in Salary Slip - Application and Claim --- .../employee_benefit_application.py | 67 ++++++++-------- .../employee_benefit_claim.py | 77 ++++++++----------- erpnext/hr/doctype/salary_slip/salary_slip.py | 60 ++++----------- 3 files changed, 80 insertions(+), 124 deletions(-) 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 e1af43090e..1902b5d6ea 100644 --- a/erpnext/hr/doctype/employee_benefit_application/employee_benefit_application.py +++ b/erpnext/hr/doctype/employee_benefit_application/employee_benefit_application.py @@ -110,8 +110,9 @@ def get_assigned_salary_sturecture(employee, _date): if salary_structure: return salary_structure -def get_employee_benefit_application(employee, start_date, end_date): - employee_benefits = frappe.db.sql(""" +def get_benefit_component_amount(employee, start_date, end_date, struct_row, sal_struct): + # Considering there is only one application for an year + benefit_application_name = frappe.db.sql(""" select name from `tabEmployee Benefit Application` where employee=%(employee)s and docstatus = 1 @@ -122,41 +123,41 @@ def get_employee_benefit_application(employee, start_date, end_date): 'end_date': end_date }) - if employee_benefits: - for employee_benefit in employee_benefits: - employee_benefit_obj = frappe.get_doc("Employee Benefit Application", employee_benefit[0]) - return get_benefit_components(employee_benefit_obj, employee, start_date, end_date) - -def get_benefit_components(employee_benefit_application, employee, start_date, end_date): - salary_components_array = [] - group_component_amount = {} payroll_period_days = get_payroll_period_days(start_date, end_date, frappe.db.get_value("Employee", employee, "company")) - for employee_benefit in employee_benefit_application.employee_benefits: - if employee_benefit.is_pro_rata_applicable == 1: - struct_row = {} - salary_components_dict = {} - amount = get_amount(payroll_period_days, start_date, end_date, employee_benefit.amount) - sc = frappe.get_doc("Salary Component", employee_benefit.earning_component) - salary_component = sc - if sc.earning_component_group and not sc.is_group and not sc.flexi_default: - salary_component = frappe.get_doc("Salary Component", sc.earning_component_group) - if group_component_amount and group_component_amount.has_key(sc.earning_component_group): - group_component_amount[sc.earning_component_group] += amount - else: - group_component_amount[sc.earning_component_group] = amount - amount = group_component_amount[sc.earning_component_group] - struct_row['depends_on_lwp'] = salary_component.depends_on_lwp - struct_row['salary_component'] = salary_component.name - struct_row['abbr'] = salary_component.salary_component_abbr - struct_row['do_not_include_in_total'] = salary_component.do_not_include_in_total - salary_components_dict['amount'] = amount - salary_components_dict['struct_row'] = struct_row - salary_components_array.append(salary_components_dict) + if payroll_period_days: + # If there is application for benefit claim then fetch the amount from it. + 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) - if len(salary_components_array) > 0: - return salary_components_array + # TODO: Check if there is benefit claim for employee then pro-rata devid the rest of amount + # 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) return False +def get_benefit_pro_rata_ratio_amount(sal_struct, component_max, payroll_period_days, start_date, end_date): + total_pro_rata_max = 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: + total_pro_rata_max += max_benefit_amount + if total_pro_rata_max > 0: + 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 + +def get_benefit_amount(application, start_date, end_date, struct_row, payroll_period_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) + 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 amount_per_day = amount / payroll_period_days 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 cd9c07c64b..b8aef0cf9d 100644 --- a/erpnext/hr/doctype/employee_benefit_claim/employee_benefit_claim.py +++ b/erpnext/hr/doctype/employee_benefit_claim/employee_benefit_claim.py @@ -13,9 +13,9 @@ from frappe.desk.reportview import get_match_cond class EmployeeBenefitClaim(Document): def validate(self): max_benefits = get_max_benefits(self.employee, self.claim_date) - self.validate_max_benefit_for_component() - self.validate_max_benefit_for_sal_struct(max_benefits) 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) self.validate_benefit_claim_amount(max_benefits, payroll_period) if not self.is_pro_rata_applicable: self.validate_non_pro_rata_benefit_claim(max_benefits, payroll_period) @@ -31,20 +31,27 @@ class EmployeeBenefitClaim(Document): if self.claimed_amount > max_benefits: frappe.throw(_("Maximum benefit amount of employee {0} exceeds {1}").format(self.employee, max_benefits)) - def validate_max_benefit_for_component(self): - if self.claimed_amount > self.max_amount_eligible: + def validate_max_benefit_for_component(self, payroll_period): + claimed_amount = self.claimed_amount + claimed_amount += self.get_previous_claimed_amount(payroll_period, self.earning_component) + if claimed_amount > self.max_amount_eligible: frappe.throw(_("Maximum amount eligible for the component {0} exceeds {1}").format(self.earning_component, self.max_amount_eligible)) def validate_non_pro_rata_benefit_claim(self, max_benefits, payroll_period): claimed_amount = self.claimed_amount pro_rata_amount = self.get_pro_rata_amount_in_application(payroll_period.name) + if not pro_rata_amount: + pro_rata_amount = 0 + # TODO: + # Get pro_rata_amount if there is no application, + # get salary slip for the period and calculate pro-rata amount per day and mulitply with payroll_period_days + claimed_amount += self.get_previous_claimed_amount(payroll_period, True) if max_benefits < pro_rata_amount + claimed_amount: frappe.throw(_("Maximum benefit of employee {0} exceeds {1} by the sum {2} of benefit application pro-rata component\ amount and previous claimed amount").format(self.employee, max_benefits, pro_rata_amount+claimed_amount-max_benefits)) def get_pro_rata_amount_in_application(self, payroll_period): - pro_rata_dispensed_amount = 0 application = frappe.db.exists( "Employee Benefit Application", { @@ -54,10 +61,10 @@ class EmployeeBenefitClaim(Document): } ) if application: - pro_rata_dispensed_amount = frappe.db.get_value("Employee Benefit Application", application, "pro_rata_dispensed_amount") - return pro_rata_dispensed_amount + return frappe.db.get_value("Employee Benefit Application", application, "pro_rata_dispensed_amount") + return False - def get_previous_claimed_amount(self, payroll_period, non_pro_rata=False): + def get_previous_claimed_amount(self, payroll_period, non_pro_rata=False, component=False): total_claimed_amount = 0 query = """ select sum(claimed_amount) as 'total_amount' @@ -68,56 +75,36 @@ class EmployeeBenefitClaim(Document): """ if non_pro_rata: query += "and is_pro_rata_applicable = 0" + if component: + query += "and earning_component = %(component)s" sum_of_claimed_amount = frappe.db.sql(query, { 'employee': self.employee, 'start_date': payroll_period.start_date, - 'end_date': payroll_period.end_date + 'end_date': payroll_period.end_date, + 'component': component }, as_dict=True) - if sum_of_claimed_amount: + if sum_of_claimed_amount and sum_of_claimed_amount[0].total_amount > 0: total_claimed_amount = sum_of_claimed_amount[0].total_amount return total_claimed_amount -def get_employee_benefit_claim(employee, start_date, end_date): - employee_benefits = frappe.db.sql(""" - select name from `tabEmployee Benefit Claim` +def get_benefit_claim_amount(employee, start_date, end_date, struct_row): + benefit_claim_details = frappe.db.sql(""" + select claimed_amount from `tabEmployee Benefit Claim` where employee=%(employee)s and docstatus = 1 and is_pro_rata_applicable = 0 + and earning_component = %(earning_component)s and (claim_date between %(start_date)s and %(end_date)s) """, { 'employee': employee, 'start_date': start_date, - 'end_date': end_date - }) + 'end_date': end_date, + 'earning_component': struct_row.salary_component + }, as_dict = True) - if employee_benefits: - salary_components_array = [] - for employee_benefit in employee_benefits: - struct_row = {} - salary_components_dict = {} - group_component_amount = {} - - employee_benefit_claim = frappe.get_doc("Employee Benefit Claim", employee_benefit[0]) - amount = employee_benefit_claim.claimed_amount - sc = frappe.get_doc("Salary Component", employee_benefit_claim.earning_component) - - salary_component = sc - if sc.earning_component_group and not sc.is_group and not sc.flexi_default: - salary_component = frappe.get_doc("Salary Component", sc.earning_component_group) - if group_component_amount and group_component_amount.has_key(sc.earning_component_group): - group_component_amount[sc.earning_component_group] += amount - else: - group_component_amount[sc.earning_component_group] = amount - amount = group_component_amount[sc.earning_component_group] - - struct_row['depends_on_lwp'] = salary_component.depends_on_lwp - struct_row['salary_component'] = salary_component.name - struct_row['abbr'] = salary_component.salary_component_abbr - struct_row['do_not_include_in_total'] = salary_component.do_not_include_in_total - salary_components_dict['amount'] = amount - salary_components_dict['struct_row'] = struct_row - salary_components_array.append(salary_components_dict) - - if len(salary_components_array) > 0: - return salary_components_array + if benefit_claim_details: + claimed_amount = 0 + for claim_detail in benefit_claim_details: + claimed_amount += claim_detail.claimed_amount + return claimed_amount return False diff --git a/erpnext/hr/doctype/salary_slip/salary_slip.py b/erpnext/hr/doctype/salary_slip/salary_slip.py index 6651e0561f..32fc4b90aa 100644 --- a/erpnext/hr/doctype/salary_slip/salary_slip.py +++ b/erpnext/hr/doctype/salary_slip/salary_slip.py @@ -14,9 +14,8 @@ from erpnext.utilities.transaction_base import TransactionBase from frappe.utils.background_jobs import enqueue from erpnext.hr.doctype.additional_salary_component.additional_salary_component import get_additional_salary_component from erpnext.hr.utils import get_payroll_period -from erpnext.hr.doctype.employee_benefit_application.employee_benefit_application import get_employee_benefit_application, get_amount -from erpnext.hr.doctype.payroll_period.payroll_period import get_payroll_period_days -from erpnext.hr.doctype.employee_benefit_claim.employee_benefit_claim import get_employee_benefit_claim +from erpnext.hr.doctype.employee_benefit_application.employee_benefit_application import get_benefit_component_amount +from erpnext.hr.doctype.employee_benefit_claim.employee_benefit_claim import get_benefit_claim_amount class SalarySlip(TransactionBase): def autoname(self): @@ -62,6 +61,10 @@ class SalarySlip(TransactionBase): amount = self.eval_condition_and_formula(struct_row, data) if amount and struct_row.statistical_component == 0: self.update_component_row(struct_row, amount, key) + + if key=="earnings" and struct_row.is_flexible_benefit == 1: + self.add_employee_flexi_benefits(struct_row) + if key=="deductions" and struct_row.variable_based_on_taxable_salary: tax_row, amount = self.calculate_pro_rata_tax(struct_row.salary_component) if tax_row and amount: @@ -74,41 +77,15 @@ class SalarySlip(TransactionBase): amount = additional_component.amount + self.get_amount_from_exisiting_component(frappe._dict(additional_component.struct_row).salary_component) self.update_component_row(frappe._dict(additional_component.struct_row), amount, "earnings") - max_benefits = self._salary_structure_doc.get("max_benefits") - if max_benefits > 0: - self.add_employee_benefits(max_benefits) - - def add_employee_benefits(self, max_benefits): - employee_benefits = get_employee_benefit_application(self.employee, self.start_date, self.end_date) - if employee_benefits: - for employee_benefit in employee_benefits: - benefit_component = frappe._dict(employee_benefit) - amount = benefit_component.amount + self.get_amount_from_exisiting_component(frappe._dict(benefit_component.struct_row).salary_component) - self.update_component_row(frappe._dict(benefit_component.struct_row), amount, "earnings") + 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) + if benefit_component_amount: + self.update_component_row(struct_row, benefit_component_amount, "earnings") else: - default_flexi_compenent = frappe.db.exists( - 'Salary Component', - { - 'is_flexible_benefit': 1, - 'is_pro_rata_applicable': 1, - 'flexi_default': 1 - } - ) - if default_flexi_compenent: - flexi_struct_row = self.create_flexi_struct_row(default_flexi_compenent) - payroll_period_days = get_payroll_period_days(self.start_date, self.end_date, self.company) - amount = get_amount(payroll_period_days, self.start_date, self.end_date, max_benefits) - amount += self.get_amount_from_exisiting_component(default_flexi_compenent) - self.update_component_row(flexi_struct_row, amount, "earnings") - else: - frappe.throw(_("Configure default flexible benefit salary component to apply pro-rata benefit")) - - benefit_claims = get_employee_benefit_claim(self.employee, self.start_date, self.end_date) - if benefit_claims: - for benefit_claim in benefit_claims: - benefit_component = frappe._dict(benefit_claim) - amount = benefit_component.amount + self.get_amount_from_exisiting_component(frappe._dict(benefit_component.struct_row).salary_component) - self.update_component_row(frappe._dict(benefit_component.struct_row), amount, "earnings") + benefit_claim_amount = get_benefit_claim_amount(self.employee, self.start_date, self.end_date, struct_row) + if benefit_claim_amount: + self.update_component_row(struct_row, benefit_claim_amount, "earnings") def get_amount_from_exisiting_component(self, salary_component): amount = 0 @@ -117,15 +94,6 @@ class SalarySlip(TransactionBase): amount = d.amount return amount - def create_flexi_struct_row(self, default_flexi_compenent): - salary_component = frappe.get_doc("Salary Component", default_flexi_compenent) - flexi_struct_row = {} - flexi_struct_row['depends_on_lwp'] = salary_component.depends_on_lwp - flexi_struct_row['salary_component'] = salary_component.name - flexi_struct_row['abbr'] = salary_component.salary_component_abbr - flexi_struct_row['do_not_include_in_total'] = salary_component.do_not_include_in_total - return frappe._dict(flexi_struct_row) - def update_component_row(self, struct_row, amount, key): component_row = None for d in self.get(key):