Merge pull request #24824 from resilient-tech/fix-salary

refactor(payroll): simplified logic for additional salary
This commit is contained in:
Anurag Mishra 2021-03-16 21:42:10 +05:30 committed by GitHub
commit 8fbb043190
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 91 additions and 95 deletions

View File

@ -89,10 +89,11 @@ class AdditionalSalary(Document):
no_of_days = date_diff(getdate(end_date), getdate(start_date)) + 1 no_of_days = date_diff(getdate(end_date), getdate(start_date)) + 1
return amount_per_day * no_of_days return amount_per_day * no_of_days
@frappe.whitelist() def get_additional_salaries(employee, start_date, end_date, component_type):
def get_additional_salary_component(employee, start_date, end_date, component_type): additional_salary_list = frappe.db.sql("""
additional_salaries = frappe.db.sql(""" select name, salary_component as component, type, amount,
select name, salary_component, type, amount, overwrite_salary_structure_amount, deduct_full_tax_on_selected_payroll_date overwrite_salary_structure_amount as overwrite,
deduct_full_tax_on_selected_payroll_date
from `tabAdditional Salary` from `tabAdditional Salary`
where employee=%(employee)s where employee=%(employee)s
and docstatus = 1 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 from_date <= %(to_date)s and to_date >= %(to_date)s
) )
and type = %(component_type)s and type = %(component_type)s
order by salary_component, overwrite_salary_structure_amount DESC order by salary_component, overwrite ASC
""", { """, {
'employee': employee, 'employee': employee,
'from_date': start_date, '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" 'component_type': "Earning" if component_type == "earnings" else "Deduction"
}, as_dict=1) }, as_dict=1)
existing_salary_components= [] additional_salaries = []
salary_components_details = {} components_to_overwrite = []
additional_salary_details = []
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'] components_to_overwrite.append(d.component)
for d in additional_salaries:
if d.salary_component not in existing_salary_components: additional_salaries.append(d)
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])
struct_row['deduct_full_tax_on_selected_payroll_date'] = d.deduct_full_tax_on_selected_payroll_date return additional_salaries
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

View File

