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>
This commit is contained in:
parent
830fa60acd
commit
2ef4844a3c
@ -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()
|
||||
|
||||
|
@ -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 = []
|
||||
|
@ -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",
|
||||
|
@ -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:
|
||||
taxable_earnings += (amount - additional_amount)
|
||||
additional_income += additional_amount
|
||||
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
|
||||
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
|
||||
|
||||
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)
|
||||
)):
|
||||
|
@ -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…
x
Reference in New Issue
Block a user