diff --git a/l10n_us_hr_payroll/__manifest__.py b/l10n_us_hr_payroll/__manifest__.py
index 2b656521..fb47d741 100755
--- a/l10n_us_hr_payroll/__manifest__.py
+++ b/l10n_us_hr_payroll/__manifest__.py
@@ -29,6 +29,7 @@ USA Payroll Rules.
'data/federal/fed_941_fit_parameters.xml',
'data/federal/fed_941_fit_rules.xml',
'data/state/fl_florida.xml',
+ 'data/state/pa_pennsylvania.xml',
'data/final.xml',
'views/hr_contract_views.xml',
'views/us_payroll_config_views.xml',
diff --git a/l10n_us_hr_payroll/data/base.xml b/l10n_us_hr_payroll/data/base.xml
index db3e707e..7327729f 100644
--- a/l10n_us_hr_payroll/data/base.xml
+++ b/l10n_us_hr_payroll/data/base.xml
@@ -13,4 +13,11 @@
+
+
+ EE: State Income Tax Withholding
+ EE_US_SIT
+
+
+
\ No newline at end of file
diff --git a/l10n_us_hr_payroll/data/final.xml b/l10n_us_hr_payroll/data/final.xml
index 2ab128c0..d266884a 100644
--- a/l10n_us_hr_payroll/data/final.xml
+++ b/l10n_us_hr_payroll/data/final.xml
@@ -17,6 +17,9 @@
ref('hr_payroll_rule_ee_fed_941_fit'),
ref('hr_payroll_rule_er_us_fl_suta'),
+ ref('hr_payroll_rule_er_us_pa_suta'),
+ ref('hr_payroll_rule_ee_us_pa_suta'),
+ ref('hr_payroll_rule_ee_us_pa_sit'),
ref('hr_salary_rule_commission'),
ref('hr_salary_rule_gamification'),
diff --git a/l10n_us_hr_payroll/data/state/pa_pennsylvania.xml b/l10n_us_hr_payroll/data/state/pa_pennsylvania.xml
new file mode 100644
index 00000000..d0db79c9
--- /dev/null
+++ b/l10n_us_hr_payroll/data/state/pa_pennsylvania.xml
@@ -0,0 +1,126 @@
+
+
+
+
+
+ US PA Pennsylvania SUTA Wage Base (ER)
+ us_pa_suta_wage_base
+ 10000.00
+
+
+
+ US PA Pennsylvania SUTA Wage Base (ER)
+ us_pa_suta_wage_base
+ 10000.00
+
+
+
+
+
+
+
+ US PA Pennsylvania SUTA Rate
+ us_pa_suta_rate
+ 3.6890
+
+
+
+ US PA Pennsylvania SUTA Rate
+ us_pa_suta_rate
+ 3.6890
+
+
+
+
+
+
+ US PA Pennsylvania SUTA Employee Rate
+ us_pa_suta_ee_rate
+ 0.06
+
+
+
+ US PA Pennsylvania SUTA Employee Rate
+ us_pa_suta_ee_rate
+ 0.06
+
+
+
+
+
+
+ US PA Pennsylvania SIT Rate
+ us_pa_sit_rate
+ 3.07
+
+
+
+ US PA Pennsylvania SIT Rate
+ us_pa_sit_rate
+ 3.07
+
+
+
+
+
+
+ US Pennsylvania - Department of Revenue - Unemployment Tax
+
+
+
+ US Pennsylvania - Department of Revenue - Unemployment Tax
+
+
+
+
+ US Pennsylvania - Department of Revenue - Income Tax
+
+
+
+ US Pennsylvania - Department of Revenue - Income Tax
+
+
+
+
+
+
+
+
+
+ ER: US PA Pennsylvania State Unemployment (UC-2)
+ ER_US_PA_SUTA
+ python
+ result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_pa_suta_wage_base', rate='us_pa_suta_rate', state_code='PA')
+ code
+ result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_pa_suta_wage_base', rate='us_pa_suta_rate', state_code='PA')
+
+
+
+
+
+
+
+ EE: US PA Pennsylvania State Unemployment (UC-2)
+ EE_US_PA_SUTA
+ python
+ result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, rate='us_pa_suta_ee_rate', state_code='PA')
+ code
+ result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, rate='us_pa_suta_ee_rate', state_code='PA')
+
+
+
+
+
+
+
+ EE: US PA Pennsylvania State Income Tax Withholding (PA-501)
+ EE_US_PA_SIT
+ python
+ result, _ = general_state_income_withholding(payslip, categories, worked_days, inputs, rate='us_pa_sit_rate', state_code='PA')
+ code
+ result, result_rate = general_state_income_withholding(payslip, categories, worked_days, inputs, rate='us_pa_sit_rate', state_code='PA')
+
+
+
+
+
\ No newline at end of file
diff --git a/l10n_us_hr_payroll/migrations/12.0.2020.1.0/post-migration.py b/l10n_us_hr_payroll/migrations/12.0.2020.1.0/post-migration.py
index 24c8fd0d..95984afb 100644
--- a/l10n_us_hr_payroll/migrations/12.0.2020.1.0/post-migration.py
+++ b/l10n_us_hr_payroll/migrations/12.0.2020.1.0/post-migration.py
@@ -27,7 +27,6 @@ def migrate(cr, installed_version):
def get_state(code, cache={}):
country_key = 'US_COUNTRY'
if code in cache:
- _logger.warn(' return from cache: ' + str(cache[code]))
return cache[code]
if country_key not in cache:
cache[country_key] = env.ref('base.us')
diff --git a/l10n_us_hr_payroll/migrations/data.py b/l10n_us_hr_payroll/migrations/data.py
index 6d87366e..99534b57 100644
--- a/l10n_us_hr_payroll/migrations/data.py
+++ b/l10n_us_hr_payroll/migrations/data.py
@@ -8,7 +8,8 @@ FIELDS_CONTRACT_TO_US_PAYROLL_FORMS_2020 = {
'w4_additional_withholding': 'fed_941_fit_w4_additional_withholding',
'fica_exempt': 'fed_941_fica_exempt',
'futa_type': 'fed_940_type',
-
+ # State
+ 'pa_additional_withholding': 'state_income_tax_additional_withholding',
}
XMLIDS_TO_REMOVE_2020 = [
@@ -31,6 +32,14 @@ XMLIDS_TO_REMOVE_2020 = [
'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',
+ 'l10n_us_pa_hr_payroll.res_partner_pador_unemp_employee',
+ 'l10n_us_pa_hr_payroll.contrib_register_pador_unemp_employee',
+ 'l10n_us_pa_hr_payroll.hr_payroll_pa_unemp_wages',
+ 'l10n_us_pa_hr_payroll.hr_payroll_pa_unemp_employee',
+ 'l10n_us_pa_hr_payroll.hr_payroll_pa_unemp_company',
+ 'l10n_us_pa_hr_payroll.hr_payroll_pa_withhold',
+ 'l10n_us_pa_hr_payroll.hr_payroll_rules_pa_unemp_wages_2018',
+ 'l10n_us_pa_hr_payroll.hr_payroll_rules_pa_inc_withhold_add',
]
XMLIDS_TO_RENAME_2020 = {
@@ -50,4 +59,11 @@ XMLIDS_TO_RENAME_2020 = {
'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',
+ 'l10n_us_pa_hr_payroll.res_partner_pador_unemp_company': 'l10n_us_hr_payroll.res_partner_us_pa_dor',
+ 'l10n_us_pa_hr_payroll.res_partner_pador_withhold': 'l10n_us_hr_payroll.res_partner_us_pa_dor_sit',
+ 'l10n_us_pa_hr_payroll.contrib_register_pador_unemp_company': 'l10n_us_hr_payroll.contrib_register_us_pa_dor',
+ 'l10n_us_pa_hr_payroll.contrib_register_pador_withhold': 'l10n_us_hr_payroll.contrib_register_us_pa_dor_sit',
+ 'l10n_us_pa_hr_payroll.hr_payroll_rules_pa_unemp_employee_2018': 'l10n_us_hr_payroll.hr_payroll_rule_ee_us_pa_suta',
+ 'l10n_us_pa_hr_payroll.hr_payroll_rules_pa_unemp_company_2018': 'l10n_us_hr_payroll.hr_payroll_rule_er_us_pa_suta',
+ 'l10n_us_pa_hr_payroll.hr_payroll_rules_pa_inc_withhold_2018': 'l10n_us_hr_payroll.hr_payroll_rule_ee_us_pa_sit',
}
diff --git a/l10n_us_hr_payroll/models/hr_payslip.py b/l10n_us_hr_payroll/models/hr_payslip.py
index ed37490d..90476930 100644
--- a/l10n_us_hr_payroll/models/hr_payslip.py
+++ b/l10n_us_hr_payroll/models/hr_payslip.py
@@ -9,7 +9,8 @@ from .federal.fed_941 import ee_us_941_fica_ss, \
er_us_941_fica_ss, \
er_us_941_fica_m, \
ee_us_941_fit
-from .state.general import general_state_unemployment
+from .state.general import general_state_unemployment, \
+ general_state_income_withholding
class HRPayslip(models.Model):
@@ -40,6 +41,7 @@ class HRPayslip(models.Model):
'er_us_941_fica_m': er_us_941_fica_m,
'ee_us_941_fit': ee_us_941_fit,
'general_state_unemployment': general_state_unemployment,
+ 'general_state_income_withholding': general_state_income_withholding,
}
def get_year(self):
diff --git a/l10n_us_hr_payroll/models/state/general.py b/l10n_us_hr_payroll/models/state/general.py
index 88acba56..fd259f0e 100644
--- a/l10n_us_hr_payroll/models/state/general.py
+++ b/l10n_us_hr_payroll/models/state/general.py
@@ -5,26 +5,17 @@ from odoo.exceptions import UserError
# _logger = logging.getLogger(__name__)
-def general_state_unemployment(payslip, categories, worked_days, inputs, wage_base=None, wage_start=None, rate=None, state_code=None):
+def _state_applies(payslip, state_code):
+ return state_code == payslip.dict.contract_id.us_payroll_config_value('state_code')
+
+
+def _general_rate(payslip, wage, ytd_wage, wage_base=None, wage_start=None, rate=None):
"""
- Returns SUTA eligible wage and rate.
- WAGE = GROSS - WAGE_US_940_FUTA_EXEMPT
-
- The contract's `futa_type` determines if SUTA should be collected.
-
Function parameters:
wage_base, wage_start, rate can either be strings (rule_parameters) or floats
-
- :return: result, result_rate (wage, percent)
+ :return: result, result_rate(wage, percent)
"""
- if state_code != payslip.dict.contract_id.us_payroll_config_value('state_code'):
- return 0.0, 0.0
-
- # Determine Eligible.
- if payslip.dict.contract_id.futa_type in (payslip.dict.contract_id.FUTA_TYPE_EXEMPT, payslip.dict.contract_id.FUTA_TYPE_BASIC):
- return 0.0, 0.0
-
# Resolve parameters. On exception, return (probably missing a year, would rather not have exception)
if wage_base and isinstance(wage_base, str):
try:
@@ -50,15 +41,6 @@ def general_state_unemployment(payslip, categories, worked_days, inputs, wage_ba
# Rate assumed positive percentage!
rate = -rate
- # Determine Wage
- year = payslip.dict.get_year()
- ytd_wage = payslip.sum_category('GROSS', str(year) + '-01-01', str(year + 1) + '-01-01')
- ytd_wage -= payslip.sum_category('WAGE_US_940_FUTA_EXEMPT', str(year) + '-01-01', str(year + 1) + '-01-01')
- ytd_wage += payslip.dict.contract_id.external_wages
-
- wage = categories.GROSS - categories.WAGE_US_940_FUTA_EXEMPT
- #_logger.warn('ytd_wage: ' + str(ytd_wage) + ' wage: ' + str(wage))
-
if wage_base:
remaining = wage_base - ytd_wage
if remaining < 0.0:
@@ -68,18 +50,74 @@ def general_state_unemployment(payslip, categories, worked_days, inputs, wage_ba
else:
result = wage
- #_logger.warn(' wage_base method result: ' + str(result) + ' rate: ' + str(rate))
+ # _logger.warn(' wage_base method result: ' + str(result) + ' rate: ' + str(rate))
return result, rate
if wage_start:
if ytd_wage >= wage_start:
- #_logger.warn(' wage_start 1 method result: ' + str(wage) + ' rate: ' + str(rate))
+ # _logger.warn(' wage_start 1 method result: ' + str(wage) + ' rate: ' + str(rate))
return wage, rate
if ytd_wage + wage <= wage_start:
- #_logger.warn(' wage_start 2 method result: ' + str(0.0) + ' rate: ' + str(0.0))
+ # _logger.warn(' wage_start 2 method result: ' + str(0.0) + ' rate: ' + str(0.0))
return 0.0, 0.0
- #_logger.warn(' wage_start 3 method result: ' + str((wage - (wage_start - ytd_wage))) + ' rate: ' + str(rate))
+ # _logger.warn(' wage_start 3 method result: ' + str((wage - (wage_start - ytd_wage))) + ' rate: ' + str(rate))
return (wage - (wage_start - ytd_wage)), rate
# If the wage doesn't have a start or a base
- #_logger.warn(' basic result: ' + str(wage) + ' rate: ' + str(rate))
+ # _logger.warn(' basic result: ' + str(wage) + ' rate: ' + str(rate))
return wage, rate
+
+
+def general_state_unemployment(payslip, categories, worked_days, inputs, wage_base=None, wage_start=None, rate=None, state_code=None):
+ """
+ Returns SUTA eligible wage and rate.
+ WAGE = GROSS - WAGE_US_940_FUTA_EXEMPT
+
+ The contract's `futa_type` determines if SUTA should be collected.
+
+ :return: result, result_rate(wage, percent)
+ """
+
+ if not _state_applies(payslip, state_code):
+ return 0.0, 0.0
+
+ # Determine Eligible.
+ if payslip.dict.contract_id.futa_type in (payslip.dict.contract_id.FUTA_TYPE_EXEMPT, payslip.dict.contract_id.FUTA_TYPE_BASIC):
+ return 0.0, 0.0
+
+ # Determine Wage
+ year = payslip.dict.get_year()
+ ytd_wage = payslip.sum_category('GROSS', str(year) + '-01-01', str(year + 1) + '-01-01')
+ ytd_wage -= payslip.sum_category('WAGE_US_940_FUTA_EXEMPT', str(year) + '-01-01', str(year + 1) + '-01-01')
+ ytd_wage += payslip.dict.contract_id.external_wages
+
+ wage = categories.GROSS - categories.WAGE_US_940_FUTA_EXEMPT
+ return _general_rate(payslip, wage, ytd_wage, wage_base=wage_base, wage_start=wage_start, rate=rate)
+
+
+def general_state_income_withholding(payslip, categories, worked_days, inputs, wage_base=None, wage_start=None, rate=None, state_code=None):
+ """
+ Returns SUTA eligible wage and rate.
+ WAGE = GROSS - WAGE_US_941_FIT_EXEMPT
+
+ :return: result, result_rate (wage, percent)
+ """
+ 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
+ year = payslip.dict.get_year()
+ ytd_wage = payslip.sum_category('GROSS', str(year) + '-01-01', str(year + 1) + '-01-01')
+ ytd_wage -= payslip.sum_category('WAGE_US_941_FIT_EXEMPT', str(year) + '-01-01', str(year + 1) + '-01-01')
+ ytd_wage += payslip.dict.contract_id.external_wages
+
+ wage = categories.GROSS - categories.WAGE_US_941_FIT_EXEMPT
+ result, result_rate = _general_rate(payslip, wage, ytd_wage, wage_base=wage_base, wage_start=wage_start, rate=rate)
+ additional = payslip.dict.contract_id.us_payroll_config_value('state_income_tax_additional_withholding')
+ if additional:
+ tax = result * (result_rate / 100.0)
+ tax -= additional # assumed result_rate is negative and that the 'additional' should increase it.
+ return result, ((tax / result) * 100.0)
+ return result, result_rate
diff --git a/l10n_us_hr_payroll/models/us_payroll_config.py b/l10n_us_hr_payroll/models/us_payroll_config.py
index d87ebcac..e75b5210 100644
--- a/l10n_us_hr_payroll/models/us_payroll_config.py
+++ b/l10n_us_hr_payroll/models/us_payroll_config.py
@@ -15,6 +15,8 @@ class HRContractUSPayrollConfig(models.Model):
employee_id = fields.Many2one('hr.employee', string="Employee", required=True)
state_id = fields.Many2one('res.country.state', string="Applied State")
state_code = fields.Char(related='state_id.code')
+ state_income_tax_exempt = fields.Boolean(string='State Income Tax Exempt')
+ state_income_tax_additional_withholding = fields.Float(string='State Income Tax Additional Withholding')
fed_940_type = fields.Selection([
(FUTA_TYPE_EXEMPT, 'Exempt (0%)'),
diff --git a/l10n_us_hr_payroll/tests/__init__.py b/l10n_us_hr_payroll/tests/__init__.py
index 10545ad0..57f8e5e1 100755
--- a/l10n_us_hr_payroll/tests/__init__.py
+++ b/l10n_us_hr_payroll/tests/__init__.py
@@ -6,3 +6,5 @@ from . import test_us_payslip_2020
from . import test_us_fl_florida_payslip_2019
from . import test_us_fl_florida_payslip_2020
+from . import test_us_pa_pennsylvania_payslip_2019
+from . import test_us_pa_pennsylvania_payslip_2020
diff --git a/l10n_us_hr_payroll/tests/common.py b/l10n_us_hr_payroll/tests/common.py
index 78a3d1f5..042302bb 100755
--- a/l10n_us_hr_payroll/tests/common.py
+++ b/l10n_us_hr_payroll/tests/common.py
@@ -174,7 +174,7 @@ class TestUsPayslip(common.TransactionCase):
cache[code] = us_state
return us_state
- def _test_er_suta(self, state_code, rate, date, wage_base=None, **extra_contract):
+ def _test_suta(self, category, state_code, rate, date, wage_base=None, **extra_contract):
if wage_base:
# Slightly larger than 1/2 the wage_base
wage = round(wage_base / 2.0) + 100.0
@@ -197,28 +197,29 @@ class TestUsPayslip(common.TransactionCase):
contract.us_payroll_config_id.fed_940_type = USHRContract.FUTA_TYPE_EXEMPT
payslip.compute_sheet()
cats = self._getCategories(payslip)
- self.assertPayrollEqual(cats.get('ER_US_SUTA', 0.0), 0.0)
+ self.assertPayrollEqual(cats.get(category, 0.0), 0.0)
contract.us_payroll_config_id.fed_940_type = USHRContract.FUTA_TYPE_BASIC
payslip.compute_sheet()
cats = self._getCategories(payslip)
- self.assertPayrollEqual(cats.get('ER_US_SUTA', 0.0), 0.0)
+ self.assertPayrollEqual(cats.get(category, 0.0), 0.0)
# Test Normal
contract.us_payroll_config_id.fed_940_type = USHRContract.FUTA_TYPE_NORMAL
payslip.compute_sheet()
cats = self._getCategories(payslip)
- self.assertPayrollEqual(cats.get('ER_US_SUTA', 0.0), wage * rate)
+ self.assertPayrollEqual(cats.get(category, 0.0), wage * rate)
+ process_payslip(payslip)
+
+ # Second Payslip
+ payslip = self._createPayslip(employee, date + timedelta(days=31), date + timedelta(days=60))
+ payslip.compute_sheet()
+ cats = self._getCategories(payslip)
if wage_base:
- process_payslip(payslip)
-
remaining_unemp_wages = wage_base - wage
self.assertTrue((remaining_unemp_wages * rate) <= 0.01) # less than 0.01 because rate is negative
- payslip = self._createPayslip(employee, date + timedelta(days=31), date + timedelta(days=60))
- payslip.compute_sheet()
- cats = self._getCategories(payslip)
- self.assertPayrollEqual(cats.get('ER_US_SUTA', 0.0), remaining_unemp_wages * rate)
+ self.assertPayrollEqual(cats.get(category, 0.0), remaining_unemp_wages * rate)
# As if they were paid once already, so the first "two payslips" would remove all of the tax obligation
# 1 wage - Payslip (confirmed)
@@ -227,4 +228,12 @@ class TestUsPayslip(common.TransactionCase):
contract.external_wages = wage
payslip.compute_sheet()
cats = self._getCategories(payslip)
- self.assertPayrollEqual(cats.get('ER_US_SUTA', 0.0), 0.0)
+ self.assertPayrollEqual(cats.get(category, 0.0), 0.0)
+ else:
+ self.assertPayrollEqual(cats.get(category, 0.0), wage * rate)
+
+ def _test_er_suta(self, state_code, rate, date, wage_base=None, **extra_contract):
+ self._test_suta('ER_US_SUTA', state_code, rate, date, wage_base=wage_base, **extra_contract)
+
+ def _test_ee_suta(self, state_code, rate, date, wage_base=None, **extra_contract):
+ self._test_suta('EE_US_SUTA', state_code, rate, date, wage_base=wage_base, **extra_contract)
diff --git a/l10n_us_hr_payroll/tests/test_us_fl_florida_payslip_2019.py b/l10n_us_hr_payroll/tests/test_us_fl_florida_payslip_2019.py
index 981e9ce0..419be377 100755
--- a/l10n_us_hr_payroll/tests/test_us_fl_florida_payslip_2019.py
+++ b/l10n_us_hr_payroll/tests/test_us_fl_florida_payslip_2019.py
@@ -1,3 +1,5 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
from .common import TestUsPayslip, process_payslip
from odoo.addons.l10n_us_hr_payroll.models.hr_contract import USHRContract
diff --git a/l10n_us_hr_payroll/tests/test_us_fl_florida_payslip_2020.py b/l10n_us_hr_payroll/tests/test_us_fl_florida_payslip_2020.py
index b32c1030..5952eb1f 100755
--- a/l10n_us_hr_payroll/tests/test_us_fl_florida_payslip_2020.py
+++ b/l10n_us_hr_payroll/tests/test_us_fl_florida_payslip_2020.py
@@ -1,3 +1,5 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
from datetime import date
from .common import TestUsPayslip
diff --git a/l10n_us_hr_payroll/tests/test_us_pa_pennsylvania_payslip_2019.py b/l10n_us_hr_payroll/tests/test_us_pa_pennsylvania_payslip_2019.py
new file mode 100755
index 00000000..ce7e4fb4
--- /dev/null
+++ b/l10n_us_hr_payroll/tests/test_us_pa_pennsylvania_payslip_2019.py
@@ -0,0 +1,33 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from .common import TestUsPayslip, process_payslip
+
+
+class TestUsPAPayslip(TestUsPayslip):
+ ###
+ # Taxes and Rates
+ ###
+ PA_UNEMP_MAX_WAGE = 10000.0
+ ER_PA_UNEMP = -3.6890 / 100.0
+ EE_PA_UNEMP = -0.06 / 100.0
+ PA_INC_WITHHOLD = 3.07
+
+ def test_2019_taxes(self):
+ salary = 4166.67
+ wh = -127.92
+
+
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('PA'))
+
+ self._log('2019 Pennsylvania tax first payslip:')
+ payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['EE_US_SUTA'], cats['GROSS'] * self.EE_PA_UNEMP)
+ self.assertPayrollEqual(cats['ER_US_SUTA'], cats['GROSS'] * self.ER_PA_UNEMP)
+ self.assertPayrollEqual(cats['EE_US_SIT'], wh)
diff --git a/l10n_us_hr_payroll/tests/test_us_pa_pennsylvania_payslip_2020.py b/l10n_us_hr_payroll/tests/test_us_pa_pennsylvania_payslip_2020.py
new file mode 100755
index 00000000..3dd3fd27
--- /dev/null
+++ b/l10n_us_hr_payroll/tests/test_us_pa_pennsylvania_payslip_2020.py
@@ -0,0 +1,43 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from datetime import date
+from .common import TestUsPayslip
+
+
+class TestUsPAPayslip(TestUsPayslip):
+ ###
+ # Taxes and Rates
+ ###
+ PA_UNEMP_MAX_WAGE = 10000.0
+ ER_PA_UNEMP = 3.6890
+ EE_PA_UNEMP = 0.06
+ PA_INC_WITHHOLD = 3.07
+
+ def test_2020_taxes(self):
+ self._test_er_suta('PA', self.ER_PA_UNEMP, date(2020, 1, 1), wage_base=self.PA_UNEMP_MAX_WAGE)
+ self._test_ee_suta('PA', self.EE_PA_UNEMP, date(2020, 1, 1))
+
+ salary = 4166.67
+ wh = -127.92
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('PA'))
+
+ self._log('2019 Pennsylvania tax first payslip:')
+ payslip = self._createPayslip(employee, '2020-01-01', '2020-01-31')
+ payslip.compute_sheet()
+ cats = self._getCategories(payslip)
+ self.assertPayrollEqual(cats['EE_US_SIT'], wh)
+
+ # Test Additional
+ contract.us_payroll_config_id.state_income_tax_additional_withholding = 100.0
+ payslip.compute_sheet()
+ cats = self._getCategories(payslip)
+ self.assertPayrollEqual(cats['EE_US_SIT'], wh - 100.0)
+
+ # Test Exempt
+ contract.us_payroll_config_id.state_income_tax_exempt = True
+ payslip.compute_sheet()
+ cats = self._getCategories(payslip)
+ self.assertPayrollEqual(cats.get('EE_US_SIT', 0.0), 0.0)
diff --git a/l10n_us_hr_payroll/tests/test_us_payslip_2019.py b/l10n_us_hr_payroll/tests/test_us_payslip_2019.py
index e9be35cd..46ab66af 100644
--- a/l10n_us_hr_payroll/tests/test_us_payslip_2019.py
+++ b/l10n_us_hr_payroll/tests/test_us_payslip_2019.py
@@ -211,7 +211,7 @@ class TestUsPayslip2019(TestUsPayslip):
self.assertPayrollEqual(cats['ER_US_940_FUTA'], (self.FUTA_MAX_WAGE - external_wages) * self.FUTA)
def test_2019_taxes_with_full_futa(self):
- self.debug = True
+ self.debug = False
futa_rate = self.FUTA_RATE_BASIC / -100.0
# social security salary
salary = self.FICA_M_ADD_START_WAGE
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 232f27ab..4c6e183c 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,10 @@
No additional fields.
+
+
+
+