From f6beed71652efafc87323ff3a0774bd885cad211 Mon Sep 17 00:00:00 2001 From: Bhoomi Vaishnani Date: Tue, 24 Mar 2020 15:50:48 -0400 Subject: [PATCH] IMP `l10n_us_hr_payroll` Port `l10n_us_ia_hr_payroll` IA Iowa including migration --- l10n_us_hr_payroll/__manifest__.py | 1 + l10n_us_hr_payroll/data/state/ia_iowa.xml | 177 ++++++++++++++++++ l10n_us_hr_payroll/models/hr_payslip.py | 2 + l10n_us_hr_payroll/models/state/ia_iowa.py | 44 +++++ .../models/us_payroll_config.py | 2 + l10n_us_hr_payroll/tests/__init__.py | 3 + .../tests/test_us_ia_iowa_payslip_2019.py | 152 +++++++++++++++ .../tests/test_us_ia_iowa_payslip_2020.py | 33 ++++ .../views/us_payroll_config_views.xml | 6 + 9 files changed, 420 insertions(+) create mode 100644 l10n_us_hr_payroll/data/state/ia_iowa.xml create mode 100644 l10n_us_hr_payroll/models/state/ia_iowa.py create mode 100644 l10n_us_hr_payroll/tests/test_us_ia_iowa_payslip_2019.py create mode 100755 l10n_us_hr_payroll/tests/test_us_ia_iowa_payslip_2020.py diff --git a/l10n_us_hr_payroll/__manifest__.py b/l10n_us_hr_payroll/__manifest__.py index 1a3214ff..d04c4772 100644 --- a/l10n_us_hr_payroll/__manifest__.py +++ b/l10n_us_hr_payroll/__manifest__.py @@ -36,6 +36,7 @@ United States of America - Payroll Rules. 'data/state/fl_florida.xml', 'data/state/ga_georgia.xml', 'data/state/hi_hawaii.xml', + 'data/state/ia_iowa.xml', 'data/state/id_idaho.xml', 'data/state/il_illinois.xml', 'data/state/mi_michigan.xml', diff --git a/l10n_us_hr_payroll/data/state/ia_iowa.xml b/l10n_us_hr_payroll/data/state/ia_iowa.xml new file mode 100644 index 00000000..6a7d060d --- /dev/null +++ b/l10n_us_hr_payroll/data/state/ia_iowa.xml @@ -0,0 +1,177 @@ + + + + + US IA Iowa SUTA Wage Base + us_ia_suta_wage_base + + + + + 30600.0 + + + + + 31600.0 + + + + + + + + US IA Iowa SUTA Rate + us_ia_suta_rate + + + + + 1.0 + + + + + 1.0 + + + + + + + US IA Iowa SIT Tax Rate + us_ia_sit_tax_rate + + + + + { + 'daily': [(5.13, 0.0033, 0.0), (10.25, 0.0067, 0.02), (20.50, 0.0225, 0.05), (46.13, 0.0414, 0.28), (76.89, 0.0563, 1.34), (102.52, 0.0596, 3.07), (153.78, 0.0625, 4.60), (230.68, 0.0744, 7.80), ('inf', 0.0853, 13.52)], + 'weekly': [(25.63, 0.0033, 0.0), (51.27, 0.0067, 0.08), (102.52, 0.0225, 0.025), (230.67, 0.0414, 1.40), (384.46, 0.0563, 6.71), (512.62, 0.0596, 15.37), (768.92, 0.0625, 23.01), (1153.38, 0.0744, 39.03), ('inf', 0.0853, 67.63)], + 'bi-weekly': [(51.27, 0.0033, 0.00), (102.54, 0.0067, 0.17), (205.04, 0.00225, 0.51), (461.35, 0.0414, 2.82), (768.92, 0.0563, 13.43), (1025.23, 0.0596, 30.75), (1537.85, 0.0625, 46.03), (2306.77, 0.0744, 78.07), ('inf', 0.0853, 135.28)], + 'semi-monthly': [(55.54, 0.0033, 0.00), (111.08, 0.0067, 0.18), (222.13, 0.0225, 0.55), (499.79, 0.0414, 3.05), (833.00, 0.0563, 14.59), (1110.67, 0.0596, 33.31), (1666.00, 0.0625, 49.86), (2499.00, 0.0744, 84.57), ('inf', 0.0853, 146.55)], + 'monthly': [(111.08, 0.0033, 0.00), (222.17, 0.0067, 0.37), (444.25, 0.0225, 1.11), (999.58, 0.0414, 6.11), (1666.00, 0.0563, 29.10), (2221.33, 0.0596, 62.66), (3332.00, 0.0625, 99.72), (4998.00, 0.0744, 169.14), ('inf', 0.0853, 293.09)], + 'annual': [(1333.00, 0.0033, 0.00), (2666.00, 0.0067, 4.40), (5331.00, 0.0225, 13.33), (11995.00, 0.0414, 73.29), (19992.00, 0.0563, 349.19), (26656.00, 0.0596, 799.41), (39984.00, 0.0625, 1196.58), (59976.00, 0.0744, 2029.58), ('inf', 0.0853, 3516.98)], + } + + + + + { + 'daily': [(5.69, 0.0033, 0.0), (11.38, 0.0067, 0.02), (22.76, 0.0225, 0.06), (51.22, 0.0414, 0.32), (85.36, 0.0563, 1.50), (113.81, 0.0596, 3.42), (170.71, 0.0625, 5.12), (256.07, 0.0744, 8.68), ('inf', 0.0853, 15.03)], + 'weekly': [(28.46, 0.0033, 0.0), (56.90, 0.0067, 0.09), (113.81, 0.0225, 0.028), (256.08, 0.0414, 1.56), (426.79, 0.0563, 7.45), (569.04, 0.0596, 17.06), (853.56, 0.0625, 25.54), (1280.35, 0.0744, 43.32), ('inf', 0.0853, 75.07)], + 'bi-weekly': [(56.92, 0.0033, 0.00), (113.81, 0.0067, 0.19), (227.62, 0.00225, 0.57), (512.15, 0.0414, 3.13), (853.58, 0.0563, 14.91), (1138.08, 0.0596, 34.13), (1707.12, 0.0625, 51.09), (2560.69, 0.0744, 86.66), ('inf', 0.0853, 150.17)], + 'semi-monthly': [(61.67, 0.0033, 0.00), (123.29, 0.0067, 0.20), (246.58, 0.0225, 0.61), (554.83, 0.0414, 3.38), (924.71, 0.0563, 16.14), (1232.92, 0.0596, 36.96), (1849.38, 0.0625, 55.33), (2774.08, 0.0744, 93.86), ('inf', 0.0853, 162.66)], + 'monthly': [(123.33, 0.0033, 0.00), (246.58, 0.0067, 0.41), (493.17, 0.0225, 1.24), (1109.67, 0.0414, 6.79), (1849.42, 0.0563, 32.31), (2465.83, 0.0596, 73.96), (3698.75, 0.0625, 110.70), (5548.17, 0.0744, 187.76), ('inf', 0.0853, 325.36)], + 'annual': [(1480.00, 0.0033, 0.00), (2959.00, 0.0067, 4.88), (5918.00, 0.0225, 14.79), (13316.00, 0.0414, 81.37), (22193.00, 0.0563, 387.65), (29590.00, 0.0596, 887.43), (44385.00, 0.0625, 1328.29), (66578.00, 0.0744, 2252.98), ('inf', 0.0853, 3904.14)], + } + + + + + + + US IA Iowa Standard Deduction Rate + us_ia_sit_standard_deduction_rate + + + + + { + 'daily': ( 6.50, 16.00), + 'weekly': ( 32.50, 80.00), + 'bi-weekly': ( 65.00, 160.00), + 'semi-monthly': ( 70.42, 173.33), + 'monthly': ( 140.83, 346.67), + 'annually': (1690.00, 4160.00), + } + + + + + { + 'daily': ( 7.23, 17.81), + 'weekly': ( 36.15, 89.04), + 'bi-weekly': ( 72.31, 178.08), + 'semi-monthly': ( 78.33, 192.92), + 'monthly': ( 156.67, 385.83), + 'annually': (1880.00, 4630.00), + } + + + + + + + US IA Iowa Deduction Allowance Rate + us_ia_sit_deduction_allowance_rate + + + + + { + 'daily': 0.15, + 'weekly': 0.77, + 'bi-weekly': 1.54, + 'semi-monthly': 1.67, + 'monthly': 3.33, + 'annually': 40.00, + } + + + + + { + 'daily': 0.15, + 'weekly': 0.77, + 'bi-weekly': 1.54, + 'semi-monthly': 1.67, + 'monthly': 3.33, + 'annually': 40.00, + } + + + + + + + + US Iowa - Department of Economic Security (IDES) - Unemployment Tax + + + + US Iowa - Department of Revenue (IDOR) - Income Tax + + + + + + + + + + ER: US IA Iowa State Unemployment + ER_US_IA_SUTA + python + result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_ia_suta_wage_base', rate='us_ia_suta_rate', state_code='IA') + code + result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_ia_suta_wage_base', rate='us_ia_suta_rate', state_code='IA') + + + + + + + + + EE: US IA Iowa State Income Tax Withholding + EE_US_IA_SIT + python + result, _ = ia_iowa_state_income_withholding(payslip, categories, worked_days, inputs) + code + result, result_rate = ia_iowa_state_income_withholding(payslip, categories, worked_days, inputs) + + + + + \ No newline at end of file diff --git a/l10n_us_hr_payroll/models/hr_payslip.py b/l10n_us_hr_payroll/models/hr_payslip.py index c0667415..24bf0548 100644 --- a/l10n_us_hr_payroll/models/hr_payslip.py +++ b/l10n_us_hr_payroll/models/hr_payslip.py @@ -21,6 +21,7 @@ from .state.ct_connecticut import ct_connecticut_state_income_withholding from .state.de_delaware import de_delaware_state_income_withholding from .state.ga_georgia import ga_georgia_state_income_withholding from .state.hi_hawaii import hi_hawaii_state_income_withholding +from .state.ia_iowa import ia_iowa_state_income_withholding from .state.id_idaho import id_idaho_state_income_withholding from .state.il_illinois import il_illinois_state_income_withholding from .state.mi_michigan import mi_michigan_state_income_withholding @@ -74,6 +75,7 @@ class HRPayslip(models.Model): 'de_delaware_state_income_withholding': de_delaware_state_income_withholding, 'ga_georgia_state_income_withholding': ga_georgia_state_income_withholding, 'hi_hawaii_state_income_withholding': hi_hawaii_state_income_withholding, + 'ia_iowa_state_income_withholding': ia_iowa_state_income_withholding, 'id_idaho_state_income_withholding': id_idaho_state_income_withholding, 'il_illinois_state_income_withholding': il_illinois_state_income_withholding, 'mi_michigan_state_income_withholding': mi_michigan_state_income_withholding, diff --git a/l10n_us_hr_payroll/models/state/ia_iowa.py b/l10n_us_hr_payroll/models/state/ia_iowa.py new file mode 100644 index 00000000..d12adc64 --- /dev/null +++ b/l10n_us_hr_payroll/models/state/ia_iowa.py @@ -0,0 +1,44 @@ +# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details. + +from .general import _state_applies, sit_wage + + +def ia_iowa_state_income_withholding(payslip, categories, worked_days, inputs): + """ + Returns SIT eligible wage and rate. + + :return: result, result_rate (wage, percent) + """ + state_code = 'IA' + if not _state_applies(payslip, state_code): + return 0.0, 0.0 + + # Determine Wage + wage = sit_wage(payslip, categories) + if not wage: + return 0.0, 0.0 + + schedule_pay = payslip.contract_id.schedule_pay + fed_withholding = categories.EE_US_941_FIT + additional = payslip.contract_id.us_payroll_config_value('state_income_tax_additional_withholding') + allowances = payslip.contract_id.us_payroll_config_value('ia_w4_sit_allowances') + standard_deduction = payslip.rule_parameter('us_ia_sit_standard_deduction_rate')[schedule_pay] + tax_table = payslip.rule_parameter('us_ia_sit_tax_rate')[schedule_pay] + deduction_per_allowance = payslip.rule_parameter('us_ia_sit_deduction_allowance_rate')[schedule_pay] + + t1 = wage + fed_withholding + t2 = t1 - standard_deduction[0] if allowances < 2 else standard_deduction[1] + t3 = 0.0 + last = 0.0 + for row in tax_table: + cap, rate, flat_fee = row + if float(cap) > float(t2): + taxed_amount = t2 - last + t3 = flat_fee + (rate * taxed_amount) + break + last = cap + withholding = t3 - (deduction_per_allowance * allowances) + + withholding = max(withholding, 0.0) + withholding += additional + return wage, -((withholding / wage) * 100.0) diff --git a/l10n_us_hr_payroll/models/us_payroll_config.py b/l10n_us_hr_payroll/models/us_payroll_config.py index 55c48951..b1aa0699 100644 --- a/l10n_us_hr_payroll/models/us_payroll_config.py +++ b/l10n_us_hr_payroll/models/us_payroll_config.py @@ -112,6 +112,8 @@ class HRContractUSPayrollConfig(models.Model): ], string='Hawaii HW-4 Marital Status', help='HI HW-4 3.') hi_hw4_sit_allowances = fields.Integer(string='Hawaii HW-4 Allowances', help='HI HW-4 4.') + ia_w4_sit_allowances = fields.Integer(string='Iowa W-4 allowances', help='IA W-4 6.') + id_w4_sit_filing_status = fields.Selection([ ('single', 'Single'), ('married', 'Married'), diff --git a/l10n_us_hr_payroll/tests/__init__.py b/l10n_us_hr_payroll/tests/__init__.py index fd80b46d..ffbcfba4 100755 --- a/l10n_us_hr_payroll/tests/__init__.py +++ b/l10n_us_hr_payroll/tests/__init__.py @@ -35,6 +35,9 @@ from . import test_us_ga_georgia_payslip_2020 from . import test_us_hi_hawaii_payslip_2019 from . import test_us_hi_hawaii_payslip_2020 +from . import test_us_ia_iowa_payslip_2019 +from . import test_us_ia_iowa_payslip_2020 + from . import test_us_id_idaho_payslip_2019 from . import test_us_id_idaho_payslip_2020 diff --git a/l10n_us_hr_payroll/tests/test_us_ia_iowa_payslip_2019.py b/l10n_us_hr_payroll/tests/test_us_ia_iowa_payslip_2019.py new file mode 100644 index 00000000..cb3bccfd --- /dev/null +++ b/l10n_us_hr_payroll/tests/test_us_ia_iowa_payslip_2019.py @@ -0,0 +1,152 @@ +# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details. + +from .common import TestUsPayslip, process_payslip + + +class TestUsIAPayslip(TestUsPayslip): + IA_UNEMP_MAX_WAGE = 30600 + IA_UNEMP = -1.0 / 100.0 + IA_INC_TAX = -0.0535 + + def test_taxes_weekly(self): + wages = 30000.00 + schedule_pay = 'weekly' + allowances = 1 + additional_wh = 0.00 + employee = self._createEmployee() + contract = self._createContract(employee, + wage=wages, + state_id=self.get_us_state('IA'), + state_income_tax_additional_withholding=additional_wh, + ia_w4_sit_allowances=allowances, + schedule_pay=schedule_pay) + + self._log('2019 Iowa tax first payslip weekly:') + payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31') + payslip.compute_sheet() + cats = self._getCategories(payslip) + + # T1 is the gross taxable wages for the pay period minus the Federal withholding amount. We add the federal + # withholding amount because it is calculated in the base US payroll module as a negative + # t1 = 30000 - (10399.66) = 19600.34 + t1_to_test = wages + cats['EE_US_941_FIT'] + self.assertPayrollAlmostEqual(t1_to_test, 19600.34) + + # T2 is T1 minus our standard deduction which is a table of flat rates dependent on the number of allowances. + # In our case, we have a weekly period which on the table has a std deduct. of $32.50 for 0 or 1 allowances, + # and 80.00 of 2 or more allowances. + standard_deduction = 32.50 # The allowance tells us what standard_deduction amount to use. + # t2 = 19600.34 - 32.50 = 19567.84 + t2_to_test = t1_to_test - standard_deduction + self.assertPayrollAlmostEqual(t2_to_test, 19567.84) + # T3 is T2 multiplied by the income rates in the large table plus a flat fee for that bracket. + # 1153.38 is the bracket floor. 8.53 is the rate, and 67.63 is the flat fee. + # t3 = 1638.38 + t3_to_test = ((t2_to_test - 1153.38) * (8.53 / 100)) + 67.63 + self.assertPayrollAlmostEqual(t3_to_test, 1638.38) + # T4 is T3 minus a flat amount determined by pay period * the number of deductions. For 2019, our weekly + # deduction amount per allowance is 0.77 + # t4 = 1638.38 - 0.77 = 155.03 + t4_to_test = t3_to_test - (0.77 * allowances) + self.assertPayrollAlmostEqual(t4_to_test, 1637.61) + # t5 is our T4 plus the additional withholding per period + # t5 = 1637.61 + 0.0 + # Convert to negative as well. + t5_to_test = -t4_to_test - additional_wh + self.assertPayrollAlmostEqual(t5_to_test, -1637.61) + + self.assertPayrollEqual(cats['ER_US_SUTA'], wages * self.IA_UNEMP) + self.assertPayrollEqual(cats['EE_US_SIT'], t5_to_test) + + + # Make a new payslip, this one will have maximums + + remaining_IA_UNEMP_wages = self.IA_UNEMP_MAX_WAGE - wages if (self.IA_UNEMP_MAX_WAGE - 2*wages < wages) \ + else wages + + self._log('2019 Iowa tax second payslip weekly:') + payslip = self._createPayslip(employee, '2019-02-01', '2019-02-28') + payslip.compute_sheet() + cats = self._getCategories(payslip) + + self.assertPayrollEqual(cats['ER_US_SUTA'], wages * self.IA_UNEMP) + + def test_taxes_biweekly(self): + wages = 3000.00 + schedule_pay = 'bi-weekly' + allowances = 1 + additional_wh = 0.00 + employee = self._createEmployee() + contract = self._createContract(employee, + wage=wages, + state_id=self.get_us_state('IA'), + state_income_tax_additional_withholding=additional_wh, + ia_w4_sit_allowances=allowances, + schedule_pay=schedule_pay) + + self._log('2019 Iowa tax first payslip bi-weekly:') + payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31') + payslip.compute_sheet() + cats = self._getCategories(payslip) + + # T1 is the gross taxable wages for the pay period minus the Federal withholding amount. We add the federal + # withholding amount because it is calculated in the base US payroll module as a negative + t1_to_test = wages + cats['EE_US_941_FIT'] + # T2 is T1 minus our standard deduction which is a table of flat rates dependent on the number of allowances. + # In our case, we have a biweekly period which on the table has a std deduct. of $65.00 for 0 or 1 allowances, + # and $160.00 of 2 or more allowances. + standard_deduction = 65.00 # The allowance tells us what standard_deduction amount to use. + t2_to_test = t1_to_test - standard_deduction + # T3 is T2 multiplied by the income rates in the large table plus a flat fee for that bracket. + t3_to_test = ((t2_to_test - 2306.77) * (8.53 / 100)) + 135.28 + # T4 is T3 minus a flat amount determined by pay period * the number of deductions. For 2019, our weekly + # deduction amount per allowance is 0.77 + t4_to_test = t3_to_test - (1.54 * allowances) + # t5 is our T4 plus the additional withholding per period + t5_to_test = -t4_to_test - additional_wh + + self.assertPayrollEqual(cats['ER_US_SUTA'], wages * self.IA_UNEMP) + self.assertPayrollEqual(cats['EE_US_SIT'], t5_to_test - additional_wh) + + process_payslip(payslip) + + def test_taxes_with_external_weekly(self): + wages = 2500.00 + schedule_pay = 'weekly' + allowances = 1 + additional_wh = 0.00 + + employee = self._createEmployee() + contract = self._createContract(employee, + wage=wages, + state_id=self.get_us_state('IA'), + state_income_tax_additional_withholding=additional_wh, + ia_w4_sit_allowances=allowances, + schedule_pay=schedule_pay) + + self._log('2019 Iowa external tax first payslip external weekly:') + payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31') + payslip.compute_sheet() + cats = self._getCategories(payslip) + + + # T1 is the gross taxable wages for the pay period minus the Federal withholding amount. We add the federal + # withholding amount because it is calculated in the base US payroll module as a negative + t1_to_test = wages + cats['EE_US_941_FIT'] + # T2 is T1 minus our standard deduction which is a table of flat rates dependent on the number of allowances. + # In our case, we have a weekly period which on the table has a std deduct. of $32.50 for 0 or 1 allowances, + # and 80.00 of 2 or more allowances. + standard_deduction = 32.50 # The allowance tells us what standard_deduction amount to use. + t2_to_test = t1_to_test - standard_deduction + # T3 is T2 multiplied by the income rates in the large table plus a flat fee for that bracket. + t3_to_test = ((t2_to_test - 1153.38) * (8.53 / 100)) + 67.63 + # T4 is T3 minus a flat amount determined by pay period * the number of deductions. For 2019, our weekly + # deduction amount per allowance is 0.77 + t4_to_test = t3_to_test - (0.77 * allowances) + # t5 is our T4 plus the additional withholding per period + t5_to_test = -t4_to_test - additional_wh + + self.assertPayrollEqual(cats['ER_US_SUTA'], wages * self.IA_UNEMP) + self.assertPayrollAlmostEqual(cats['EE_US_SIT'], t5_to_test) + + process_payslip(payslip) \ No newline at end of file diff --git a/l10n_us_hr_payroll/tests/test_us_ia_iowa_payslip_2020.py b/l10n_us_hr_payroll/tests/test_us_ia_iowa_payslip_2020.py new file mode 100755 index 00000000..d5d66b16 --- /dev/null +++ b/l10n_us_hr_payroll/tests/test_us_ia_iowa_payslip_2020.py @@ -0,0 +1,33 @@ +# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details. + +from datetime import date, timedelta +from .common import TestUsPayslip + + +class TestUsIAPayslip(TestUsPayslip): + ### + # 2020 Taxes and Rates + ### + IA_UNEMP_MAX_WAGE = 31600.00 + IA_UNEMP = 1.0 + + def _test_sit(self, wage, additional_withholding, allowances, schedule_pay, date_start, expected_withholding): + employee = self._createEmployee() + contract = self._createContract(employee, + wage=wage, + state_id=self.get_us_state('IA'), + state_income_tax_additional_withholding=additional_withholding, + ia_w4_sit_allowances=allowances, + schedule_pay=schedule_pay) + payslip = self._createPayslip(employee, date_start, date_start + timedelta(days=7)) + payslip.compute_sheet() + cats = self._getCategories(payslip) + + self._log('Computed period tax: ' + str(expected_withholding)) + self.assertPayrollEqual(cats.get('EE_US_SIT', 0.0), -expected_withholding) + + def test_2020_taxes_example(self): + self._test_er_suta('IA', self.IA_UNEMP, date(2020, 1, 1), wage_base=self.IA_UNEMP_MAX_WAGE) + self._test_sit(3000.0, 0.0, 1.0, 'bi-weekly', date(2020, 1, 1), 146.68) + self._test_sit(3000.0, 10.0, 1.0, 'bi-weekly', date(2020, 1, 1), 156.68) + self._test_sit(30000.0, 0.0, 1.0, 'weekly', date(2020, 1, 1), 1640.04) diff --git a/l10n_us_hr_payroll/views/us_payroll_config_views.xml b/l10n_us_hr_payroll/views/us_payroll_config_views.xml index eaa0c8d0..a1afad81 100644 --- a/l10n_us_hr_payroll/views/us_payroll_config_views.xml +++ b/l10n_us_hr_payroll/views/us_payroll_config_views.xml @@ -102,6 +102,12 @@ + +

Form IA W-4 - State Income Tax

+ + + +

Form ID W-4 - State Income Tax