From e96ae2dde4b855ff27b507513b40400fb200e021 Mon Sep 17 00:00:00 2001 From: Bhoomi Vaishnani Date: Wed, 1 Apr 2020 14:05:09 -0400 Subject: [PATCH] IMP `l10n_us_hr_payroll` Port `l10n_us_sc_hr_payroll` SC South Carolina including migration --- l10n_us_hr_payroll/__manifest__.py | 1 + .../data/state/sc_south_carolina.xml | 149 ++++++++++++++++++ l10n_us_hr_payroll/models/hr_payslip.py | 2 + .../models/state/sc_south_carolina.py | 48 ++++++ .../models/us_payroll_config.py | 1 + l10n_us_hr_payroll/tests/__init__.py | 3 + .../test_us_sc_south_carolina_payslip_2019.py | 97 ++++++++++++ .../test_us_sc_south_carolina_payslip_2020.py | 34 ++++ .../views/us_payroll_config_views.xml | 5 + 9 files changed, 340 insertions(+) create mode 100644 l10n_us_hr_payroll/data/state/sc_south_carolina.xml create mode 100644 l10n_us_hr_payroll/models/state/sc_south_carolina.py create mode 100644 l10n_us_hr_payroll/tests/test_us_sc_south_carolina_payslip_2019.py create mode 100644 l10n_us_hr_payroll/tests/test_us_sc_south_carolina_payslip_2020.py diff --git a/l10n_us_hr_payroll/__manifest__.py b/l10n_us_hr_payroll/__manifest__.py index fee61b48..c8a0acaf 100644 --- a/l10n_us_hr_payroll/__manifest__.py +++ b/l10n_us_hr_payroll/__manifest__.py @@ -52,6 +52,7 @@ United States of America - Payroll Rules. 'data/state/nm_new_mexico.xml', 'data/state/oh_ohio.xml', 'data/state/pa_pennsylvania.xml', + 'data/state/sc_south_carolina.xml', 'data/state/tx_texas.xml', 'data/state/va_virginia.xml', 'data/state/wa_washington.xml', diff --git a/l10n_us_hr_payroll/data/state/sc_south_carolina.xml b/l10n_us_hr_payroll/data/state/sc_south_carolina.xml new file mode 100644 index 00000000..95867646 --- /dev/null +++ b/l10n_us_hr_payroll/data/state/sc_south_carolina.xml @@ -0,0 +1,149 @@ + + + + + US SC South Carolina SUTA Wage Base + us_sc_suta_wage_base + + + + + 14000.0 + + + + + 14000.0 + + + + + + + + US SC South Carolina SUTA Rate + us_sc_suta_rate + + + + + 1.09 + + + + + 1.09 + + + + + + + US SC South Carolina SIT Tax Rate + us_sc_sit_tax_rate + + + + + [ + ( 2450, 1.1, 0.0), + ( 4900, 3.0, 26.95), + ( 7350, 4.0, 100.45), + ( 9800, 5.0, 198.45), + (12250, 6.0, 320.95), + ('inf', 7.0, 467.95), + ] + + + + + [ + ( 2620, 0.8, 0.0), + ( 5240, 3.0, 57.64), + ( 7860, 4.0, 110.04), + (10490, 5.0, 188.64), + (13110, 6.0, 293.54), + ('inf', 7.0, 424.64), + ] + + + + + + + US SC South Carolina Personal Exemption Rate + us_sc_sit_personal_exemption_rate + + + + + 2510 + + + + + 2590 + + + + + + + US SC South Carolina Standard Deduction Rate + us_sc_sit_standard_deduction_rate + + + + + 3470.0 + + + + + 3820.0 + + + + + + + + US South Carolina - Department of Labor and Industrial Relations - Unemployment Tax + + + + US South Carolina - Department of Taxation - Income Tax + + + + + + + + + + ER: US SC South Carolina State Unemployment + ER_US_SC_SUTA + python + result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_sc_suta_wage_base', rate='us_sc_suta_rate', state_code='SC') + code + result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_sc_suta_wage_base', rate='us_sc_suta_rate', state_code='SC') + + + + + + + + + EE: US SC South Carolina State Income Tax Withholding + EE_US_SC_SIT + python + result, _ = sc_south_carolina_state_income_withholding(payslip, categories, worked_days, inputs) + code + result, result_rate = sc_south_carolina_state_income_withholding(payslip, categories, worked_days, inputs) + + + + + \ No newline at end of file diff --git a/l10n_us_hr_payroll/models/hr_payslip.py b/l10n_us_hr_payroll/models/hr_payslip.py index bf663242..a35f3b38 100644 --- a/l10n_us_hr_payroll/models/hr_payslip.py +++ b/l10n_us_hr_payroll/models/hr_payslip.py @@ -35,6 +35,7 @@ from .state.nc_northcarolina import nc_northcarolina_state_income_withholding from .state.nj_newjersey import nj_newjersey_state_income_withholding from .state.nm_new_mexico import nm_new_mexico_state_income_withholding from .state.oh_ohio import oh_ohio_state_income_withholding +from .state.sc_south_carolina import sc_south_carolina_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 @@ -92,6 +93,7 @@ class HRPayslip(models.Model): 'nj_newjersey_state_income_withholding': nj_newjersey_state_income_withholding, 'nm_new_mexico_state_income_withholding': nm_new_mexico_state_income_withholding, 'oh_ohio_state_income_withholding': oh_ohio_state_income_withholding, + 'sc_south_carolina_state_income_withholding': sc_south_carolina_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/sc_south_carolina.py b/l10n_us_hr_payroll/models/state/sc_south_carolina.py new file mode 100644 index 00000000..169961eb --- /dev/null +++ b/l10n_us_hr_payroll/models/state/sc_south_carolina.py @@ -0,0 +1,48 @@ +# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details. + +from .general import _state_applies, sit_wage + + +def sc_south_carolina_state_income_withholding(payslip, categories, worked_days, inputs): + """ + Returns SIT eligible wage and rate. + WAGE = GROSS + DED_FIT_EXEMPT + + :return: result, result_rate (wage, percent) + """ + state_code = 'SC' + if not _state_applies(payslip, state_code): + return 0.0, 0.0 + + personal_exempt = payslip.contract_id.us_payroll_config_value('state_income_tax_exempt') + if personal_exempt: + return 0.0, 0.0 + + # Determine Wage + wage = sit_wage(payslip, categories) + if not wage: + return 0.0, 0.0 + + pay_periods = payslip.dict.get_pay_periods_in_year() + allowances = payslip.contract_id.us_payroll_config_value('sc_w4_sit_allowances') + tax_rate = payslip.rule_parameter('us_sc_sit_tax_rate') + personal_exemption = payslip.rule_parameter('us_sc_sit_personal_exemption_rate') + deduction = payslip.rule_parameter('us_sc_sit_standard_deduction_rate') + + annual_wage = wage * pay_periods + personal_exemption_amt = allowances * personal_exemption + standard_deduction = 0.0 + if allowances > 0: + if (annual_wage * 0.1) > deduction: + standard_deduction = deduction + else: + standard_deduction = annual_wage * (10 / 100) + taxable_income = annual_wage - personal_exemption_amt - standard_deduction + withholding = 0.0 + last = 0.0 + for cap, rate, flat_amt in tax_rate: + if float(cap) > taxable_income: + withholding = (taxable_income * (rate / 100.0) - flat_amt) + break + withholding /= pay_periods + 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 d548e87c..5abc4759 100644 --- a/l10n_us_hr_payroll/models/us_payroll_config.py +++ b/l10n_us_hr_payroll/models/us_payroll_config.py @@ -202,6 +202,7 @@ 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') + sc_w4_sit_allowances = fields.Integer(string='South Carolina SC W-4 Allowances', help='SC W-4 5.') va_va4_sit_exemptions = fields.Integer(string='Virginia VA-4(P) Personal Exemptions', help='VA-4(P) 1(a)') diff --git a/l10n_us_hr_payroll/tests/__init__.py b/l10n_us_hr_payroll/tests/__init__.py index 526762c3..85be27cb 100755 --- a/l10n_us_hr_payroll/tests/__init__.py +++ b/l10n_us_hr_payroll/tests/__init__.py @@ -80,6 +80,9 @@ from . import test_us_oh_ohio_payslip_2020 from . import test_us_pa_pennsylvania_payslip_2019 from . import test_us_pa_pennsylvania_payslip_2020 +from . import test_us_sc_south_carolina_payslip_2019 +from . import test_us_sc_south_carolina_payslip_2020 + from . import test_us_tx_texas_payslip_2019 from . import test_us_tx_texas_payslip_2020 diff --git a/l10n_us_hr_payroll/tests/test_us_sc_south_carolina_payslip_2019.py b/l10n_us_hr_payroll/tests/test_us_sc_south_carolina_payslip_2019.py new file mode 100644 index 00000000..793f84c4 --- /dev/null +++ b/l10n_us_hr_payroll/tests/test_us_sc_south_carolina_payslip_2019.py @@ -0,0 +1,97 @@ +# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details. + +from .common import TestUsPayslip, process_payslip + + +class TestUsSCPayslip(TestUsPayslip): + + # Taxes and Rates + SC_UNEMP_MAX_WAGE = 14000.0 + US_SC_UNEMP = -1.09 / 100 + US_SC_exemption_amount = 2510.00 + + def test_2019_taxes_weekly(self): + # We will hand calculate the amount to test for state withholding. + schedule_pay = 'weekly' + salary = 50000.00 # Employee is paid 50000 per week to be in top tax bracket + allowances = 2 + # Calculate annual wages + annual = 50000 * 52.0 + # From our annual we deduct personal exemption amounts. + # We deduct 2510.00 per exemption. Since we have two exemptions: + personal_exemption = self.US_SC_exemption_amount * allowances # 5020.0 + # From annual, we will also deduct a standard_deduction of 3470.00 or .1 of salary, which ever + # is small -> if 1 or more exemptions, else 0 + standard_deduction = 3470.00 + taxable_income = annual - personal_exemption - standard_deduction # 2591510.0 + # We then calculate the amounts off the SC tax pdf tables. + # 2591478.0 is in the highest bracket + test_amt = (taxable_income * (7.0 / 100.0)) - 467.95 + test_amt = 180935.51 + # Make it per period then negative + test_amt = (test_amt / 52.0) # Divided by 52 since it is weekly. + # test_amt = 3479.52 + test_amt = -test_amt + + employee = self._createEmployee() + contract = self._createContract(employee, + wage=salary, + state_id=self.get_us_state('SC'), + state_income_tax_exempt=False, + sc_w4_sit_allowances=allowances, + schedule_pay=schedule_pay) + + self._log('2019 South Carolina tax first payslip:') + payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31') + payslip.compute_sheet() + cats = self._getCategories(payslip) + + self.assertPayrollAlmostEqual(cats['ER_US_SUTA'], self.SC_UNEMP_MAX_WAGE * self.US_SC_UNEMP) + self.assertPayrollAlmostEqual(cats['EE_US_SIT'], test_amt) + + process_payslip(payslip) + + remaining_SC_UNEMP_wages = self.SC_UNEMP_MAX_WAGE - annual if (annual < self.SC_UNEMP_MAX_WAGE) \ + else 0.00 + + self._log('2019 South Carolina tax second payslip:') + + payslip = self._createPayslip(employee, '2019-02-01', '2019-02-28') + payslip.compute_sheet() + cats = self._getCategories(payslip) + + self.assertEqual(0.0, remaining_SC_UNEMP_wages) + self.assertPayrollEqual(cats['ER_US_SUTA'], remaining_SC_UNEMP_wages * self.US_SC_UNEMP) + + def test_2019_taxes_filing_status(self): + salary = 20000.00 # Wages per pay period + schedule_pay = 'monthly' + annual = salary * 12 + allowances = 1 + # Hand Calculations + personal_exemption = 2510.00 + standard_deduction = min(3470.00, .1 * annual) # 3470.0 but min is shown for the process + taxable = annual - personal_exemption - standard_deduction + # taxable = 234020 + test_amt = ((taxable) * (7.0 / 100.0)) - 467.95 # 15991.850000000002 + test_amt = test_amt / 12.0 # Put it into monthly -> 1332.654166666667 + # Make it negative + test_amt = -test_amt + + employee = self._createEmployee() + contract = self._createContract(employee, + wage=salary, + state_id=self.get_us_state('SC'), + state_income_tax_exempt=False, + sc_w4_sit_allowances=allowances, + schedule_pay=schedule_pay) + + self._log('2019 South Carolina 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.SC_UNEMP_MAX_WAGE * self.US_SC_UNEMP) + self.assertPayrollAlmostEqual(cats['EE_US_SIT'], test_amt) + + process_payslip(payslip) diff --git a/l10n_us_hr_payroll/tests/test_us_sc_south_carolina_payslip_2020.py b/l10n_us_hr_payroll/tests/test_us_sc_south_carolina_payslip_2020.py new file mode 100644 index 00000000..e17a8052 --- /dev/null +++ b/l10n_us_hr_payroll/tests/test_us_sc_south_carolina_payslip_2020.py @@ -0,0 +1,34 @@ +# 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 TestUsSCPayslip(TestUsPayslip): + ### + # 2020 Taxes and Rates + ### + SC_UNEMP_MAX_WAGE = 14000.0 + SC_UNEMP = 1.09 + # Calculation based on https://dor.sc.gov/forms-site/Forms/WH1603F_2020.pdf + + def _test_sit(self, wage, exempt, allowances, schedule_pay, date_start, expected_withholding): + employee = self._createEmployee() + contract = self._createContract(employee, + wage=wage, + state_id=self.get_us_state('SC'), + state_income_tax_exempt=exempt, + sc_w4_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('SC', self.SC_UNEMP, date(2020, 1, 1), wage_base=self.SC_UNEMP_MAX_WAGE) + self._test_sit(750.0, False, 3.0, 'weekly', date(2020, 1, 1), 28.73) + self._test_sit(800.0, True, 0.0, 'weekly', date(2020, 1, 1), 0.00) + self._test_sit(9000.0, False, 0.0, 'monthly', date(2020, 1, 1), 594.61) 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 7865d1f8..a517bdb4 100644 --- a/l10n_us_hr_payroll/views/us_payroll_config_views.xml +++ b/l10n_us_hr_payroll/views/us_payroll_config_views.xml @@ -191,6 +191,11 @@ + +

Form SC W-4 - State Income Tax

+ + +

No additional fields.