From bbea403f33eb0b17e701d38fb21d14c7db216ce7 Mon Sep 17 00:00:00 2001 From: Bhoomi Vaishnani Date: Wed, 5 Feb 2020 11:30:31 -0500 Subject: [PATCH] IMP `l10n_us_hr_payroll` Port `l10n_us_ar_hr_payroll` AR Arkansas including migration --- l10n_us_hr_payroll/__manifest__.py | 1 + l10n_us_hr_payroll/data/final.xml | 3 + l10n_us_hr_payroll/data/state/ar_arkansas.xml | 137 ++++++++++++++++++ l10n_us_hr_payroll/migrations/data.py | 17 +++ l10n_us_hr_payroll/models/hr_payslip.py | 2 + .../models/state/ar_arkansas.py | 47 ++++++ .../models/us_payroll_config.py | 2 + l10n_us_hr_payroll/tests/__init__.py | 3 + .../tests/test_us_ar_arkansas_payslip_2019.py | 72 +++++++++ .../tests/test_us_ar_arkansas_payslip_2020.py | 35 +++++ .../views/us_payroll_config_views.xml | 6 + 11 files changed, 325 insertions(+) create mode 100644 l10n_us_hr_payroll/data/state/ar_arkansas.xml create mode 100644 l10n_us_hr_payroll/models/state/ar_arkansas.py create mode 100644 l10n_us_hr_payroll/tests/test_us_ar_arkansas_payslip_2019.py create mode 100644 l10n_us_hr_payroll/tests/test_us_ar_arkansas_payslip_2020.py diff --git a/l10n_us_hr_payroll/__manifest__.py b/l10n_us_hr_payroll/__manifest__.py index e6bb1b7e..065334d3 100755 --- a/l10n_us_hr_payroll/__manifest__.py +++ b/l10n_us_hr_payroll/__manifest__.py @@ -28,6 +28,7 @@ USA Payroll Rules. 'data/federal/fed_941_fica_rules.xml', 'data/federal/fed_941_fit_parameters.xml', 'data/federal/fed_941_fit_rules.xml', + 'data/state/ar_arkansas.xml', 'data/state/fl_florida.xml', 'data/state/ga_georgia.xml', 'data/state/mi_michigan.xml', diff --git a/l10n_us_hr_payroll/data/final.xml b/l10n_us_hr_payroll/data/final.xml index e18d015e..573f2c59 100644 --- a/l10n_us_hr_payroll/data/final.xml +++ b/l10n_us_hr_payroll/data/final.xml @@ -16,6 +16,9 @@ ref('hr_payroll_rule_ee_fed_941_fit'), + ref('hr_payroll_rule_er_us_ar_suta'), + ref('hr_payroll_rule_ee_us_ar_sit'), + ref('hr_payroll_rule_er_us_fl_suta'), ref('hr_payroll_rule_er_us_ga_suta'), diff --git a/l10n_us_hr_payroll/data/state/ar_arkansas.xml b/l10n_us_hr_payroll/data/state/ar_arkansas.xml new file mode 100644 index 00000000..fcc162eb --- /dev/null +++ b/l10n_us_hr_payroll/data/state/ar_arkansas.xml @@ -0,0 +1,137 @@ + + + + + + US AR Arkansas SUTA Wage Base + us_ar_suta_wage_base + 10000.0 + + + + US AR Arkansas SUTA Wage Base + us_ar_suta_wage_base + 8000.0 + + + + + + + + US AR Arkansas SUTA Rate + us_ar_suta_rate + 3.2 + + + + US AR Arkansas SUTA Rate + us_ar_suta_rate + 2.9 + + + + + + + US AR Arkansas SIT Tax Rate + us_ar_sit_tax_rate + [ + ( 4599, 0.0, 0.00), + ( 9099, 2.0, 91.98), + ( 13699, 3.0, 182.97), + ( 22599, 3.4, 237.77), + ( 37899, 5.0, 421.46), + ( 80800, 5.9, 762.55), + ( 81800, 6.6, 1243.40), + ( 82800, 6.6, 1143.40), + ( 84100, 6.6, 1043.40), + ( 85200, 6.6, 943.40), + ( 86200, 6.6, 843.40), + ( 'inf', 6.6, 803.40), + ] + + + + US AR Arkansas SIT Tax Rate + us_ar_sit_tax_rate + [ + ( 4599, 0.0, 0.00), + ( 9099, 2.0, 91.98), + ( 13699, 3.0, 182.97), + ( 22599, 3.4, 237.77), + ( 37899, 5.0, 421.46), + ( 80800, 5.9, 762.55), + ( 81800, 6.6, 1243.40), + ( 82800, 6.6, 1143.40), + ( 84100, 6.6, 1043.40), + ( 85200, 6.6, 943.40), + ( 86200, 6.6, 843.40), + ( 'inf', 6.6, 803.40), + ] + + + + + + + US AR Arkansas Allowances Rate + us_ar_sit_standard_deduction_rate + 2200.0 + + + + US AR Arkansas Allowances Rate + us_ar_sit_standard_deduction_rate + 2200.0 + + + + + + + US Arkansas - Department of Workforce Solutions - Unemployment Tax + + + US Arkansas - Department of Workforce Solutions - Unemployment Tax + + + + + US Arkansas - Department of Financial Administration - Income Tax + + + US Arkansas - Department of Financial Administration - Income Tax + + + + + + + + + + ER: US AR Arkansas State Unemployment + ER_US_AR_SUTA + python + result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_ar_suta_wage_base', rate='us_ar_suta_rate', state_code='AR') + code + result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_ar_suta_wage_base', rate='us_ar_suta_rate', state_code='AR') + + + + + + + + EE: US AR Arkansas State Income Tax Withholding + EE_US_AR_SIT + python + result, _ = ar_arkansas_state_income_withholding(payslip, categories, worked_days, inputs) + code + result, result_rate = ar_arkansas_state_income_withholding(payslip, categories, worked_days, inputs) + + + + + \ No newline at end of file diff --git a/l10n_us_hr_payroll/migrations/data.py b/l10n_us_hr_payroll/migrations/data.py index 432b3680..10951185 100644 --- a/l10n_us_hr_payroll/migrations/data.py +++ b/l10n_us_hr_payroll/migrations/data.py @@ -9,6 +9,10 @@ FIELDS_CONTRACT_TO_US_PAYROLL_FORMS_2020 = { 'fica_exempt': 'fed_941_fica_exempt', 'futa_type': 'fed_940_type', # State + 'ar_w4_allowances': 'ar_ar4ec_sit_allowances', + 'ar_w4_tax_exempt': 'state_income_tax_exempt', + 'ar_w4_additional_wh': 'state_income_tax_additional_withholding', + 'ga_g4_filing_status': 'ga_g4_sit_filing_status', 'ga_g4_dependent_allowances': 'ga_g4_sit_dependent_allowances', 'ga_g4_additional_allowances': 'ga_g4_sit_additional_allowances', @@ -68,6 +72,11 @@ XMLIDS_TO_REMOVE_2020 = [ 'l10n_us_hr_payroll.hr_payroll_rules_futa_wages_2018', 'l10n_us_hr_payroll.hr_payroll_rules_fed_inc_withhold_2018_married', # State + 'l10n_us_ar_hr_payroll.hr_payroll_ar_unemp_wages', + 'l10n_us_ar_hr_payroll.hr_payroll_ar_unemp', + 'l10n_us_ar_hr_payroll.hr_payroll_ar_income_withhold', + 'l10n_us_ar_hr_payroll.hr_payroll_rules_ar_unemp_wages', + 'l10n_us_fl_hr_payroll.hr_payroll_fl_unemp_wages', 'l10n_us_fl_hr_payroll.hr_payroll_fl_unemp', 'l10n_us_fl_hr_payroll.hr_payroll_rules_fl_unemp_wages_2018', @@ -182,6 +191,14 @@ XMLIDS_TO_RENAME_2020 = { 'l10n_us_hr_payroll.hr_payroll_rules_fica_comp_m': 'l10n_us_hr_payroll.hr_payroll_rule_er_fed_941_m', 'l10n_us_hr_payroll.hr_payroll_rules_fed_inc_withhold_2018_single': 'l10n_us_hr_payroll.hr_payroll_rule_ee_fed_941_fit', # State + 'l10n_us_ar_hr_payroll.res_partner_ar_dws_unemp': 'l10n_us_hr_payroll.res_partner_us_ar_dor', + 'l10n_us_ar_hr_payroll.res_partner_ar_dfa_withhold': 'l10n_us_hr_payroll.res_partner_us_ar_dor_sit', + 'l10n_us_ar_hr_payroll.contrib_register_ar_dws_unemp': 'l10n_us_hr_payroll.contrib_register_us_ar_dor', + 'l10n_us_ar_hr_payroll.contrib_register_ar_dfa_withhold': 'l10n_us_hr_payroll.contrib_register_us_ar_dor_sit', + 'l10n_us_ar_hr_payroll.hr_payroll_rules_ar_unemp': 'l10n_us_hr_payroll.hr_payroll_rule_er_us_ar_suta', + 'l10n_us_ar_hr_payroll.hr_payroll_rules_ar_inc_withhold': 'l10n_us_hr_payroll', + 'l10n_us_ar_hr_payroll.': 'l10n_us_hr_payroll.hr_payroll_rule_ee_us_ar_sit', + 'l10n_us_fl_hr_payroll.hr_payroll_rules_fl_unemp_2018': 'l10n_us_hr_payroll.hr_payroll_rule_er_us_fl_suta', 'l10n_us_fl_hr_payroll.res_partner_fldor': 'l10n_us_hr_payroll.res_partner_us_fl_dor', 'l10n_us_fl_hr_payroll.contrib_register_fldor': 'l10n_us_hr_payroll.contrib_register_us_fl_dor', diff --git a/l10n_us_hr_payroll/models/hr_payslip.py b/l10n_us_hr_payroll/models/hr_payslip.py index c2174f97..cab0c257 100644 --- a/l10n_us_hr_payroll/models/hr_payslip.py +++ b/l10n_us_hr_payroll/models/hr_payslip.py @@ -14,6 +14,7 @@ from .federal.fed_941 import ee_us_941_fica_ss, \ from .state.general import general_state_unemployment, \ general_state_income_withholding, \ is_us_state +from .state.ar_arkansas import ar_arkansas_state_income_withholding from .state.ga_georgia import ga_georgia_state_income_withholding from .state.mi_michigan import mi_michigan_state_income_withholding from .state.mn_minnesota import mn_minnesota_state_income_withholding @@ -58,6 +59,7 @@ class HRPayslip(models.Model): 'general_state_unemployment': general_state_unemployment, 'general_state_income_withholding': general_state_income_withholding, 'is_us_state': is_us_state, + 'ar_arkansas_state_income_withholding': ar_arkansas_state_income_withholding, 'ga_georgia_state_income_withholding': ga_georgia_state_income_withholding, 'mi_michigan_state_income_withholding': mi_michigan_state_income_withholding, 'mn_minnesota_state_income_withholding': mn_minnesota_state_income_withholding, diff --git a/l10n_us_hr_payroll/models/state/ar_arkansas.py b/l10n_us_hr_payroll/models/state/ar_arkansas.py new file mode 100644 index 00000000..87190e62 --- /dev/null +++ b/l10n_us_hr_payroll/models/state/ar_arkansas.py @@ -0,0 +1,47 @@ +# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details. + +from .general import _state_applies, sit_wage + + +def ar_arkansas_state_income_withholding(payslip, categories, worked_days, inputs): + """ + Returns SIT eligible wage and rate. + + :return: result, result_rate (wage, percent) + """ + state_code = 'AR' + if not _state_applies(payslip, state_code): + return 0.0, 0.0 + + if payslip.dict.contract_id.us_payroll_config_value('state_income_tax_exempt'): + return 0.0, 0.0 + + # Determine Wage + wage = sit_wage(payslip, categories) + if wage == 0.0: + return 0.0, 0.0 + + pay_periods = payslip.dict.get_pay_periods_in_year() + additional = payslip.dict.contract_id.us_payroll_config_value('state_income_tax_additional_withholding') + sit_tax_rate = payslip.dict.rule_parameter('us_ar_sit_tax_rate') + standard_deduction = payslip.dict.rule_parameter('us_ar_sit_standard_deduction_rate') + allowances = payslip.dict.contract_id.us_payroll_config_value('ar_ar4ec_sit_allowances') + + allowances_amt = allowances * 26.0 + taxable_income = (wage * pay_periods) - standard_deduction + if taxable_income < 87001.0: + taxable_income = (taxable_income // 50) * 50.0 + 50.0 + + withholding = 0.0 + for row in sit_tax_rate: + cap, rate, adjust_amount = row + cap = float(cap) + if cap > taxable_income: + withholding = (((rate / 100.0) * taxable_income) - adjust_amount) - allowances_amt + break + + # In case withholding or taxable_income is negative + withholding = max(withholding, 0.0) + withholding = round(withholding / pay_periods) + 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 fd21e0d2..b269cab7 100644 --- a/l10n_us_hr_payroll/models/us_payroll_config.py +++ b/l10n_us_hr_payroll/models/us_payroll_config.py @@ -51,6 +51,8 @@ class HRContractUSPayrollConfig(models.Model): fed_941_fit_w4_additional_withholding = fields.Float(string='Federal W4 Additional Withholding [4(c)]', help='Form W4 (2020+) 4(c)') + ar_ar4ec_sit_allowances = fields.Integer(string='Arkansas AR4EC allowances', help='AR4EC 3.') + ga_g4_sit_filing_status = fields.Selection([ ('exempt', 'Exempt'), ('single', 'Single'), diff --git a/l10n_us_hr_payroll/tests/__init__.py b/l10n_us_hr_payroll/tests/__init__.py index 711bb35c..83e839d9 100755 --- a/l10n_us_hr_payroll/tests/__init__.py +++ b/l10n_us_hr_payroll/tests/__init__.py @@ -4,6 +4,9 @@ from . import common from . import test_us_payslip_2019 from . import test_us_payslip_2020 +from . import test_us_ar_arkansas_payslip_2019 +from . import test_us_ar_arkansas_payslip_2020 + from . import test_us_fl_florida_payslip_2019 from . import test_us_fl_florida_payslip_2020 diff --git a/l10n_us_hr_payroll/tests/test_us_ar_arkansas_payslip_2019.py b/l10n_us_hr_payroll/tests/test_us_ar_arkansas_payslip_2019.py new file mode 100644 index 00000000..73b0f59c --- /dev/null +++ b/l10n_us_hr_payroll/tests/test_us_ar_arkansas_payslip_2019.py @@ -0,0 +1,72 @@ +# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details. + +from .common import TestUsPayslip, process_payslip + + +class TestUsARPayslip(TestUsPayslip): + # https://www.dfa.arkansas.gov/images/uploads/incomeTaxOffice/whformula.pdf Calculation based on this file. + AR_UNEMP_MAX_WAGE = 10000.00 + AR_UNEMP = -3.2 / 100.0 + AR_INC_TAX = -0.0535 + + def test_taxes_monthly(self): + salary = 2127.0 + schedule_pay = 'monthly' + + employee = self._createEmployee() + contract = self._createContract(employee, + wage=salary, + state_id=self.get_us_state('AR'), + state_income_tax_additional_withholding=0.0, + ar_ar4ec_sit_allowances=2.0, + state_income_tax_exempt=False, + schedule_pay='monthly') + + self._log('2019 Arkansas tax first payslip weekly:') + payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31') + payslip.compute_sheet() + cats = self._getCategories(payslip) + + # Not exempt from rule 1 or rule 2 - unemployment wages., and actual unemployment. + self.assertPayrollEqual(cats['ER_US_SUTA'], salary * self.AR_UNEMP) + + process_payslip(payslip) + + # Make a new payslip, this one will have maximums + remaining_AR_UNEMP_wages = self.AR_UNEMP_MAX_WAGE - salary if (self.AR_UNEMP_MAX_WAGE - 2*salary < salary) else salary + # We reached the cap of 10000.0 in the first payslip. + self._log('2019 Arkansas 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'], remaining_AR_UNEMP_wages * self.AR_UNEMP) + + def test_additional_withholding(self): + salary = 5000.0 + schedule_pay = 'monthly' + pay_periods = 12 + allowances = 2 + # TODO: comment on how it was calculated + test_ar_amt = 2598.60 + + employee = self._createEmployee() + contract = self._createContract(employee, + wage=salary, + state_id=self.get_us_state('AR'), + state_income_tax_additional_withholding=100.0, + ar_ar4ec_sit_allowances=2.0, + state_income_tax_exempt=False, + schedule_pay='monthly') + + + self._log('2019 Arkansas tax first payslip weekly:') + payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31') + payslip.compute_sheet() + cats = self._getCategories(payslip) + + self.assertPayrollEqual(cats['ER_US_SUTA'], salary * self.AR_UNEMP) + # TODO: change to hand the test_ar_amt already be divided by pay periods + self.assertPayrollAlmostEqual(cats['EE_US_SIT'], -round(test_ar_amt / pay_periods) - 100) + + process_payslip(payslip) diff --git a/l10n_us_hr_payroll/tests/test_us_ar_arkansas_payslip_2020.py b/l10n_us_hr_payroll/tests/test_us_ar_arkansas_payslip_2020.py new file mode 100644 index 00000000..bf630b6c --- /dev/null +++ b/l10n_us_hr_payroll/tests/test_us_ar_arkansas_payslip_2020.py @@ -0,0 +1,35 @@ +# 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 TestUsARPayslip(TestUsPayslip): + # Taxes and Rates + AR_UNEMP_MAX_WAGE = 8000.0 + AR_UNEMP = 2.9 + + def _test_sit(self, wage, exemptions, allowances, additional_withholding, schedule_pay, date_start, expected_withholding): + + employee = self._createEmployee() + contract = self._createContract(employee, + wage=wage, + state_id=self.get_us_state('AR'), + state_income_tax_exempt=exemptions, + state_income_tax_additional_withholding=additional_withholding, + ar_ar4ec_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('AR', self.AR_UNEMP, date(2020, 1, 1), wage_base=self.AR_UNEMP_MAX_WAGE) + self._test_sit(5000.0, True, 0.0, 0, 'monthly', date(2020, 1, 1), 0.0) + self._test_sit(5000.0, False, 0.0, 0, 'monthly', date(2020, 1, 1), 221.0) + self._test_sit(5000.0, False, 0.0, 150, 'monthly', date(2020, 1, 1), 371.0) + self._test_sit(5000.0, False, 2.0, 0, 'monthly', date(2020, 1, 1), 217) + self._test_sit(5000.0, False, 2.0, 150, 'monthly', date(2020, 1, 1), 367) 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 12e5c507..51a4c6b9 100644 --- a/l10n_us_hr_payroll/views/us_payroll_config_views.xml +++ b/l10n_us_hr_payroll/views/us_payroll_config_views.xml @@ -44,6 +44,12 @@ + +

Form AR4EC - State Income Tax

+ + + +

No additional fields.