Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Tax for recurring additional salary (backport #27459) #28024

Merged
merged 2 commits into from
Oct 20, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions erpnext/hr/doctype/employee/test_employee.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down
41 changes: 21 additions & 20 deletions erpnext/payroll/doctype/additional_salary/additional_salary.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,27 +125,28 @@ def get_amount(self, sal_start_date, sal_end_date):
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 = []
Expand Down
13 changes: 11 additions & 2 deletions erpnext/payroll/doctype/salary_detail/salary_detail.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
48 changes: 34 additions & 14 deletions erpnext/payroll/doctype/salary_slip/salary_slip.py
Original file line number Diff line number Diff line change
Expand Up @@ -630,7 +630,8 @@ def add_additional_salary_components(self, component_type):
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):
Expand All @@ -651,7 +652,7 @@ def add_tax_components(self, payroll_period):
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:
Expand Down Expand Up @@ -702,6 +703,7 @@ def update_component_row(self, component_data, amount, component_type, additiona
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
Expand Down Expand Up @@ -898,25 +900,33 @@ def get_taxable_earnings(self, allow_tax_exemption=False, based_on_payment_days=
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,
Expand All @@ -925,11 +935,21 @@ def get_taxable_earnings(self, allow_tax_exemption=False, based_on_payment_days=
"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)
)):
Expand Down
71 changes: 70 additions & 1 deletion erpnext/payroll/doctype/salary_slip/test_salary_slip.py
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,61 @@ def test_tax_for_payroll_period(self):
# 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
Expand Down Expand Up @@ -1010,4 +1065,18 @@ def make_salary_slip_for_payment_days_dependency_test(employee, salary_structure
else:
salary_slip = frappe.get_doc("Salary Slip", salary_slip_name)

return salary_slip
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()