Browse Source

feat: Tax for recurring additional salary (#27459)

* fix: Logic for tax calculation on recurring additional salary

* fix: Get actual amount always in case of overwritten additional salary even if based on payment days

* feat: Test case added for recurring additional salary

* fix: use query builder to get additional salaries instead of raw SQL

* fix: query formatting and remove trailing spaces

Co-authored-by: Rucha Mahabal <ruchamahabal2@gmail.com>
develop
Nabin Hait 3 years ago
committed by GitHub
parent
commit
2ef4844a3c
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      erpnext/hr/doctype/employee/test_employee.py
  2. 41
      erpnext/payroll/doctype/additional_salary/additional_salary.py
  3. 13
      erpnext/payroll/doctype/salary_detail/salary_detail.json
  4. 49
      erpnext/payroll/doctype/salary_slip/salary_slip.py
  5. 69
      erpnext/payroll/doctype/salary_slip/test_salary_slip.py

1
erpnext/hr/doctype/employee/test_employee.py

@ -55,6 +55,7 @@ def make_employee(user, company=None, **kwargs):
"email": user,
"first_name": user,
"new_password": "password",
"send_welcome_email": 0,
"roles": [{"doctype": "Has Role", "role": "Employee"}]
}).insert()

41
erpnext/payroll/doctype/additional_salary/additional_salary.py

