diff --git a/l10n_us_hr_payroll/__manifest__.py b/l10n_us_hr_payroll/__manifest__.py
index 7c651472..6aef7df9 100755
--- a/l10n_us_hr_payroll/__manifest__.py
+++ b/l10n_us_hr_payroll/__manifest__.py
@@ -33,6 +33,7 @@ USA Payroll Rules.
'data/state/oh_ohio.xml',
'data/state/pa_pennsylvania.xml',
'data/state/tx_texas.xml',
+ 'data/state/va_virginia.xml',
'data/state/wa_washington.xml',
'data/final.xml',
'views/hr_contract_views.xml',
diff --git a/l10n_us_hr_payroll/data/final.xml b/l10n_us_hr_payroll/data/final.xml
index 146fb073..6d8ea0f4 100644
--- a/l10n_us_hr_payroll/data/final.xml
+++ b/l10n_us_hr_payroll/data/final.xml
@@ -33,6 +33,9 @@
ref('hr_payroll_rule_er_us_tx_suta_oa'),
ref('hr_payroll_rule_er_us_tx_suta_etia'),
+ ref('hr_payroll_rule_er_us_va_suta'),
+ ref('hr_payroll_rule_ee_us_va_sit'),
+
ref('hr_payroll_rule_er_us_wa_suta'),
ref('hr_payroll_rule_er_us_wa_fml'),
ref('hr_payroll_rule_ee_us_wa_fml'),
diff --git a/l10n_us_hr_payroll/data/state/va_virginia.xml b/l10n_us_hr_payroll/data/state/va_virginia.xml
new file mode 100644
index 00000000..a7e04b5b
--- /dev/null
+++ b/l10n_us_hr_payroll/data/state/va_virginia.xml
@@ -0,0 +1,124 @@
+
+
+
+
+
+ US VA Virginia SUTA Wage Base
+ us_va_suta_wage_base
+ 8000.0
+
+
+
+ US VA Virginia SUTA Wage Base
+ us_va_suta_wage_base
+ 8000.0
+
+
+
+
+
+
+
+ US VA Virginia SUTA Rate
+ us_va_suta_rate
+ 2.51
+
+
+
+ US VA Virginia SUTA Rate
+ us_va_suta_rate
+ 2.51
+
+
+
+
+
+
+ US VA Virginia SIT Rate Table
+ us_va_sit_rate
+ [
+ ( 0.00, 0.0, 2.00),
+ ( 3000.00, 60.0, 3.00),
+ ( 5000.00, 120.0, 5.00),
+ ( 17000.00, 720.0, 5.75),
+ ]
+
+
+
+
+
+
+ US VA Virginia SIT Exemption Rate Table
+ us_va_sit_exemption_rate
+ 930.0
+
+
+
+
+
+
+ US VA Virginia SIT Other Exemption Rate Table
+ us_va_sit_other_exemption_rate
+ 800.0
+
+
+
+
+
+
+ US VA Virginia SIT Deduction
+ us_va_sit_deduction
+ 4500.0
+
+
+
+
+
+
+ US Virginia - Department of Taxation - Unemployment Tax
+
+
+
+ US Virginia - Department of Taxation - Unemployment Tax
+
+
+
+
+ US Virginia - Department of Taxation - Income Tax
+
+
+
+ US Virginia - Department of Taxation - Income Tax
+
+
+
+
+
+
+
+
+
+ ER: US VA Virginia State Unemployment
+ ER_US_VA_SUTA
+ python
+ result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_va_suta_wage_base', rate='us_va_suta_rate', state_code='VA')
+ code
+ result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_va_suta_wage_base', rate='us_va_suta_rate', state_code='VA')
+
+
+
+
+
+
+
+ EE: US VA Virginia State Income Tax Withholding
+ EE_US_VA_SIT
+ python
+ result, _ = va_virginia_state_income_withholding(payslip, categories, worked_days, inputs)
+ code
+ result, result_rate = va_virginia_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 02b90af2..0c426c2a 100644
--- a/l10n_us_hr_payroll/migrations/data.py
+++ b/l10n_us_hr_payroll/migrations/data.py
@@ -17,6 +17,9 @@ FIELDS_CONTRACT_TO_US_PAYROLL_FORMS_2020 = {
'oh_income_allowances': 'oh_it4_sit_exemptions',
'pa_additional_withholding': 'state_income_tax_additional_withholding',
+
+ 'va_va4_exemptions': 'va_va4_sit_exemptions',
+
}
XMLIDS_TO_REMOVE_2020 = [
@@ -66,6 +69,11 @@ XMLIDS_TO_REMOVE_2020 = [
'l10n_us_tx_hr_payroll.hr_payroll_tx_etia',
'l10n_us_tx_hr_payroll.hr_payroll_rules_tx_unemp_wages_2018',
+ 'l10n_us_va_hr_payroll.hr_payroll_va_unemp_wages',
+ 'l10n_us_va_hr_payroll.hr_payroll_va_unemp',
+ 'l10n_us_va_hr_payroll.hr_payroll_va_income_withhold',
+ 'l10n_us_va_hr_payroll.hr_payroll_rules_va_unemp_wages_2018',
+
'l10n_us_wa_hr_payroll.hr_payroll_wa_unemp_wages',
'l10n_us_wa_hr_payroll.hr_payroll_wa_unemp',
'l10n_us_wa_hr_payroll.hr_payroll_wa_lni',
@@ -120,6 +128,13 @@ XMLIDS_TO_RENAME_2020 = {
'l10n_us_tx_hr_payroll.hr_payroll_rules_tx_oa_2018': 'l10n_us_hr_payroll.hr_payroll_rule_er_us_tx_suta_oa',
'l10n_us_tx_hr_payroll.hr_payroll_rules_tx_etia_2018': 'l10n_us_hr_payroll.hr_payroll_rule_er_us_tx_suta_etia',
+ 'l10n_us_va_hr_payroll.res_partner_vador_unemp': 'l10n_us_hr_payroll.res_partner_us_va_dor',
+ 'l10n_us_va_hr_payroll.res_partner_vador_withhold': 'l10n_us_hr_payroll.res_partner_us_va_dor_sit',
+ 'l10n_us_va_hr_payroll.contrib_register_vador_unemp': 'l10n_us_hr_payroll.contrib_register_us_va_dor',
+ 'l10n_us_va_hr_payroll.contrib_register_vador_withhold': 'l10n_us_hr_payroll.contrib_register_us_va_dor_sit',
+ 'l10n_us_va_hr_payroll.hr_payroll_rules_va_unemp_2018': 'l10n_us_hr_payroll.hr_payroll_rule_er_us_va_suta',
+ 'l10n_us_va_hr_payroll.hr_payroll_rules_va_inc_withhold_2018': 'l10n_us_hr_payroll.hr_payroll_rule_ee_us_va_sit',
+
'l10n_us_wa_hr_payroll.res_partner_wador_unemp': 'l10n_us_hr_payroll.res_partner_us_wa_dor',
'l10n_us_wa_hr_payroll.res_partner_wador_lni': 'l10n_us_hr_payroll.res_partner_us_wa_dor_lni',
'l10n_us_wa_hr_payroll.contrib_register_wador_unemp': 'l10n_us_hr_payroll.contrib_register_us_wa_dor',
diff --git a/l10n_us_hr_payroll/models/hr_payslip.py b/l10n_us_hr_payroll/models/hr_payslip.py
index 5521b078..9b7e2f78 100644
--- a/l10n_us_hr_payroll/models/hr_payslip.py
+++ b/l10n_us_hr_payroll/models/hr_payslip.py
@@ -14,6 +14,7 @@ from .state.general import general_state_unemployment, \
is_us_state
from .state.mt_montana import mt_montana_state_income_withholding
from .state.oh_ohio import oh_ohio_state_income_withholding
+from .state.va_virginia import va_virginia_state_income_withholding
from .state.wa_washington import wa_washington_fml_er, \
wa_washington_fml_ee
@@ -50,6 +51,7 @@ class HRPayslip(models.Model):
'is_us_state': is_us_state,
'mt_montana_state_income_withholding': mt_montana_state_income_withholding,
'oh_ohio_state_income_withholding': oh_ohio_state_income_withholding,
+ 'va_virginia_state_income_withholding': va_virginia_state_income_withholding,
'wa_washington_fml_er': wa_washington_fml_er,
'wa_washington_fml_ee': wa_washington_fml_ee,
}
diff --git a/l10n_us_hr_payroll/models/state/va_virginia.py b/l10n_us_hr_payroll/models/state/va_virginia.py
new file mode 100644
index 00000000..881e80e6
--- /dev/null
+++ b/l10n_us_hr_payroll/models/state/va_virginia.py
@@ -0,0 +1,43 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from .general import _state_applies
+
+
+def va_virginia_state_income_withholding(payslip, categories, worked_days, inputs):
+ """
+ Returns SIT eligible wage and rate.
+ WAGE = GROSS - WAGE_US_941_FIT_EXEMPT
+
+ :return: result, result_rate (wage, percent)
+ """
+ state_code = 'VA'
+ 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 = categories.GROSS - categories.WAGE_US_941_FIT_EXEMPT
+ pay_periods = payslip.dict.get_pay_periods_in_year()
+ additional = payslip.dict.contract_id.us_payroll_config_value('state_income_tax_additional_withholding')
+ personal_exemptions = payslip.dict.contract_id.us_payroll_config_value('va_va4_sit_exemptions')
+ other_exemptions = payslip.dict.contract_id.us_payroll_config_value('va_va4_sit_other_exemptions')
+ personal_exemption_rate = payslip.dict.rule_parameter('us_va_sit_exemption_rate')
+ other_exemption_rate = payslip.dict.rule_parameter('us_va_sit_other_exemption_rate')
+ deduction = payslip.dict.rule_parameter('us_va_sit_deduction')
+ withholding_rate = payslip.dict.rule_parameter('us_va_sit_rate')
+ if wage == 0.0:
+ return 0.0, 0.0
+
+ taxable_wage = (wage * pay_periods) - (deduction + (personal_exemptions * personal_exemption_rate) + (other_exemptions * other_exemption_rate))
+ withholding = 0.0
+ if taxable_wage > 0.0:
+ for row in withholding_rate:
+ if taxable_wage > row[0]:
+ selected_row = row
+ wage_min, base, rate = selected_row
+ withholding = base + ((taxable_wage - wage_min) * rate / 100.0)
+ 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 be1fa946..befbb3b2 100644
--- a/l10n_us_hr_payroll/models/us_payroll_config.py
+++ b/l10n_us_hr_payroll/models/us_payroll_config.py
@@ -66,3 +66,8 @@ class HRContractUSPayrollConfig(models.Model):
# Ohio will use generic SIT exempt and additional fields
oh_it4_sit_exemptions = fields.Integer(string='Ohio IT-4 Exemptions',
help='Line 4')
+
+ va_va4_sit_exemptions = fields.Integer(string='Virginia VA-4(P) Personal Exemptions',
+ help='VA-4(P) 1(a)')
+ va_va4_sit_other_exemptions = fields.Integer(string='Virginia VA-4(P) Age & Blindness Exemptions',
+ help='VA-4(P) 1(b)')
diff --git a/l10n_us_hr_payroll/tests/__init__.py b/l10n_us_hr_payroll/tests/__init__.py
index 90d6dfef..ef846086 100755
--- a/l10n_us_hr_payroll/tests/__init__.py
+++ b/l10n_us_hr_payroll/tests/__init__.py
@@ -19,5 +19,8 @@ from . import test_us_pa_pennsylvania_payslip_2020
from . import test_us_tx_texas_payslip_2019
from . import test_us_tx_texas_payslip_2020
+from . import test_us_va_virginia_payslip_2019
+from . import test_us_va_virginia_payslip_2020
+
from . import test_us_wa_washington_payslip_2019
from . import test_us_wa_washington_payslip_2020
diff --git a/l10n_us_hr_payroll/tests/test_us_va_virginia_payslip_2019.py b/l10n_us_hr_payroll/tests/test_us_va_virginia_payslip_2019.py
new file mode 100644
index 00000000..b8f14393
--- /dev/null
+++ b/l10n_us_hr_payroll/tests/test_us_va_virginia_payslip_2019.py
@@ -0,0 +1,133 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from datetime import date
+from .common import TestUsPayslip, process_payslip
+from odoo.addons.l10n_us_hr_payroll.models.hr_contract import USHRContract
+
+
+class TestUsVaPayslip(TestUsPayslip):
+ ###
+ # Taxes and Rates
+ ###
+ VA_UNEMP_MAX_WAGE = 8000.0
+ VA_UNEMP = 2.51
+ VA_SIT_DEDUCTION = 4500.0
+ VA_SIT_EXEMPTION = 930.0
+ VA_SIT_OTHER_EXEMPTION = 800.0
+
+ def test_2019_taxes(self):
+ salary = 5000.0
+
+ # For formula from https://www.tax.virginia.gov/withholding-calculator
+ """
+ Key
+ G = Gross Pay for Pay Period P = Pay periods per year
+ A = Annualized gross pay E1 = Personal and Dependent Exemptions
+ T = Annualized taxable income E2 = Age 65 and Over & Blind Exemptions
+ WH = Tax to be withheld for pay period W = Annualized tax to be withheld
+ G x P - [$3000+ (E1 x 930) + (E2 x 800)] = T
+ Calculate W as follows:
+ If T is: W is:
+ Not over $3,000 2% of T
+ Over But Not Over Then
+ $3,000 $5,000 $60 + (3% of excess over $3,000)
+ $5,000 $17,000 $120 + (5% of excess over $5,000)
+ $17,000 $720 + (5.75% of excess over $17,000)
+ W / P = WH
+ """
+ e1 = 2
+ e2 = 0
+ t = salary * 12 - (self.VA_SIT_DEDUCTION + (e1 * self.VA_SIT_EXEMPTION) + (e2 * self.VA_SIT_OTHER_EXEMPTION))
+
+ if t <= 3000:
+ w = 0.02 * t
+ elif t <= 5000:
+ w = 60 + (0.03 * (t - 3000))
+ elif t <= 17000:
+ w = 120 + (0.05 * (t - 5000))
+ else:
+ w = 720 + (0.0575 * (t - 17000))
+
+ wh = w / 12
+
+ employee = self._createEmployee()
+
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('VA'),
+ va_va4_sit_exemptions=e1,
+ va_va4_sit_other_exemptions=e2
+ )
+
+ # tax rates
+ va_unemp = self.VA_UNEMP / -100.0
+
+ self._log('2019 Virginia tax first payslip:')
+ payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['ER_US_SUTA'], salary * va_unemp)
+ self.assertPayrollEqual(cats['EE_US_SIT'], -wh)
+
+ process_payslip(payslip)
+
+ # Make a new payslip, this one will have maximums
+
+ remaining_va_unemp_wages = self.VA_UNEMP_MAX_WAGE - salary if (self.VA_UNEMP_MAX_WAGE - 2*salary < salary) \
+ else salary
+
+ self._log('2019 Virginia tax second payslip:')
+ payslip = self._createPayslip(employee, '2019-02-01', '2019-02-28')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+ self.assertPayrollEqual(cats['ER_US_SUTA'], remaining_va_unemp_wages * va_unemp)
+
+ def test_2019_taxes_with_external(self):
+ salary = 5000.0
+ external_wages = 6000.0
+
+ employee = self._createEmployee()
+
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('VA'),
+ external_wages=external_wages,
+ )
+
+ # tax rates
+ va_unemp = self.VA_UNEMP / -100.0
+
+ self._log('2019 Virginia_external tax first payslip:')
+ payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+ self.assertPayrollEqual(cats['ER_US_SUTA'], (self.VA_UNEMP_MAX_WAGE - external_wages) * va_unemp)
+
+ def test_2019_taxes_with_state_exempt(self):
+ salary = 5000.0
+ external_wages = 6000.0
+
+ employee = self._createEmployee()
+
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('VA'),
+ external_wages=external_wages,
+ futa_type=USHRContract.FUTA_TYPE_BASIC)
+
+ # tax rates
+ self._log('2019 Virginia exempt tax first payslip:')
+ payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['ER_US_SUTA'], 0.0)
diff --git a/l10n_us_hr_payroll/tests/test_us_va_virginia_payslip_2020.py b/l10n_us_hr_payroll/tests/test_us_va_virginia_payslip_2020.py
new file mode 100644
index 00000000..012e4845
--- /dev/null
+++ b/l10n_us_hr_payroll/tests/test_us_va_virginia_payslip_2020.py
@@ -0,0 +1,116 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from datetime import date
+from .common import TestUsPayslip
+
+
+class TestUsVaPayslip(TestUsPayslip):
+ ###
+ # Taxes and Rates
+ ###
+ VA_UNEMP_MAX_WAGE = 8000.0
+ VA_UNEMP = 2.51
+ VA_SIT_DEDUCTION = 4500.0
+ VA_SIT_EXEMPTION = 930.0
+ VA_SIT_OTHER_EXEMPTION = 800.0
+
+ def _run_test_sit(self,
+ wage=0.0,
+ schedule_pay='monthly',
+ filing_status='single',
+ dependent_credit=0.0,
+ other_income=0.0,
+ deductions=0.0,
+ additional_withholding=0.0,
+ is_nonresident_alien=False,
+ state_income_tax_exempt=False,
+ state_income_tax_additional_withholding=0.0,
+ va_va4_sit_exemptions=0,
+ va_va4_sit_other_exemptions=0,
+ expected=0.0,
+ ):
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=wage,
+ schedule_pay=schedule_pay,
+ fed_941_fit_w4_is_nonresident_alien=is_nonresident_alien,
+ fed_941_fit_w4_filing_status=filing_status,
+ fed_941_fit_w4_multiple_jobs_higher=False,
+ fed_941_fit_w4_dependent_credit=dependent_credit,
+ fed_941_fit_w4_other_income=other_income,
+ fed_941_fit_w4_deductions=deductions,
+ fed_941_fit_w4_additional_withholding=additional_withholding,
+ state_income_tax_exempt=state_income_tax_exempt,
+ state_income_tax_additional_withholding=state_income_tax_additional_withholding,
+ va_va4_sit_exemptions=va_va4_sit_exemptions,
+ va_va4_sit_other_exemptions=va_va4_sit_other_exemptions,
+ state_id=self.get_us_state('VA'),
+ )
+ payslip = self._createPayslip(employee, '2020-01-01', '2020-01-31')
+ payslip.compute_sheet()
+ cats = self._getCategories(payslip)
+ # Instead of PayrollEqual after initial first round of testing.
+ self.assertPayrollAlmostEqual(cats.get('EE_US_SIT', 0.0), -expected)
+ return payslip
+
+ def test_2020_taxes(self):
+ self._test_er_suta('VA', self.VA_UNEMP, date(2020, 1, 1), wage_base=self.VA_UNEMP_MAX_WAGE)
+
+ salary = 5000.0
+
+ # For formula from https://www.tax.virginia.gov/withholding-calculator
+ e1 = 2
+ e2 = 0
+ t = salary * 12 - (self.VA_SIT_DEDUCTION + (e1 * self.VA_SIT_EXEMPTION) + (e2 * self.VA_SIT_OTHER_EXEMPTION))
+
+ if t <= 3000:
+ w = 0.02 * t
+ elif t <= 5000:
+ w = 60 + (0.03 * (t - 3000))
+ elif t <= 17000:
+ w = 120 + (0.05 * (t - 5000))
+ else:
+ w = 720 + (0.0575 * (t - 17000))
+
+ wh = w / 12
+
+ self._run_test_sit(wage=salary,
+ schedule_pay='monthly',
+ state_income_tax_exempt=False,
+ state_income_tax_additional_withholding=0.0,
+ va_va4_sit_exemptions=e1,
+ va_va4_sit_other_exemptions=e2,
+ expected=wh,)
+ self.assertPayrollEqual(wh, 235.57) # To test against calculator
+
+ # Below expected comes from the calculator linked above
+ self._run_test_sit(wage=450.0,
+ schedule_pay='weekly',
+ state_income_tax_exempt=False,
+ state_income_tax_additional_withholding=0.0,
+ va_va4_sit_exemptions=3,
+ va_va4_sit_other_exemptions=1,
+ expected=12.22,)
+ self._run_test_sit(wage=2500.0,
+ schedule_pay='bi-weekly',
+ state_income_tax_exempt=False,
+ state_income_tax_additional_withholding=0.0,
+ va_va4_sit_exemptions=1,
+ va_va4_sit_other_exemptions=0,
+ expected=121.84,)
+ self._run_test_sit(wage=10000.0,
+ schedule_pay='semi-monthly',
+ state_income_tax_exempt=False,
+ state_income_tax_additional_withholding=100.0,
+ va_va4_sit_exemptions=0,
+ va_va4_sit_other_exemptions=1,
+ expected=651.57,)
+
+ # Test exempt
+ self._run_test_sit(wage=2400.0,
+ schedule_pay='monthly',
+ state_income_tax_exempt=True,
+ state_income_tax_additional_withholding=0.0,
+ va_va4_sit_exemptions=1,
+ va_va4_sit_other_exemptions=1,
+ expected=0.0,)
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 fefcc61e..677ee061 100644
--- a/l10n_us_hr_payroll/views/us_payroll_config_views.xml
+++ b/l10n_us_hr_payroll/views/us_payroll_config_views.xml
@@ -66,6 +66,13 @@
No additional fields.
+
+ Form VA-4/VA-4P - State Income Tax
+
+
+
+
+
No additional fields.
Ensure that your Employee and Employer workers' comp code fields are filled in for WA LNI withholding.