From 514122bf6f24b60c51b180fc5feb86a1f96c3e0a Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Thu, 11 Mar 2021 17:22:11 +0530 Subject: [PATCH] refactor(payroll): simplified logic for additional salary --- .../additional_salary/additional_salary.py | 53 +++---- .../doctype/salary_slip/salary_slip.py | 133 ++++++++++-------- 2 files changed, 91 insertions(+), 95 deletions(-) diff --git a/erpnext/payroll/doctype/additional_salary/additional_salary.py b/erpnext/payroll/doctype/additional_salary/additional_salary.py index f5af677fce..029e11ff9b 100644 --- a/erpnext/payroll/doctype/additional_salary/additional_salary.py +++ b/erpnext/payroll/doctype/additional_salary/additional_salary.py @@ -89,10 +89,11 @@ class AdditionalSalary(Document): 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, component_type): - additional_salaries = frappe.db.sql(""" - select name, salary_component, type, amount, overwrite_salary_structure_amount, deduct_full_tax_on_selected_payroll_date +def get_additional_salaries(employee, start_date, end_date, component_type): + additional_salary_list = frappe.db.sql(""" + select name, salary_component as component, type, amount, + overwrite_salary_structure_amount as overwrite, + deduct_full_tax_on_selected_payroll_date from `tabAdditional Salary` where employee=%(employee)s and docstatus = 1 @@ -102,7 +103,7 @@ def get_additional_salary_component(employee, start_date, end_date, component_ty from_date <= %(to_date)s and to_date >= %(to_date)s ) and type = %(component_type)s - order by salary_component, overwrite_salary_structure_amount DESC + order by salary_component, overwrite ASC """, { 'employee': employee, 'from_date': start_date, @@ -110,38 +111,18 @@ def get_additional_salary_component(employee, start_date, end_date, component_ty 'component_type': "Earning" if component_type == "earnings" else "Deduction" }, as_dict=1) - existing_salary_components= [] - salary_components_details = {} - additional_salary_details = [] + additional_salaries = [] + components_to_overwrite = [] - overwrites_components = [ele.salary_component for ele in additional_salaries if ele.overwrite_salary_structure_amount == 1] + for d in additional_salary_list: + if d.overwrite: + if d.component in components_to_overwrite: + frappe.throw(_("Multiple Additional Salaries with overwrite " + "property exist for Salary Component {0} between {1} and {2}.").format( + frappe.bold(d.component), start_date, end_date), title=_("Error")) - component_fields = ["depends_on_payment_days", "salary_component_abbr", "is_tax_applicable", "variable_based_on_taxable_salary", 'type'] - for d in additional_salaries: + components_to_overwrite.append(d.component) - if d.salary_component not in existing_salary_components: - component = frappe.get_all("Salary Component", filters={'name': d.salary_component}, fields=component_fields) - struct_row = frappe._dict({'salary_component': d.salary_component}) - if component: - struct_row.update(component[0]) + additional_salaries.append(d) - struct_row['deduct_full_tax_on_selected_payroll_date'] = d.deduct_full_tax_on_selected_payroll_date - struct_row['is_additional_component'] = 1 - - salary_components_details[d.salary_component] = struct_row - - - if overwrites_components.count(d.salary_component) > 1: - frappe.throw(_("Multiple Additional Salaries with overwrite property exist for Salary Component: {0} between {1} and {2}.".format(d.salary_component, start_date, end_date)), title=_("Error")) - else: - additional_salary_details.append({ - 'name': d.name, - 'component': d.salary_component, - 'amount': d.amount, - 'type': d.type, - 'overwrite': d.overwrite_salary_structure_amount, - }) - - existing_salary_components.append(d.salary_component) - - return salary_components_details, additional_salary_details + return additional_salaries diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py index 595d6974fd..a04a635807 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py @@ -13,7 +13,7 @@ from erpnext.payroll.doctype.payroll_entry.payroll_entry import get_start_end_da from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee from erpnext.utilities.transaction_base import TransactionBase from frappe.utils.background_jobs import enqueue -from erpnext.payroll.doctype.additional_salary.additional_salary import get_additional_salary_component +from erpnext.payroll.doctype.additional_salary.additional_salary import get_additional_salaries from erpnext.payroll.doctype.payroll_period.payroll_period import get_period_factor, get_payroll_period from erpnext.payroll.doctype.employee_benefit_application.employee_benefit_application import get_benefit_component_amount from erpnext.payroll.doctype.employee_benefit_claim.employee_benefit_claim import get_benefit_claim_amount, get_last_payroll_period_benefits @@ -524,7 +524,7 @@ class SalarySlip(TransactionBase): except NameError as err: frappe.throw(_("{0}
This error can be due to missing or deleted field.").format(err), - title=_("Name error")) + title=_("Name error")) except SyntaxError as err: frappe.throw(_("Syntax error in formula or condition: {0}").format(err)) except Exception as e: @@ -558,15 +558,16 @@ class SalarySlip(TransactionBase): self.update_component_row(frappe._dict(last_benefit.struct_row), amount, "earnings") def add_additional_salary_components(self, component_type): - salary_components_details, additional_salary_details = get_additional_salary_component(self.employee, + additional_salaries = get_additional_salaries(self.employee, self.start_date, self.end_date, component_type) - if salary_components_details and additional_salary_details: - for additional_salary in additional_salary_details: - additional_salary =frappe._dict(additional_salary) - amount = additional_salary.amount - overwrite = additional_salary.overwrite - self.update_component_row(frappe._dict(salary_components_details[additional_salary.component]), amount, - component_type, overwrite=overwrite, additional_salary=additional_salary.name) + + for additional_salary in additional_salaries: + self.update_component_row( + get_salary_component_data(additional_salary.component), + additional_salary.amount, + component_type, + additional_salary + ) def add_tax_components(self, payroll_period): # Calculate variable_based_on_taxable_salary after all components updated in salary slip @@ -583,47 +584,59 @@ class SalarySlip(TransactionBase): for d in tax_components: tax_amount = self.calculate_variable_based_on_taxable_salary(d, payroll_period) - tax_row = self.get_salary_slip_row(d) + tax_row = get_salary_component_data(d) self.update_component_row(tax_row, tax_amount, "deductions") - def update_component_row(self, struct_row, amount, key, overwrite=1, additional_salary = ''): + def update_component_row(self, component_data, amount, component_type, additional_salary=None): component_row = None - for d in self.get(key): - if d.salary_component == struct_row.salary_component: + for d in self.get(component_type): + if d.salary_component != component_data.salary_component: + continue + + if ( + not d.additional_salary + and (not additional_salary or additional_salary.overwrite) + or additional_salary + and additional_salary.name == d.additional_salary + ): component_row = d + break - if not component_row or (struct_row.get("is_additional_component") and not overwrite): - if amount: - self.append(key, { - 'amount': amount, - 'default_amount': amount if not struct_row.get("is_additional_component") else 0, - 'depends_on_payment_days' : struct_row.depends_on_payment_days, - 'salary_component' : struct_row.salary_component, - 'abbr' : struct_row.abbr or struct_row.get("salary_component_abbr"), - 'additional_salary': additional_salary, - 'do_not_include_in_total' : struct_row.do_not_include_in_total, - 'is_tax_applicable': struct_row.is_tax_applicable, - 'is_flexible_benefit': struct_row.is_flexible_benefit, - 'variable_based_on_taxable_salary': struct_row.variable_based_on_taxable_salary, - 'deduct_full_tax_on_selected_payroll_date': struct_row.deduct_full_tax_on_selected_payroll_date, - 'additional_amount': amount if struct_row.get("is_additional_component") else 0, - 'exempted_from_income_tax': struct_row.exempted_from_income_tax - }) + if additional_salary and additional_salary.overwrite: + # Additional Salary with overwrite checked, remove default rows of same component + self.set(component_type, [ + d for d in self.get(component_type) + if d.salary_component != component_data.salary_component + or d.additional_salary and additional_salary.name != d.additional_salary + or d == component_row + ]) + + if not component_row: + if not amount: + return + + component_row = self.append(component_type) + for attr in ( + 'depends_on_payment_days', 'salary_component', 'abbr' + 'do_not_include_in_total', 'is_tax_applicable', + 'is_flexible_benefit', 'variable_based_on_taxable_salary', + 'exempted_from_income_tax' + ): + component_row.set(attr, component_data.get(attr)) + + if additional_salary: + component_row.default_amount = 0 + component_row.additional_amount = amount + component_row.additional_salary = additional_salary.name + component_row.deduct_full_tax_on_selected_payroll_date = \ + additional_salary.deduct_full_tax_on_selected_payroll_date else: - if struct_row.get("is_additional_component"): - if overwrite: - component_row.additional_amount = amount - component_row.get("default_amount", 0) - component_row.additional_salary = additional_salary - else: - component_row.additional_amount = amount + component_row.default_amount = amount + component_row.additional_amount = 0 + component_row.deduct_full_tax_on_selected_payroll_date = \ + component_data.deduct_full_tax_on_selected_payroll_date - if not overwrite and component_row.default_amount: - amount += component_row.default_amount - else: - component_row.default_amount = amount - - component_row.amount = amount - component_row.deduct_full_tax_on_selected_payroll_date = struct_row.deduct_full_tax_on_selected_payroll_date + component_row.amount = amount def calculate_variable_based_on_taxable_salary(self, tax_component, payroll_period): if not payroll_period: @@ -950,26 +963,13 @@ class SalarySlip(TransactionBase): return frappe.safe_eval(condition, self.whitelisted_globals, data) except NameError as err: frappe.throw(_("{0}
This error can be due to missing or deleted field.").format(err), - title=_("Name error")) + title=_("Name error")) except SyntaxError as err: frappe.throw(_("Syntax error in condition: {0}").format(err)) except Exception as e: frappe.throw(_("Error in formula or condition: {0}").format(e)) raise - def get_salary_slip_row(self, salary_component): - component = frappe.get_doc("Salary Component", salary_component) - # Data for update_component_row - struct_row = frappe._dict() - struct_row['depends_on_payment_days'] = component.depends_on_payment_days - struct_row['salary_component'] = component.name - struct_row['abbr'] = component.salary_component_abbr - struct_row['do_not_include_in_total'] = component.do_not_include_in_total - struct_row['is_tax_applicable'] = component.is_tax_applicable - struct_row['is_flexible_benefit'] = component.is_flexible_benefit - struct_row['variable_based_on_taxable_salary'] = component.variable_based_on_taxable_salary - return struct_row - def get_component_totals(self, component_type, depends_on_payment_days=0): joining_date, relieving_date = frappe.get_cached_value("Employee", self.employee, ["date_of_joining", "relieving_date"]) @@ -1032,7 +1032,6 @@ class SalarySlip(TransactionBase): self.total_loan_repayment += payment.total_payment def get_loan_details(self): - return frappe.get_all("Loan", fields=["name", "interest_income_account", "loan_account", "loan_type"], filters = { @@ -1263,3 +1262,19 @@ def unlink_ref_doc_from_salary_slip(ref_no): def generate_password_for_pdf(policy_template, employee): employee = frappe.get_doc("Employee", employee) return policy_template.format(**employee.as_dict()) + +def get_salary_component_data(component): + return frappe.get_value( + "Salary Component", + component, + [ + "name as salary_component", + "depends_on_payment_days", + "salary_component_abbr as abbr", + "do_not_include_in_total", + "is_tax_applicable", + "is_flexible_benefit", + "variable_based_on_taxable_salary", + ], + as_dict=1, + )