@ -125,27 +125,28 @@ 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_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
and (
payroll_date between %(from_date)s and %(to_date)s
or
from_date <= %(to_date)s and to_date >= %(to_date)s
)
and type = %(component_type)s
order by salary_component, overwrite ASC
""", {
'employee': employee,
'from_date': start_date,
'to_date': end_date,
'component_type': "Earning" if component_type == "earnings" else "Deduction"
}, as_dict=1)
comp_type = 'Earning' if component_type == 'earnings' else 'Deduction'
additional_sal = frappe.qb.DocType('Additional Salary')
component_field = additional_sal.salary_component.as_('component')
overwrite_field = additional_sal.overwrite_salary_structure_amount.as_('overwrite')
additional_salary_list = frappe.qb.from_(
additional_sal
).select(
additional_sal.name, component_field, additional_sal.type,
additional_sal.amount, additional_sal.is_recurring, overwrite_field,
additional_sal.deduct_full_tax_on_selected_payroll_date
).where(
(additional_sal.employee == employee)
& (additional_sal.docstatus == 1)
& (additional_sal.type == comp_type)
).where(
additional_sal.payroll_date[start_date: end_date]
| ((additional_sal.from_date <= end_date) & (additional_sal.to_date >= end_date))
).run(as_dict=True)
additional_salaries = []
components_to_overwrite = []

13
erpnext/payroll/doctype/salary_detail/salary_detail.json

@ -12,6 +12,7 @@
"year_to_date",
"section_break_5",
"additional_salary",
"is_recurring_additional_salary",
"statistical_component",
"depends_on_payment_days",
"exempted_from_income_tax",
@ -235,11 +236,19 @@
"label": "Year To Date",
"options": "currency",
"read_only": 1
}
},
{
"default": "0",
"depends_on": "eval:doc.parenttype=='Salary Slip' && doc.additional_salary",
"fieldname": "is_recurring_additional_salary",
"fieldtype": "Check",
"label": "Is Recurring Additional Salary",
"read_only": 1
}
],
"istable": 1,
"links": [],
"modified": "2021-01-14 13:39:15.847158",
"modified": "2021-08-30 13:39:15.847158",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Salary Detail",

49
erpnext/payroll/doctype/salary_slip/salary_slip.py

@ -630,7 +630,8 @@ class SalarySlip(TransactionBase):
get_salary_component_data(additional_salary.component),
additional_salary.amount,
component_type,
additional_salary
additional_salary,
is_recurring = additional_salary.is_recurring
)
def add_tax_components(self, payroll_period):
@ -651,7 +652,7 @@ class SalarySlip(TransactionBase):
tax_row = get_salary_component_data(d)
self.update_component_row(tax_row, tax_amount, "deductions")
def update_component_row(self, component_data, amount, component_type, additional_salary=None):
def update_component_row(self, component_data, amount, component_type, additional_salary=None, is_recurring = 0):
component_row = None
for d in self.get(component_type):
if d.salary_component != component_data.salary_component:
@ -698,6 +699,8 @@ class SalarySlip(TransactionBase):
else:
component_row.default_amount = 0
component_row.additional_amount = amount
component_row.is_recurring_additional_salary = is_recurring
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
@ -894,25 +897,33 @@ class SalarySlip(TransactionBase):
amount, additional_amount = earning.default_amount, earning.additional_amount
if earning.is_tax_applicable:
if additional_amount:
if earning.is_flexible_benefit:
flexi_benefits += amount
else:
taxable_earnings += (amount - additional_amount)
additional_income += additional_amount
# Get additional amount based on future recurring additional salary
if additional_amount and earning.is_recurring_additional_salary:
additional_income += self.get_future_recurring_additional_amount(earning.additional_salary,
earning.additional_amount) # Used earning.additional_amount to consider the amount for the full month
if earning.deduct_full_tax_on_selected_payroll_date:
additional_income_with_full_tax += additional_amount
continue
if earning.is_flexible_benefit:
flexi_benefits += amount
else:
taxable_earnings += amount
if allow_tax_exemption:
for ded in self.deductions:
if ded.exempted_from_income_tax:
amount = ded.amount
amount, additional_amount = ded.amount, ded.additional_amount
if based_on_payment_days:
amount = self.get_amount_based_on_payment_days(ded, joining_date, relieving_date)[0]
taxable_earnings -= flt(amount)
amount, additional_amount = self.get_amount_based_on_payment_days(ded, joining_date, relieving_date)
taxable_earnings -= flt(amount - additional_amount)
additional_income -= additional_amount
if additional_amount and ded.is_recurring_additional_salary:
additional_income -= self.get_future_recurring_additional_amount(ded.additional_salary,
ded.additional_amount) # Used ded.additional_amount to consider the amount for the full month
return frappe._dict({
"taxable_earnings": taxable_earnings,
@ -921,11 +932,21 @@ class SalarySlip(TransactionBase):
"flexi_benefits": flexi_benefits
})
def get_future_recurring_additional_amount(self, additional_salary, monthly_additional_amount):
future_recurring_additional_amount = 0
to_date = frappe.db.get_value("Additional Salary", additional_salary, 'to_date')
# future month count excluding current
future_recurring_period = (getdate(to_date).month - getdate(self.start_date).month)
if future_recurring_period > 0:
future_recurring_additional_amount = monthly_additional_amount * future_recurring_period # Used earning.additional_amount to consider the amount for the full month
return future_recurring_additional_amount
def get_amount_based_on_payment_days(self, row, joining_date, relieving_date):
amount, additional_amount = row.amount, row.additional_amount
if (self.salary_structure and
cint(row.depends_on_payment_days) and cint(self.total_working_days) and
(not self.salary_slip_based_on_timesheet or
cint(row.depends_on_payment_days) and cint(self.total_working_days)
and not (row.additional_salary and row.default_amount) # to identify overwritten additional salary
and (not self.salary_slip_based_on_timesheet or
getdate(self.start_date) < joining_date or
(relieving_date and getdate(self.end_date) > relieving_date)
)):

69
erpnext/payroll/doctype/salary_slip/test_salary_slip.py

@ -536,6 +536,61 @@ class TestSalarySlip(unittest.TestCase):
# undelete fixture data
frappe.db.rollback()
def test_tax_for_recurring_additional_salary(self):
frappe.db.sql("""delete from `tabPayroll Period`""")
frappe.db.sql("""delete from `tabSalary Component`""")
payroll_period = create_payroll_period()
create_tax_slab(payroll_period, allow_tax_exemption=True)
employee = make_employee("test_tax@salary.slip")
delete_docs = [
"Salary Slip",
"Additional Salary",
"Employee Tax Exemption Declaration",
"Employee Tax Exemption Proof Submission",
"Employee Benefit Claim",
"Salary Structure Assignment"
]
for doc in delete_docs:
frappe.db.sql("delete from `tab%s` where employee='%s'" % (doc, employee))
from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure
salary_structure = make_salary_structure("Stucture to test tax", "Monthly",
other_details={"max_benefits": 100000}, test_tax=True,
employee=employee, payroll_period=payroll_period)
create_salary_slips_for_payroll_period(employee, salary_structure.name,
payroll_period, deduct_random=False, num=3)
tax_paid = get_tax_paid_in_period(employee)
annual_tax = 23196.0
self.assertEqual(tax_paid, annual_tax)
frappe.db.sql("""delete from `tabSalary Slip` where employee=%s""", (employee))
#------------------------------------
# Recurring additional salary
start_date = add_months(payroll_period.start_date, 3)
end_date = add_months(payroll_period.start_date, 5)
create_recurring_additional_salary(employee, "Performance Bonus", 20000, start_date, end_date)
frappe.db.sql("""delete from `tabSalary Slip` where employee=%s""", (employee))
create_salary_slips_for_payroll_period(employee, salary_structure.name,
payroll_period, deduct_random=False, num=4)
tax_paid = get_tax_paid_in_period(employee)
annual_tax = 32315.0
self.assertEqual(tax_paid, annual_tax)
frappe.db.rollback()
def make_activity_for_employee(self):
activity_type = frappe.get_doc("Activity Type", "_Test Activity Type")
activity_type.billing_rate = 50
@ -1007,3 +1062,17 @@ def make_salary_slip_for_payment_days_dependency_test(employee, salary_structure
salary_slip = frappe.get_doc("Salary Slip", salary_slip_name)
return salary_slip
def create_recurring_additional_salary(employee, salary_component, amount, from_date, to_date, company=None):
frappe.get_doc({
"doctype": "Additional Salary",
"employee": employee,
"company": company or erpnext.get_default_company(),
"salary_component": salary_component,
"is_recurring": 1,
"from_date": from_date,
"to_date": to_date,
"amount": amount,
"type": "Earning",
"currency": erpnext.get_default_currency()
}).submit()

Loading…
Cancel
Save