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.