From dff2ba72d3a875773ee7818532663d5751f60e5f Mon Sep 17 00:00:00 2001 From: Ranjith Kurungadam Date: Thu, 19 Jul 2018 14:34:05 +0530 Subject: [PATCH] test TDS calculation (#14919) * test TDS calculation * fix failing test cases * fix codacy --- ...test_employee_tax_exemption_declaration.py | 15 +- .../payroll_entry/test_payroll_entry.py | 4 +- .../doctype/salary_slip/test_salary_slip.py | 263 +++++++++++++----- .../salary_structure/test_salary_structure.py | 22 +- .../doctype/timesheet/test_timesheet.py | 8 +- 5 files changed, 221 insertions(+), 91 deletions(-) diff --git a/erpnext/hr/doctype/employee_tax_exemption_declaration/test_employee_tax_exemption_declaration.py b/erpnext/hr/doctype/employee_tax_exemption_declaration/test_employee_tax_exemption_declaration.py index 64138e5f79..beaddd98dd 100644 --- a/erpnext/hr/doctype/employee_tax_exemption_declaration/test_employee_tax_exemption_declaration.py +++ b/erpnext/hr/doctype/employee_tax_exemption_declaration/test_employee_tax_exemption_declaration.py @@ -3,9 +3,9 @@ # See license.txt from __future__ import unicode_literals -import frappe +import frappe, erpnext import unittest -from erpnext.hr.doctype.salary_structure.test_salary_structure import make_employee +from erpnext.hr.doctype.employee.test_employee import make_employee class TestEmployeeTaxExemptionDeclaration(unittest.TestCase): def setUp(self): @@ -39,7 +39,7 @@ class TestEmployeeTaxExemptionDeclaration(unittest.TestCase): declaration = frappe.get_doc({ "doctype": "Employee Tax Exemption Declaration", "employee": frappe.get_value("Employee", {"user_id":"employee@taxexepmtion.com"}, "name"), - "company": "_Test Company", + "company": erpnext.get_default_company(), "payroll_period": "_Test Payroll Period", "declarations": [dict(exemption_sub_category = "_Test Sub Category", exemption_category = "_Test Category", @@ -55,7 +55,7 @@ class TestEmployeeTaxExemptionDeclaration(unittest.TestCase): declaration = frappe.get_doc({ "doctype": "Employee Tax Exemption Declaration", "employee": frappe.get_value("Employee", {"user_id":"employee@taxexepmtion.com"}, "name"), - "company": "_Test Company", + "company": erpnext.get_default_company(), "payroll_period": "_Test Payroll Period", "declarations": [dict(exemption_sub_category = "_Test Sub Category", exemption_category = "_Test Category", @@ -70,7 +70,7 @@ class TestEmployeeTaxExemptionDeclaration(unittest.TestCase): duplicate_declaration = frappe.get_doc({ "doctype": "Employee Tax Exemption Declaration", "employee": frappe.get_value("Employee", {"user_id":"employee@taxexepmtion.com"}, "name"), - "company": "_Test Company", + "company": erpnext.get_default_company(), "payroll_period": "_Test Payroll Period", "declarations": [dict(exemption_sub_category = "_Test Sub Category", exemption_category = "_Test Category", @@ -87,10 +87,13 @@ def create_payroll_period(): payroll_period = frappe.get_doc(dict( doctype = 'Payroll Period', name = "_Test Payroll Period", - company = "_Test Company", + company = erpnext.get_default_company(), start_date = date(date.today().year, 1, 1), end_date = date(date.today().year, 12, 31) )).insert() + return payroll_period + else: + return frappe.get_doc("Payroll Period", "_Test Payroll Period") def create_exemption_category(): if not frappe.db.exists("Employee Tax Exemption Category", "_Test Category"): diff --git a/erpnext/hr/doctype/payroll_entry/test_payroll_entry.py b/erpnext/hr/doctype/payroll_entry/test_payroll_entry.py index 7ff5a459f5..b3df2dc787 100644 --- a/erpnext/hr/doctype/payroll_entry/test_payroll_entry.py +++ b/erpnext/hr/doctype/payroll_entry/test_payroll_entry.py @@ -18,8 +18,8 @@ class TestPayrollEntry(unittest.TestCase): for dt in ["Salary Slip", "Salary Component", "Salary Component Account", "Payroll Entry", "Loan"]: frappe.db.sql("delete from `tab%s`" % dt) - make_earning_salary_component(["Basic Salary", "Special Allowance", "HRA", "Leave Encashment"]) - make_deduction_salary_component(["Professional Tax", "TDS"]) + make_earning_salary_component(setup=True) + make_deduction_salary_component(setup=True) def test_payroll_entry(self): # pylint: disable=no-self-use company = erpnext.get_default_company() diff --git a/erpnext/hr/doctype/salary_slip/test_salary_slip.py b/erpnext/hr/doctype/salary_slip/test_salary_slip.py index b856487b8d..f4dbec7a0b 100644 --- a/erpnext/hr/doctype/salary_slip/test_salary_slip.py +++ b/erpnext/hr/doctype/salary_slip/test_salary_slip.py @@ -6,18 +6,19 @@ import unittest import frappe import erpnext import calendar +import random from erpnext.accounts.utils import get_fiscal_year from frappe.utils.make_random import get_random -from frappe.utils import getdate, nowdate, add_days, add_months, flt +from frappe.utils import getdate, nowdate, add_days, add_months, flt, get_first_day, get_last_day from erpnext.hr.doctype.salary_structure.salary_structure import make_salary_slip from erpnext.hr.doctype.payroll_entry.payroll_entry import get_month_details from erpnext.hr.doctype.employee.test_employee import make_employee - +from erpnext.hr.doctype.employee_tax_exemption_declaration.test_employee_tax_exemption_declaration import create_payroll_period, create_exemption_category class TestSalarySlip(unittest.TestCase): def setUp(self): - make_earning_salary_component(["Basic Salary", "Special Allowance", "HRA"]) - make_deduction_salary_component(["Professional Tax", "TDS"]) + make_earning_salary_component(setup=True) + make_deduction_salary_component(setup=True) for dt in ["Leave Application", "Leave Allocation", "Salary Slip"]: frappe.db.sql("delete from `tab%s`" % dt) @@ -164,6 +165,61 @@ class TestSalarySlip(unittest.TestCase): elif payroll_frequncy == "Daily": self.assertEqual(ss.end_date, nowdate()) + def test_tax_for_payroll_period(self): + data = {} + # test the impact of tax exemption declaration, tax exemption proof submission and deduct check boxes in annual tax calculation + # as per assigned salary structure 40500 in monthly salary so 236000*5/100/12 + frappe.db.sql("""delete from `tabPayroll Period`""") + frappe.db.sql("""delete from `tabSalary Component`""") + payroll_period = create_payroll_period() + create_tax_slab(payroll_period) + employee = make_employee("test_tax@salary.slip") + frappe.db.sql("""delete from `tabSalary Slip` where employee=%s""", (employee)) + frappe.db.sql("""delete from `tabEmployee Tax Exemption Declaration` where employee=%s""", (employee)) + frappe.db.sql("""delete from `tabEmployee Tax Exemption Proof Submission` where employee=%s""", (employee)) + from erpnext.hr.doctype.salary_structure.test_salary_structure import make_salary_structure, create_salary_structure_assignment + salary_structure = make_salary_structure("Stucture to test tax", "Monthly", test_tax=True) + create_salary_structure_assignment(employee, salary_structure.name, payroll_period.start_date) + + # create salary slip for whole period deducting tax only on last period to find the total tax amount paid + create_salary_slips_for_payroll_period(employee, salary_structure.name, payroll_period) + tax_paid_amount = frappe.db.sql("""select sum(sd.amount) from `tabSalary Detail` sd join `tabSalary Slip` ss where + ss.name=sd.parent and ss.employee=%s and ss.docstatus=1 and sd.salary_component='TDS'""", (employee)) + + # total taxable income 236000, at 5% tax slab + annual_tax = 11800 + self.assertEqual(tax_paid_amount[0][0], annual_tax) + frappe.db.sql("""delete from `tabSalary Slip` where employee=%s""", (employee)) + + # create exemption declaration so the tax amount varies + create_exemption_declaration(employee, payroll_period.name) + + # create for payroll deducting in random months + data["deducted_dates"] = create_salary_slips_for_payroll_period(employee, salary_structure.name, payroll_period, deduct_random=True) + tax_paid_amount = frappe.db.sql("""select sum(sd.amount) from `tabSalary Detail` sd join `tabSalary Slip` ss where + ss.name=sd.parent and ss.employee=%s and ss.docstatus=1 and sd.salary_component='TDS'""", (employee)) + + # No proof sumitted, total tax paid, should not change + try: + self.assertEqual(tax_paid_amount[0][0], annual_tax) + except AssertionError: + print("\nTax calculation failed on following case\n", data, "\n") + raise + + # Submit proof for total 86000 + data["proof"] = [create_proof_submission(employee, payroll_period, 50000), 50000] + data["proof1"] = [create_proof_submission(employee, payroll_period, 36000), 36000] + frappe.db.sql("""delete from `tabSalary Slip` where employee=%s""", (employee)) + data["deducted_dates"] = create_salary_slips_for_payroll_period(employee, salary_structure.name, payroll_period, deduct_random=True) + tax_paid_amount = frappe.db.sql("""select sum(sd.amount) from `tabSalary Detail` sd join `tabSalary Slip` ss where + ss.name=sd.parent and ss.employee=%s and ss.docstatus=1 and sd.salary_component='TDS'""", (employee)) + # total taxable income 150000, at 5% tax slab + try: + self.assertEqual(tax_paid_amount[0][0], 7500) + except AssertionError: + print("\nTax calculation failed on following case\n", data, "\n") + raise + def make_holiday_list(self): fiscal_year = get_fiscal_year(nowdate(), company=erpnext.get_default_company()) if not frappe.db.get_value("Holiday List", "Salary Slip Test Holiday List"): @@ -212,28 +268,22 @@ def make_employee_salary_slip(user, payroll_frequency, salary_structure=None): return salary_slip - -def make_earning_salary_component(salary_components): +def make_salary_component(salary_components, test_tax): for salary_component in salary_components: - if not frappe.db.exists('Salary Component', salary_component): - sal_comp = frappe.get_doc({ - "doctype": "Salary Component", - "salary_component": salary_component, - "type": "Earning" - }) - sal_comp.insert() - get_salary_component_account(salary_component) - -def make_deduction_salary_component(salary_components): - for salary_component in salary_components: - if not frappe.db.exists('Salary Component', salary_component): - sal_comp = frappe.get_doc({ - "doctype": "Salary Component", - "salary_component": salary_component, - "type": "Deduction" - }) - sal_comp.insert() - get_salary_component_account(salary_component) + if not frappe.db.exists('Salary Component', salary_component["salary_component"]): + if test_tax: + if salary_component["type"] == "Earning": + salary_component["is_tax_applicable"] = 1 + elif salary_component["salary_component"] == "TDS": + salary_component["variable_based_on_taxable_salary"] = 1 + salary_component["amount_based_on_formula"] = 0 + salary_component["amount"] = 0 + salary_component["formula"] = "" + salary_component["condition"] = "" + salary_component["doctype"] = "Salary Component" + salary_component["salary_component_abbr"] = salary_component["abbr"] + frappe.get_doc(salary_component).insert() + get_salary_component_account(salary_component["salary_component"]) def get_salary_component_account(sal_comp): company = erpnext.get_default_company() @@ -244,7 +294,6 @@ def get_salary_component_account(sal_comp): }) sal_comp.save() - def create_account(company): salary_account = frappe.db.get_value("Account", "Salary - " + frappe.db.get_value('Company', company, 'abbr')) if not salary_account: @@ -256,64 +305,138 @@ def create_account(company): }).insert() return salary_account - -def get_earnings_component(setup=False): - if setup: - make_earning_salary_component(["Basic Salary", "Special Allowance", "HRA"]) - - return [ - { - "salary_component": 'Basic Salary', - "abbr":'BS', - "condition": 'base > 10000', - "formula": 'base*.5', - "idx": 1 - }, - { +def make_earning_salary_component(setup=False, test_tax=False): + data = [ + { + "salary_component": 'Basic Salary', + "abbr":'BS', + "condition": 'base > 10000', + "formula": 'base*.5', + "type": "Earning" + }, + { + "salary_component": 'HRA', + "abbr":'H', + "amount": 3000, + "type": "Earning" + }, + { + "salary_component": 'Special Allowance', + "abbr":'SA', + "condition": 'H < 10000', + "formula": 'BS*.5', + "type": "Earning" + }, + { + "salary_component": "Leave Encashment", + "abbr": 'LE', + "is_additional_component": 1, + "type": "Earning" + } + ] + if setup or test_tax: + make_salary_component(data, test_tax) + data.append({ "salary_component": 'Basic Salary', "abbr":'BS', "condition": 'base < 10000', "formula": 'base*.2', - "idx": 2 - }, - { - "salary_component": 'HRA', - "abbr":'H', - "amount": 3000, - "idx": 3 - }, - { - "salary_component": 'Special Allowance', - "abbr":'SA', - "condition": 'H < 10000', - "formula": 'BS*.5', - "idx": 4 - }, - ] + "type": "Earning" + }) + return data -def get_deductions_component(setup=False): - if setup: - make_deduction_salary_component(["Professional Tax", "TDS"]) - - return [ +def make_deduction_salary_component(setup=False, test_tax=False): + data = [ { "salary_component": 'Professional Tax', "abbr":'PT', "condition": 'base > 10000', "formula": 'base*.1', - "idx": 1 + "type": "Deduction" }, { "salary_component": 'TDS', "abbr":'T', "formula": 'base*.1', - "idx": 2 - }, - { - "salary_component": 'TDS', - "abbr":'T', - "condition": 'employment_type=="Intern"', - "formula": 'base*.1', - "idx": 3 + "type": "Deduction" } ] + if not test_tax: + data.append({ + "salary_component": 'TDS', + "abbr":'T', + "condition": 'employment_type=="Intern"', + "formula": 'base*.1', + "type": "Deduction" + }) + if setup or test_tax: + make_salary_component(data, test_tax) + + return data + +def create_exemption_declaration(employee, payroll_period): + create_exemption_category() + declaration = frappe.get_doc({"doctype": "Employee Tax Exemption Declaration", + "employee": employee, + "payroll_period": payroll_period, + "company": erpnext.get_default_company()}) + declaration.append("declarations", {"exemption_sub_category": "_Test Sub Category", + "exemption_category": "_Test Category", + "amount": 100000}) + declaration.submit() + +def create_proof_submission(employee, payroll_period, amount): + submission_date = add_months(payroll_period.start_date, random.randint(0, 11)) + proof_submission = frappe.get_doc({"doctype": "Employee Tax Exemption Proof Submission", + "employee": employee, + "payroll_period": payroll_period.name, + "submission_date": submission_date}) + proof_submission.append("tax_exemption_proofs", {"exemption_sub_category": "_Test Sub Category", + "exemption_category": "_Test Category", "type_of_proof": "Test", + "amount": amount}) + proof_submission.submit() + return submission_date + +def create_tax_slab(payroll_period): + data = [{ + "from_amount": 250000, + "to_amount": 500000, + "percent_deduction": 5 + }, + { + "from_amount": 500000, + "to_amount": 1000000, + "percent_deduction": 20 + }, + { + "from_amount": 1000000, + "percent_deduction": 30 + }] + payroll_period.taxable_salary_slabs = [] + for item in data: + payroll_period.append("taxable_salary_slabs", item) + payroll_period.save() + +def create_salary_slips_for_payroll_period(employee, salary_structure, payroll_period, deduct_random=False): + deducted_dates = [] + i = 0 + while i < 12: + slip = frappe.get_doc({"doctype": "Salary Slip", "employee": employee, + "salary_structure": salary_structure, "frequency": "Monthly"}) + if i == 0: + posting_date = add_days(payroll_period.start_date, 25) + else: + posting_date = add_months(posting_date, 1) + if i == 11: + slip.deduct_tax_for_unsubmitted_tax_exemption_proof = 1 + slip.deduct_tax_for_unclaimed_employee_benefits = 1 + if deduct_random and not random.randint(0, 2): + slip.deduct_tax_for_unsubmitted_tax_exemption_proof = 1 + deducted_dates.append(posting_date) + slip.posting_date = posting_date + slip.start_date = get_first_day(posting_date) + slip.end_date = get_last_day(posting_date) + doc = make_salary_slip(salary_structure, slip, employee) + doc.submit() + i += 1 + return deducted_dates diff --git a/erpnext/hr/doctype/salary_structure/test_salary_structure.py b/erpnext/hr/doctype/salary_structure/test_salary_structure.py index 78b16f29bf..1a16db74a5 100644 --- a/erpnext/hr/doctype/salary_structure/test_salary_structure.py +++ b/erpnext/hr/doctype/salary_structure/test_salary_structure.py @@ -8,8 +8,8 @@ import erpnext from frappe.utils.make_random import get_random from frappe.utils import nowdate, add_days, add_years, getdate, add_months from erpnext.hr.doctype.salary_structure.salary_structure import make_salary_slip -from erpnext.hr.doctype.salary_slip.test_salary_slip import get_earnings_component,\ - get_deductions_component, make_employee_salary_slip +from erpnext.hr.doctype.salary_slip.test_salary_slip import make_earning_salary_component,\ + make_deduction_salary_component, make_employee_salary_slip from erpnext.hr.doctype.employee.test_employee import make_employee @@ -34,7 +34,7 @@ class TestSalaryStructure(unittest.TestCase): "from_date": nowdate(), "to_date": add_years(nowdate(), 1), "weekly_off": "Sunday" - }).insert() + }).insert() holiday_list.get_weekly_off_dates() holiday_list.save() @@ -72,14 +72,16 @@ class TestSalaryStructure(unittest.TestCase): self.assertFalse(("\n" in row.formula) or ("\n" in row.condition)) -def make_salary_structure(salary_structure, payroll_frequency, employee=None, dont_submit=False, other_details=None): +def make_salary_structure(salary_structure, payroll_frequency, employee=None, dont_submit=False, other_details=None, test_tax=False): + if test_tax: + frappe.db.sql("""delete from `tabSalary Structure` where name=%s""",(salary_structure)) if not frappe.db.exists('Salary Structure', salary_structure): details = { "doctype": "Salary Structure", "name": salary_structure, "company": erpnext.get_default_company(), - "earnings": get_earnings_component(), - "deductions": get_deductions_component(), + "earnings": make_earning_salary_component(test_tax=test_tax), + "deductions": make_deduction_salary_component(test_tax=test_tax), "payroll_frequency": payroll_frequency, "payment_account": get_random("Account") } @@ -97,14 +99,16 @@ def make_salary_structure(salary_structure, payroll_frequency, employee=None, do return salary_structure_doc -def create_salary_structure_assignment(employee, salary_structure): +def create_salary_structure_assignment(employee, salary_structure, from_date=None): + if frappe.db.exists("Salary Structure Assignment", {"employee": employee}): + frappe.db.sql("""delete from `tabSalary Structure Assignment` where employee=%s""",(employee)) salary_structure_assignment = frappe.new_doc("Salary Structure Assignment") salary_structure_assignment.employee = employee salary_structure_assignment.base = 50000 salary_structure_assignment.variable = 5000 - salary_structure_assignment.from_date = add_months(nowdate(), -1) + salary_structure_assignment.from_date = from_date or add_months(nowdate(), -1) salary_structure_assignment.salary_structure = salary_structure salary_structure_assignment.company = erpnext.get_default_company() salary_structure_assignment.save(ignore_permissions=True) salary_structure_assignment.submit() - return salary_structure_assignment \ No newline at end of file + return salary_structure_assignment diff --git a/erpnext/projects/doctype/timesheet/test_timesheet.py b/erpnext/projects/doctype/timesheet/test_timesheet.py index 1f274dbf55..9f1c58601d 100644 --- a/erpnext/projects/doctype/timesheet/test_timesheet.py +++ b/erpnext/projects/doctype/timesheet/test_timesheet.py @@ -19,10 +19,10 @@ class TestTimesheet(unittest.TestCase): def setUp(self): for dt in ["Salary Slip", "Salary Structure", "Salary Structure Assignment", "Timesheet"]: frappe.db.sql("delete from `tab%s`" % dt) - - from erpnext.hr.doctype.salary_slip.test_salary_slip import make_earning_salary_component - make_earning_salary_component(["Timesheet Component"]) - + + if not frappe.db.exists("Salary Component", "Timesheet Component"): + frappe.get_doc({"doctype": "Salary Component", "salary_component": "Timesheet Component"}).insert() + def test_timesheet_billing_amount(self): make_salary_structure_for_timesheet("_T-Employee-00001")