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.

+ + + +