From bd0a91f36ee5229359dd5c0485961a377cf9da16 Mon Sep 17 00:00:00 2001 From: Kristen Marie Kulha Date: Thu, 24 May 2018 13:54:02 -0700 Subject: [PATCH] Initial commit of `l10n_us_ny_hr_payroll` for 11.0. --- l10n_us_ny_hr_payroll/__init__.py | 1 + l10n_us_ny_hr_payroll/__manifest__.py | 29 ++ l10n_us_ny_hr_payroll/data/base.xml | 78 ++++ l10n_us_ny_hr_payroll/data/final.xml | 21 ++ l10n_us_ny_hr_payroll/data/rules_2018.xml | 350 ++++++++++++++++++ l10n_us_ny_hr_payroll/hr_payroll.py | 57 +++ l10n_us_ny_hr_payroll/hr_payroll_view.xml | 37 ++ l10n_us_ny_hr_payroll/tests/__init__.py | 1 + .../tests/test_us_ny_payslip_2018.py | 167 +++++++++ 9 files changed, 741 insertions(+) create mode 100755 l10n_us_ny_hr_payroll/__init__.py create mode 100755 l10n_us_ny_hr_payroll/__manifest__.py create mode 100755 l10n_us_ny_hr_payroll/data/base.xml create mode 100755 l10n_us_ny_hr_payroll/data/final.xml create mode 100755 l10n_us_ny_hr_payroll/data/rules_2018.xml create mode 100755 l10n_us_ny_hr_payroll/hr_payroll.py create mode 100755 l10n_us_ny_hr_payroll/hr_payroll_view.xml create mode 100755 l10n_us_ny_hr_payroll/tests/__init__.py create mode 100755 l10n_us_ny_hr_payroll/tests/test_us_ny_payslip_2018.py diff --git a/l10n_us_ny_hr_payroll/__init__.py b/l10n_us_ny_hr_payroll/__init__.py new file mode 100755 index 00000000..e99aa24a --- /dev/null +++ b/l10n_us_ny_hr_payroll/__init__.py @@ -0,0 +1 @@ +from . import hr_payroll diff --git a/l10n_us_ny_hr_payroll/__manifest__.py b/l10n_us_ny_hr_payroll/__manifest__.py new file mode 100755 index 00000000..79b2f871 --- /dev/null +++ b/l10n_us_ny_hr_payroll/__manifest__.py @@ -0,0 +1,29 @@ +{ + 'name': 'USA - New York - Payroll', + 'author': 'Hibou Corp. ', + 'license': 'AGPL-3', + 'category': 'Localization', + 'depends': ['l10n_us_hr_payroll'], + 'version': '11.0.2018.0.0', + 'description': """ +USA::New York Payroll Rules. +============================== + +* New Jersey Department of Taxation and Finance partner +* Contribution register and partner for New York State Department of Taxation and Finance +* Company level New York Unemployment Rate +* Company Level New York Re-employment Service Fund +* Company level New York Metropolitan Commuter Transportation Mobility Tax +* Contract level New York Income Tax + """, + + 'auto_install': False, + 'website': 'https://hibou.io/', + 'data': [ + 'hr_payroll_view.xml', + 'data/base.xml', + 'data/rules_2018.xml', + 'data/final.xml', + ], + 'installable': True +} diff --git a/l10n_us_ny_hr_payroll/data/base.xml b/l10n_us_ny_hr_payroll/data/base.xml new file mode 100755 index 00000000..ac14d74b --- /dev/null +++ b/l10n_us_ny_hr_payroll/data/base.xml @@ -0,0 +1,78 @@ + + + + + + New York State Department of Taxation and Finance - Unemployment Insurance Tax + 1 + + + + New York State Department of Taxation and Finance - Income Tax Withholding + 1 + + + + New York State Department of Taxation and Finance - Re-employment Service Fund + 1 + + + + New York State Department of Taxation and Finance - Metropolitan Commuter Transportation Mobility Tax + 1 + + + + + New York Unemployment Insurance Tax + New York State Department of Taxation and Finance - Unemployment Insurance Tax + + + + New York Income Tax Withholding + New York State Department of Taxation and Finance - Income Tax Withholding + + + + Re-employment Service Fund + New York State Department of Taxation and Finance - Re-employment Service Fund + + + + Metropolitan Commuter Transportation Mobility Tax + New York State Department of Taxation and Finance - Metropolitan Commuter Transportation Mobility Tax + + + + + + + New York Unemployment Insurance Tax - Wages + NY_UNEMP_WAGES + + + + New York Unemployment Insurance Tax + NY_UNEMP + + + + + New York Re-employment Service Fund + NY_RSF + + + + + New York Metropolitan Commuter Transportation Mobility Tax + NY_MCTMT + + + + + New York Income Withholding + NY_WITHHOLD + + + + \ No newline at end of file diff --git a/l10n_us_ny_hr_payroll/data/final.xml b/l10n_us_ny_hr_payroll/data/final.xml new file mode 100755 index 00000000..c0dd889f --- /dev/null +++ b/l10n_us_ny_hr_payroll/data/final.xml @@ -0,0 +1,21 @@ + + + + + + + US_NY_EMP + USA New York Employee + + + + + + + diff --git a/l10n_us_ny_hr_payroll/data/rules_2018.xml b/l10n_us_ny_hr_payroll/data/rules_2018.xml new file mode 100755 index 00000000..c10ea324 --- /dev/null +++ b/l10n_us_ny_hr_payroll/data/rules_2018.xml @@ -0,0 +1,350 @@ + + + + + + + + + + New York Unemployment Insurance Tax - Wages (2018) + NY_UNEMP_WAGES_2018 + python + result = (payslip.date_to[:4] == '2018') + code + +### +ytd = payslip.sum('NY_UNEMP_WAGES_2018', '2018-01-01', '2019-01-01') +ytd += contract.external_wages +remaining = 11100.00 - ytd +if remaining <= 0.0: + result = 0 +elif remaining < categories.GROSS: + result = remaining +else: + result = categories.GROSS + + + + + + + + New York Unemployment Insurance Tax(2018) + NY_UNEMP_2018 + python + result = (payslip.date_to[:4] == '2018') + code + +result_rate = -contract.ny_unemp_rate(2018) +result = categories.NY_UNEMP_WAGES + +# result_rate of 0 implies 100% due to bug +if result_rate == 0.0: + result = 0.0 + + + + + + + + + + New York Re-employment Service Fund(2018) + NY_RSF_2018 + python + result = (payslip.date_to[:4] == '2018') + code + +result_rate = -contract.ny_rsf_rate(2018) +result = categories.NY_UNEMP_WAGES + +# result_rate of 0 implies 100% due to bug +if result_rate == 0.0: + result = 0.0 + + + + + + + + + + New York Metropolitan Commuter Transportation Mobility Tax(2018) + NY_MCTMT_2018 + python + result = (payslip.date_to[:4] == '2018') + code + +result_rate = -contract.ny_mctmt_rate(2018) +result = categories.MCTMT_WAGES + +# result_rate of 0 implies 100% due to bug +if result_rate == 0.0: + result = 0.0 + + + + + + + + + + New York Income Withholding + NY_INC_WITHHOLD_2018 + python + result = (payslip.date_to[:4] == '2018') + code + +wages = categories.GROSS +allowances = contract.ny_it2104_allowances +additional_withholding = contract.ny_additional_withholding +schedule_pay = contract.schedule_pay +filing_status = contract.ny_it2104_filing_status + +if filing_status == 'exempt': + result = 0.0 + +# Tables are found in https://www.tax.ny.gov/pdf/publications/withholding/nys50_t_nys.pdf +# Table A - Combined deduction and exemption allowance (Step 1) +deduction_exemption_table_single = { +'weekly': (142.30, 161.55, 180.80, 200.05, 219.30, 238.55, 257.80, 277.05, 296.30, 315.55, 334.80), +'bi-weekly': (284.60, 323.10, 361.60, 400.10, 438.60, 477.10, 515.60, 544.10, 592.60, 631.10, 669.60), +'semi-monthly': (308.35, 350.0, 391.65, 433.30, 474.95, 516.60, 558.25, 599.90, 641.55, 683.20, 724.85), +'monthly': (616.70, 700, 783.30, 866.60, 949.90, 1033.20, 1116.50, 1199.80, 1283.10, 1366.40, 1449.70), +'annually': (7400, 8400, 9400, 10400, 11400, 12400, 13400, 14400, 15400, 16400, 17400), +} + +deduction_exemption_table_married = { +'weekly': (152.90, 172.15, 191.40, 210.65, 229.90, 249.15, 268.40, 287.65, 306.90, 326.15, 345.40), +'bi-weekly': (305.80, 344.30, 382.80, 421.30, 459.80, 498.30, 536.80, 575.30, 613.80, 652.30, 690.80), +'semi-monthly': (331.25, 372.90, 414.55, 456.20, 497.85, 539.50, 581.15, 622.80, 664.45, 706.10, 747.75), +'monthly': (662.50, 745.80, 829.10, 912.40, 995.70, 1079.00, 1162.30, 1245.60, 1328.90, 1412.20, 1495.50), +'annually': (7950, 8950, 9950, 10950, 11950, 12950, 13950, 14950, 15950, 16950, 17950), +} + +# For greater than 10 exemptions, from tables B and C +over_10_deduction_table = { +'weekly': (142.30, 152.90, 19.25), +'bi-weekly': (284.60, 305.80, 38.50), +'semi-monthly': (308.35, 331.25, 41.65), +'monthly': (616.70, 662.50, 83.30), +'annual': (7400, 7950, 1000), +} + +if allowances > 10: + if filing_status == 'single': + wages -= over_10_deduction_table[schedule_pay][0] + over_10_deduction_table[schedule_pay][2] * allowances + elif filing_status == 'married': + wages -= over_10_deduction_table[schedule_pay][1] + over_10_deduction_table[schedule_pay][2] * allowances + +else: + if filing_status == 'single': + wages -= deduction_exemption_table_single[schedule_pay][allowances] + elif filing_status == 'married': + wages -= deduction_exemption_table_married[schedule_pay][allowances] + +# Tax Rate Tables +#### SINGLE #### +if filing_status == 'single': + if schedule_pay == 'weekly': + tax_rate_table = [ + (163, 0.0400, 0.0), + (225, 0.0450, 6.54), + (267, 0.0525, 9.31), + (412, 0.0590, 11.54), + (1551, 0.0633, 20.04), + (1862, 0.0657, 92.17), + (2070, 0.0758, 112.58), + (3032, 0.0808, 128.38), + (4142, 0.0707, 206.08), + (5104, 0.0856, 284.60), + (20722, 0.0735, 366.90), + (21684, 0.5208, 1514.85), + (float('inf'), 0.0962, 2015.62), + ] + + elif schedule_pay == 'bi-weekly': + tax_rate_table = [ + (327, 0.0400, 0.0), + (450, 0.0450, 13.08), + (535, 0.0525, 18.62), + (823, 0.0590, 23.08), + (3102, 0.0633, 40.08), + (3723, 0.0657, 184.35), + (4140, 0.0758, 225.15), + (6063, 0.0808, 256.77), + (8285, 0.0707, 412.15), + (10208, 0.0856, 569.19), + (41444, 0.0735, 733.81), + (43367, 0.5208, 3029.69), + (float('inf'), 0.0962, 4021.23), + ] + + elif schedule_pay == 'semi-monthly': + tax_rate_table = [ + (354, 0.0400, 0.0), + (488, 0.0450, 14.17), + (579, 0.0525, 20.17), + (892, 0.0590, 25.00), + (3360, 0.0633, 43.42), + (4033, 0.0657, 199.71), + (4485, 0.0758, 243.92), + (6569, 0.0808, 278.17), + (8975, 0.0707, 446.50), + (11058, 0.0856, 616.63), + (44898, 0.0735, 794.96), + (46981, 0.5208, 3282.17), + (float('inf'), 0.0962, 4367.17), + ] + + elif schedule_pay == 'monthly': + tax_rate_table = [ + (708, 0.0400, 0.0), + (975, 0.0450, 28.33), + (1158, 0.0525, 40.33), + (1783, 0.0590, 50.00), + (6721, 0.0633, 86.83), + (8067, 0.0657, 399.42), + (8971, 0.0758, 487.83), + (13138, 0.0808, 556.33), + (17950, 0.0707, 893.00), + (22117, 0.0856, 1233.25), + (89796, 0.0735, 1589.92), + (93963, 0.5208, 6564.33), + (float('inf'), 0.0962, 8734.33), + ] + + elif schedule_pay == 'annually': + tax_rate_table = [ + (8500, 0.0400, 0.0), + (11700, 0.0450, 340.00), + (13900, 0.0525, 484.00), + (21400, 0.0590, 600.00), + (80650, 0.0633, 1042.00), + (96800, 0.0657, 4793.00), + (107650, 0.0758, 5854.00), + (157650, 0.0808, 6676.00), + (215400, 0.0707, 10716.00), + (265400, 0.0856, 14799.00), + (1077550, 0.0735, 19079.00), + (1127550, 0.5208, 78772.00), + (float('inf'), 0.0962, 104812.00), + ] + +#### MARRIED #### +elif filing_status == 'married': + if schedule_pay == 'weekly': + tax_rate_table = [ + (163, 0.0400, 0.0), + (225, 0.0450, 6.54), + (267, 0.0525, 9.31), + (412, 0.0590, 11.54), + (1551, 0.0633, 20.04), + (1862, 0.0657, 92.17), + (2070, 0.0783, 112.58), + (3032, 0.0833, 128.90), + (4068, 0.0785, 209.00), + (6215, 0.0707, 290.37), + (7177, 0.0916, 442.17), + (20722, 0.0735, 530.25), + (41449, 0.0765, 1525.83), + (42411, 0.9454, 3111.42), + (float('inf'), 0.0962, 4020.46), + ] + + elif schedule_pay == 'bi-weekly': + tax_rate_table = [ + (327, 0.0400, 0.0), + (450, 0.0450, 13.08), + (535, 0.0525, 18.62), + (823, 0.0590, 23.08), + (3102, 0.0633, 40.08), + (3723, 0.0657, 184.35), + (4140, 0.0783, 225.15), + (6063, 0.0833, 257.81), + (8137, 0.0785, 418.00), + (12431, 0.0707, 580.73), + (14354, 0.0916, 884.35), + (41444, 0.0735, 1060.50), + (82898, 0.0765, 3051.65), + (84821, 0.9454, 6222.85), + (float('inf'), 0.0962, 8040.92), + ] + + elif schedule_pay == 'semi-monthly': + tax_rate_table = [ + (354, 0.0400, 0.0), + (488, 0.0450, 14.17), + (579, 0.0525, 20.17), + (892, 0.0590, 25.00), + (3360, 0.0633, 43.42), + (4033, 0.0657, 199.71), + (4485, 0.0783, 243.92), + (6569, 0.0833, 279.29), + (8815, 0.0785, 452.83), + (13476, 0.0707, 629.13), + (15550, 0.0916, 958.04), + (44898, 0.0735, 1148.88), + (89806, 0.0765, 3305.96), + (91890, 0.9454, 6741.42), + (float('inf'), 0.0962, 8711.00), + ] + + elif schedule_pay == 'monthly': + tax_rate_table = [ + (708, 0.0400, 0.0), + (975, 0.0450, 28.33), + (1158, 0.0525, 40.33), + (1783, 0.0590, 50.00), + (6721, 0.0633, 86.83), + (8067, 0.0657, 399.42), + (8971, 0.0783, 487.83), + (13138, 0.0833, 558.58), + (17629, 0.0785, 905.67), + (26933, 0.0707, 1258.25), + (31100, 0.0916, 1916.08), + (89796, 0.0735, 2297.75), + (179613, 0.0765, 6611.92), + (183779, 0.9454, 13482.83), + (float('inf'), 0.0962, 17422.00), + ] + + elif schedule_pay == 'annually': + tax_rate_table = [ + (8500, 0.0400, 0.0), + (11700, 0.0450, 340.00), + (13900, 0.0525, 484.00), + (21400, 0.0590, 600.00), + (80650, 0.0633, 1042.00), + (96800, 0.0657, 4793.00), + (107650, 0.0783, 5854.00), + (157650, 0.0833, 6703.00), + (211550, 0.0785, 10868.00), + (323200, 0.0707, 15099.00), + (373200, 0.0916, 22993.00), + (1077550, 0.0735, 27573.00), + (2155350, 0.0765, 79343.00), + (2205350, 0.9454, 161794.00), + (float('inf'), 0.0962, 209064.00), + ] + +over = 0.0 +tax = 0.0 +for row in tax_rate_table: + if wages <= row[0]: + tax = ((wages - over) * row[1]) + row[2] + break + over = row[0] + +tax += additional_withholding +result = -tax + + + + + + diff --git a/l10n_us_ny_hr_payroll/hr_payroll.py b/l10n_us_ny_hr_payroll/hr_payroll.py new file mode 100755 index 00000000..c2c305a6 --- /dev/null +++ b/l10n_us_ny_hr_payroll/hr_payroll.py @@ -0,0 +1,57 @@ +from odoo import models, fields, api + + +class USNYHrContract(models.Model): + _inherit = 'hr.contract' + + ny_it2104_allowances = fields.Integer(string="New York IT-2104 Allowances", + default=0, + help="Allowances claimed on line 1 of IT-2104") + ny_additional_withholding = fields.Integer(string="Additional Withholding", + default=0, + help="Line 3 of IT-2104") + ny_it2104_filing_status = fields.Selection([ + ('exempt', 'Exempt'), + ('single', 'Single'), + ('married', 'Married'), + ], string='NY Filing Status', default='single') + + @api.multi + def ny_unemp_rate(self, year): + self.ensure_one() + if self.futa_type == self.FUTA_TYPE_BASIC: + return 0.0 + + if hasattr(self.employee_id.company_id, 'ny_unemp_rate_' + str(year)): + return self.employee_id.company_id['ny_unemp_rate_' + str(year)] + + raise NotImplemented('Year (' + str(year) + ') Not implemented for US New York.') + + def ny_rsf_rate(self, year): + self.ensure_one() + if self.futa_type == self.FUTA_TYPE_BASIC: + return 0.0 + + if hasattr(self.employee_id.company_id, 'ny_rsf_rate_' + str(year)): + return self.employee_id.company_id['ny_rsf_rate_' + str(year)] + + raise NotImplemented('Year (' + str(year) + ') Not implemented for US New York.') + + def ny_mctmt_rate(self, year): + self.ensure_one() + if self.futa_type == self.FUTA_TYPE_BASIC: + return 0.0 + + if hasattr(self.employee_id.company_id, 'ny_mctmt_rate_' + str(year)): + return self.employee_id.company_id['ny_mctmt_rate_' + str(year)] + + raise NotImplemented('Year (' + str(year) + ') Not implemented for US New York.') + + +class NYCompany(models.Model): + _inherit = 'res.company' + + # Unemployment Rate is default for New Employer. + ny_unemp_rate_2018 = fields.Float(string="New York Unemployment Insurance Tax Rate 2018", default=3.925) + ny_rsf_rate_2018 = fields.Float(string="New York Re-employment Service Fund Rate 2018", default=0.075) + ny_mctmt_rate_2018 = fields.Float(string="New York Metropolitan Commuter Transportation Mobility Tax Rate 2018", default=0.0) diff --git a/l10n_us_ny_hr_payroll/hr_payroll_view.xml b/l10n_us_ny_hr_payroll/hr_payroll_view.xml new file mode 100755 index 00000000..cb291da0 --- /dev/null +++ b/l10n_us_ny_hr_payroll/hr_payroll_view.xml @@ -0,0 +1,37 @@ + + + + + res.company.form + res.company + 64 + + + + + + + + + + + + + hr.contract.form.inherit + hr.contract + 147 + + + + + + + + + + + + + + + diff --git a/l10n_us_ny_hr_payroll/tests/__init__.py b/l10n_us_ny_hr_payroll/tests/__init__.py new file mode 100755 index 00000000..050166dd --- /dev/null +++ b/l10n_us_ny_hr_payroll/tests/__init__.py @@ -0,0 +1 @@ +from . import test_us_ny_payslip_2018 diff --git a/l10n_us_ny_hr_payroll/tests/test_us_ny_payslip_2018.py b/l10n_us_ny_hr_payroll/tests/test_us_ny_payslip_2018.py new file mode 100755 index 00000000..48271fc3 --- /dev/null +++ b/l10n_us_ny_hr_payroll/tests/test_us_ny_payslip_2018.py @@ -0,0 +1,167 @@ +from odoo.addons.l10n_us_hr_payroll.tests.test_us_payslip import TestUsPayslip, process_payslip +from odoo.addons.l10n_us_hr_payroll.l10n_us_hr_payroll import USHrContract + + +class TestUsNYPayslip(TestUsPayslip): + ### + # Taxes and Rates + ### + NY_UNEMP_MAX_WAGE = 11100 + + # Examples from http://www.edd.ca.gov/pdf_pub_ctr/18methb.pdf + def test_single_example1(self): + salary = 400 + schedule_pay = 'weekly' + allowances = 3 + additional_withholding = 0 + + wh = -8.20 + + employee = self._createEmployee() + employee.company_id.ny_unemp_rate_2018 = 0.825 + employee.company_id.ny_rsf_rate_2018 = 0.075 + employee.company_id.ny_mctmt_rate_2018 = 0.0 + + contract = self._createContract(employee, + salary, + struct_id=self.ref( + 'l10n_us_ny_hr_payroll.hr_payroll_salary_structure_us_ny_employee'), + schedule_pay=schedule_pay) + contract.ny_it2104_allowances = allowances + contract.ny_additional_withholding = additional_withholding + contract.ny_it2104_filing_status = 'single' + + self.assertEqual(contract.schedule_pay, 'weekly') + + # tax rates + ny_unemp = contract.ny_unemp_rate(2018) / -100.0 + ny_rsf = contract.ny_rsf_rate(2018) / -100.0 + ny_mctmt = contract.ny_mctmt_rate(2018) / -100.0 + + self._log('2018 New York tax first payslip:') + payslip = self._createPayslip(employee, '2018-01-01', '2018-01-31') + + payslip.compute_sheet() + + cats = self._getCategories(payslip) + + self.assertPayrollEqual(cats['NY_UNEMP_WAGES'], salary) + self.assertPayrollEqual(cats['NY_UNEMP'], cats['NY_UNEMP_WAGES'] * ny_unemp) + self.assertPayrollEqual(cats['NY_RSF'], cats['NY_UNEMP_WAGES'] * ny_rsf) + self.assertPayrollEqual(cats['NY_MCTMT'], cats['NY_UNEMP_WAGES'] * ny_mctmt) + self.assertPayrollEqual(cats['NY_WITHHOLD'], wh) + + process_payslip(payslip) + + # Make a new payslip, this one will have maximums + + remaining_ny_unemp_wages = self.NY_UNEMP_MAX_WAGE - salary if (self.NY_UNEMP_MAX_WAGE - 2 * salary < salary) \ + else salary + + self._log('2018 New York tax second payslip:') + payslip = self._createPayslip(employee, '2018-02-01', '2018-02-28') + + payslip.compute_sheet() + + cats = self._getCategories(payslip) + + self.assertPayrollEqual(cats['NY_UNEMP_WAGES'], remaining_ny_unemp_wages) + self.assertPayrollEqual(cats['NY_UNEMP'], remaining_ny_unemp_wages * ny_unemp) + + def test_single_example2(self): + salary = 5000 + schedule_pay = 'semi-monthly' + allowances = 3 + additional_withholding = 0 + + wh = -284.19 + + employee = self._createEmployee() + employee.company_id.ny_unemp_rate_2018 = 0.825 + employee.company_id.ny_rsf_rate_2018 = 0.075 + employee.company_id.ny_mctmt_rate_2018 = 0.0 + + contract = self._createContract(employee, + salary, + struct_id=self.ref( + 'l10n_us_ny_hr_payroll.hr_payroll_salary_structure_us_ny_employee'), + schedule_pay=schedule_pay) + contract.ny_it2104_allowances = allowances + contract.ny_additional_withholding = additional_withholding + contract.ny_it2104_filing_status = 'married' + + self.assertEqual(contract.schedule_pay, 'semi-monthly') + + # tax rates + ny_unemp = contract.ny_unemp_rate(2018) / -100.0 + ny_rsf = contract.ny_rsf_rate(2018) / -100.0 + ny_mctmt = contract.ny_mctmt_rate(2018) / -100.0 + + self._log('2018 New York tax first payslip:') + payslip = self._createPayslip(employee, '2018-01-01', '2018-01-31') + + payslip.compute_sheet() + + cats = self._getCategories(payslip) + + self.assertPayrollEqual(cats['NY_UNEMP_WAGES'], salary) + self.assertPayrollEqual(cats['NY_UNEMP'], cats['NY_UNEMP_WAGES'] * ny_unemp) + self.assertPayrollEqual(cats['NY_RSF'], cats['NY_UNEMP_WAGES'] * ny_rsf) + self.assertPayrollEqual(cats['NY_MCTMT'], cats['NY_UNEMP_WAGES'] * ny_mctmt) + self.assertPayrollEqual(cats['NY_WITHHOLD'], wh) + + process_payslip(payslip) + + def test_single_example3(self): + salary = 50000 + schedule_pay = 'monthly' + allowances = 3 + additional_withholding = 0 + + wh = -3575.63 + + employee = self._createEmployee() + employee.company_id.ny_unemp_rate_2018 = 0.825 + employee.company_id.ny_rsf_rate_2018 = 0.075 + employee.company_id.ny_mctmt_rate_2018 = 0.0 + + contract = self._createContract(employee, + salary, + struct_id=self.ref( + 'l10n_us_ny_hr_payroll.hr_payroll_salary_structure_us_ny_employee'), + schedule_pay=schedule_pay) + contract.ny_it2104_allowances = allowances + contract.ny_additional_withholding = additional_withholding + contract.ny_it2104_filing_status = 'single' + + self.assertEqual(contract.schedule_pay, 'monthly') + + # tax rates + ny_unemp = contract.ny_unemp_rate(2018) / -100.0 + ny_rsf = contract.ny_rsf_rate(2018) / -100.0 + ny_mctmt = contract.ny_mctmt_rate(2018) / -100.0 + + self._log('2018 New York tax first payslip:') + payslip = self._createPayslip(employee, '2018-01-01', '2018-01-31') + + payslip.compute_sheet() + + cats = self._getCategories(payslip) + + self.assertPayrollEqual(cats['NY_WITHHOLD'], wh) + + def test_tax_exempt(self): + schedule_pay = 'monthly' + + wh = 0.0 + + employee = self._createEmployee() + + contract = self._createContract(employee, + salary, + struct_id=self.ref( + 'l10n_us_ny_hr_payroll.hr_payroll_salary_structure_us_ny_employee'), + schedule_pay=schedule_pay) + contract.ny_it2104_filing_status = 'exempt' + + self.assertPayrollEqual(cats['NY_WITHHOLD'], wh)