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.