@ -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.hr.doctype.employee.employee import get_holiday_list_for_employee
from erpnext.utilities.transaction_base import TransactionBase from erpnext.utilities.transaction_base import TransactionBase
from frappe.utils.background_jobs import enqueue 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.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_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 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: except NameError as err:
frappe.throw(_("{0} <br> This error can be due to missing or deleted field.").format(err), frappe.throw(_("{0} <br> This error can be due to missing or deleted field.").format(err),
title=_("Name error")) title=_("Name error"))
except SyntaxError as err: except SyntaxError as err:
frappe.throw(_("Syntax error in formula or condition: {0}").format(err)) frappe.throw(_("Syntax error in formula or condition: {0}").format(err))
except Exception as e: except Exception as e:
@ -558,15 +558,16 @@ class SalarySlip(TransactionBase):
self.update_component_row(frappe._dict(last_benefit.struct_row), amount, "earnings") self.update_component_row(frappe._dict(last_benefit.struct_row), amount, "earnings")
def add_additional_salary_components(self, component_type): 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) self.start_date, self.end_date, component_type)
if salary_components_details and additional_salary_details:
for additional_salary in additional_salary_details: for additional_salary in additional_salaries:
additional_salary =frappe._dict(additional_salary) self.update_component_row(
amount = additional_salary.amount get_salary_component_data(additional_salary.component),
overwrite = additional_salary.overwrite additional_salary.amount,
self.update_component_row(frappe._dict(salary_components_details[additional_salary.component]), amount, component_type,
component_type, overwrite=overwrite, additional_salary=additional_salary.name) additional_salary
)
def add_tax_components(self, payroll_period): def add_tax_components(self, payroll_period):
# Calculate variable_based_on_taxable_salary after all components updated in salary slip # 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: for d in tax_components:
tax_amount = self.calculate_variable_based_on_taxable_salary(d, payroll_period) 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") 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 component_row = None
for d in self.get(key): for d in self.get(component_type):
if d.salary_component == struct_row.salary_component: 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 component_row = d
break
if not component_row or (struct_row.get("is_additional_component") and not overwrite): if additional_salary and additional_salary.overwrite:
if amount: # Additional Salary with overwrite checked, remove default rows of same component
self.append(key, { self.set(component_type, [
'amount': amount, d for d in self.get(component_type)
'default_amount': amount if not struct_row.get("is_additional_component") else 0, if d.salary_component != component_data.salary_component
'depends_on_payment_days' : struct_row.depends_on_payment_days, or d.additional_salary and additional_salary.name != d.additional_salary
'salary_component' : struct_row.salary_component, or d == component_row
'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, if not component_row:
'is_tax_applicable': struct_row.is_tax_applicable, if not amount:
'is_flexible_benefit': struct_row.is_flexible_benefit, return
'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, component_row = self.append(component_type)
'additional_amount': amount if struct_row.get("is_additional_component") else 0, for attr in (
'exempted_from_income_tax': struct_row.exempted_from_income_tax '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: else:
if struct_row.get("is_additional_component"): component_row.default_amount = amount
if overwrite: component_row.additional_amount = 0
component_row.additional_amount = amount - component_row.get("default_amount", 0) component_row.deduct_full_tax_on_selected_payroll_date = \
component_row.additional_salary = additional_salary component_data.deduct_full_tax_on_selected_payroll_date
else:
component_row.additional_amount = amount
if not overwrite and component_row.default_amount: component_row.amount = 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
def calculate_variable_based_on_taxable_salary(self, tax_component, payroll_period): def calculate_variable_based_on_taxable_salary(self, tax_component, payroll_period):
if not payroll_period: if not payroll_period:
@ -950,26 +963,13 @@ class SalarySlip(TransactionBase):
return frappe.safe_eval(condition, self.whitelisted_globals, data) return frappe.safe_eval(condition, self.whitelisted_globals, data)
except NameError as err: except NameError as err:
frappe.throw(_("{0} <br> This error can be due to missing or deleted field.").format(err), frappe.throw(_("{0} <br> This error can be due to missing or deleted field.").format(err),
title=_("Name error")) title=_("Name error"))
except SyntaxError as err: except SyntaxError as err:
frappe.throw(_("Syntax error in condition: {0}").format(err)) frappe.throw(_("Syntax error in condition: {0}").format(err))
except Exception as e: except Exception as e:
frappe.throw(_("Error in formula or condition: {0}").format(e)) frappe.throw(_("Error in formula or condition: {0}").format(e))
raise 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): def get_component_totals(self, component_type, depends_on_payment_days=0):
joining_date, relieving_date = frappe.get_cached_value("Employee", self.employee, joining_date, relieving_date = frappe.get_cached_value("Employee", self.employee,
["date_of_joining", "relieving_date"]) ["date_of_joining", "relieving_date"])
@ -1032,7 +1032,6 @@ class SalarySlip(TransactionBase):
self.total_loan_repayment += payment.total_payment self.total_loan_repayment += payment.total_payment
def get_loan_details(self): def get_loan_details(self):
return frappe.get_all("Loan", return frappe.get_all("Loan",
fields=["name", "interest_income_account", "loan_account", "loan_type"], fields=["name", "interest_income_account", "loan_account", "loan_type"],
filters = { filters = {
@ -1263,3 +1262,19 @@ def unlink_ref_doc_from_salary_slip(ref_no):
def generate_password_for_pdf(policy_template, employee): def generate_password_for_pdf(policy_template, employee):
employee = frappe.get_doc("Employee", employee) employee = frappe.get_doc("Employee", employee)
return policy_template.format(**employee.as_dict()) 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,
)