diff --git a/l10n_us_hr_payroll/__init__.py b/l10n_us_hr_payroll/__init__.py
index 09434554..013f4e73 100644
--- a/l10n_us_hr_payroll/__init__.py
+++ b/l10n_us_hr_payroll/__init__.py
@@ -1,3 +1,12 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
from . import models
+
+def _post_install_hook(cr, registry):
+ """
+ This method will set the default for the Payslip Sum Behavior
+ """
+ cr.execute("SELECT id FROM ir_config_parameter WHERE key = 'hr_payroll.payslip.sum_behavior';")
+ existing = cr.fetchall()
+ if not existing:
+ cr.execute("INSERT INTO ir_config_parameter (key, value) VALUES ('hr_payroll.payslip.sum_behavior', 'date');")
diff --git a/l10n_us_hr_payroll/__manifest__.py b/l10n_us_hr_payroll/__manifest__.py
index a748aae6..32c55af1 100644
--- a/l10n_us_hr_payroll/__manifest__.py
+++ b/l10n_us_hr_payroll/__manifest__.py
@@ -8,6 +8,7 @@
'depends': [
'hr_payroll',
'hr_contract_reports',
+ 'hibou_professional',
],
'description': """
United States of America - Payroll Rules.
@@ -25,11 +26,60 @@ United States of America - Payroll Rules.
'data/federal/fed_941_fica_rules.xml',
'data/federal/fed_941_fit_parameters.xml',
'data/federal/fed_941_fit_rules.xml',
+ 'data/state/ak_alaska.xml',
+ 'data/state/al_alabama.xml',
+ 'data/state/ar_arkansas.xml',
+ 'data/state/az_arizona.xml',
+ 'data/state/ca_california.xml',
+ 'data/state/co_colorado.xml',
+ 'data/state/ct_connecticut.xml',
+ 'data/state/de_delaware.xml',
+ 'data/state/fl_florida.xml',
+ 'data/state/ga_georgia.xml',
+ 'data/state/hi_hawaii.xml',
+ 'data/state/ia_iowa.xml',
+ 'data/state/id_idaho.xml',
+ 'data/state/il_illinois.xml',
+ 'data/state/in_indiana.xml',
+ 'data/state/ks_kansas.xml',
+ 'data/state/ky_kentucky.xml',
+ 'data/state/la_louisiana.xml',
+ 'data/state/me_maine.xml',
+ 'data/state/mi_michigan.xml',
+ 'data/state/mn_minnesota.xml',
+ 'data/state/mo_missouri.xml',
+ 'data/state/ms_mississippi.xml',
+ 'data/state/mt_montana.xml',
+ 'data/state/nc_northcarolina.xml',
+ 'data/state/nd_north_dakota.xml',
+ 'data/state/ne_nebraska.xml',
+ 'data/state/nh_new_hampshire.xml',
+ 'data/state/nj_newjersey.xml',
+ 'data/state/nm_new_mexico.xml',
+ 'data/state/nv_nevada.xml',
+ 'data/state/ny_new_york.xml',
+ 'data/state/oh_ohio.xml',
+ 'data/state/ok_oklahoma.xml',
+ 'data/state/pa_pennsylvania.xml',
+ 'data/state/ri_rhode_island.xml',
+ 'data/state/sc_south_carolina.xml',
+ 'data/state/sd_south_dakota.xml',
+ 'data/state/tn_tennessee.xml',
+ 'data/state/tx_texas.xml',
+ 'data/state/ut_utah.xml',
+ 'data/state/vt_vermont.xml',
+ 'data/state/va_virginia.xml',
+ 'data/state/wa_washington.xml',
+ 'data/state/wi_wisconsin.xml',
+ 'data/state/wv_west_virginia.xml',
+ 'data/state/wy_wyoming.xml',
'views/hr_contract_views.xml',
+ 'views/res_config_settings_views.xml',
'views/us_payroll_config_views.xml',
],
'demo': [
],
'auto_install': False,
+ 'post_init_hook': '_post_install_hook',
'license': 'OPL-1',
}
diff --git a/l10n_us_hr_payroll/data/base.xml b/l10n_us_hr_payroll/data/base.xml
index 6838f283..2e29934e 100644
--- a/l10n_us_hr_payroll/data/base.xml
+++ b/l10n_us_hr_payroll/data/base.xml
@@ -17,4 +17,112 @@
]"/>
+
+
+ EE: State Unemployment SUTA
+ EE_US_SUTA
+
+
+
+ ER: State Unemployment SUTA
+ ER_US_SUTA
+
+
+
+
+
+ EE: State Income Tax Withholding
+ EE_US_SIT
+
+
+
+
+
+
+
+ Wage: US FIT Exempt
+ ALW_FIT_EXEMPT
+
+
+
+
+ Wage: US FIT & FICA Exempt
+ ALW_FIT_FICA_EXEMPT
+
+
+
+
+ Wage: US FIT & FUTA Exempt
+ ALW_FIT_FUTA_EXEMPT
+
+
+
+
+ Wage: US FIT & FICA & FUTA Exempt
+ ALW_FIT_FICA_FUTA_EXEMPT
+
+
+
+
+ Wage: US FICA Exempt
+ ALW_FICA_EXEMPT
+
+
+
+
+ Wage: US FICA & FUTA Exempt
+ ALW_FICA_FUTA_EXEMPT
+
+
+
+
+ Wage: US FUTA Exempt
+ ALW_FUTA_EXEMPT
+
+
+
+
+
+
+ Deduction: US FIT Exempt
+ DED_FIT_EXEMPT
+
+
+
+
+ Deduction: US FIT & FICA Exempt
+ DED_FIT_FICA_EXEMPT
+
+
+
+
+ Deduction: US FIT & FUTA Exempt
+ DED_FIT_FUTA_EXEMPT
+
+
+
+
+ Deduction: US FIT & FICA & FUTA Exempt
+ DED_FIT_FICA_FUTA_EXEMPT
+
+
+
+
+ Deduction: US FICA Exempt
+ DED_FICA_EXEMPT
+
+
+
+
+ Deduction: US FICA & FUTA Exempt
+ DED_FICA_FUTA_EXEMPT
+
+
+
+
+ Deduction: US FUTA Exempt
+ DED_FUTA_EXEMPT
+
+
+
\ No newline at end of file
diff --git a/l10n_us_hr_payroll/data/federal/fed_940_futa_rules.xml b/l10n_us_hr_payroll/data/federal/fed_940_futa_rules.xml
index 5b315100..6a153efb 100644
--- a/l10n_us_hr_payroll/data/federal/fed_940_futa_rules.xml
+++ b/l10n_us_hr_payroll/data/federal/fed_940_futa_rules.xml
@@ -11,12 +11,6 @@
-
-
- WAGE: Federal 940 FUTA Exempt
- WAGE_US_940_FUTA_EXEMPT
-
-
diff --git a/l10n_us_hr_payroll/data/federal/fed_941_fica_rules.xml b/l10n_us_hr_payroll/data/federal/fed_941_fica_rules.xml
index 324958a4..64c91607 100644
--- a/l10n_us_hr_payroll/data/federal/fed_941_fica_rules.xml
+++ b/l10n_us_hr_payroll/data/federal/fed_941_fica_rules.xml
@@ -17,12 +17,6 @@
-
-
- WAGE: Federal 941 FICA Exempt
- WAGE_US_941_FICA_EXEMPT
-
-
diff --git a/l10n_us_hr_payroll/data/federal/fed_941_fit_rules.xml b/l10n_us_hr_payroll/data/federal/fed_941_fit_rules.xml
index a7751adf..4e3cb28c 100644
--- a/l10n_us_hr_payroll/data/federal/fed_941_fit_rules.xml
+++ b/l10n_us_hr_payroll/data/federal/fed_941_fit_rules.xml
@@ -1,10 +1,5 @@
-
-
- WAGE: Federal 941 Income Tax Exempt
- WAGE_US_941_FIT_EXEMPT
-
EE: Federal 941 Income Tax Withholding
diff --git a/l10n_us_hr_payroll/data/state/ak_alaska.xml b/l10n_us_hr_payroll/data/state/ak_alaska.xml
new file mode 100644
index 00000000..2c995088
--- /dev/null
+++ b/l10n_us_hr_payroll/data/state/ak_alaska.xml
@@ -0,0 +1,95 @@
+
+
+
+
+ US AK Alaska SUTA Wage Base
+ us_ak_suta_wage_base
+
+
+
+
+ 39900.00
+
+
+
+
+ 41500.00
+
+
+
+
+
+
+
+ US AK Alaska SUTA Rate
+ us_ak_suta_rate
+
+
+
+
+ 1.780
+
+
+
+
+ 1.590
+
+
+
+
+
+
+ US AK Alaska SUTA Rate EE
+ us_ak_suta_ee_rate
+
+
+
+
+ 0.500
+
+
+
+
+ 0.500
+
+
+
+
+
+
+
+ US Alaska - Department of Labor and Workforce Development (ADLWD) - Unemployment Tax
+
+
+
+
+
+
+
+
+
+ ER: US AK Alaska State Unemployment
+ ER_US_AK_SUTA
+ python
+ result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_ak_suta_wage_base', rate='us_ak_suta_rate', state_code='AK')
+ code
+ result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_ak_suta_wage_base', rate='us_ak_suta_rate', state_code='AK')
+
+
+
+
+
+
+
+
+ EE: US AK Alaska State Unemployment (UC-2)
+ EE_US_AK_SUTA
+ python
+ result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_ak_suta_wage_base', rate='us_ak_suta_ee_rate', state_code='AK')
+ code
+ result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_ak_suta_wage_base', rate='us_ak_suta_ee_rate', state_code='AK')
+
+
+
+
+
\ No newline at end of file
diff --git a/l10n_us_hr_payroll/data/state/al_alabama.xml b/l10n_us_hr_payroll/data/state/al_alabama.xml
new file mode 100644
index 00000000..fc3b3af1
--- /dev/null
+++ b/l10n_us_hr_payroll/data/state/al_alabama.xml
@@ -0,0 +1,219 @@
+
+
+
+
+ US AL Alabama SUTA Wage Base
+ us_al_suta_wage_base
+
+
+
+
+ 8000.0
+
+
+
+
+ 8000.0
+
+
+
+
+
+
+
+ US AL Alabama SUTA Rate
+ us_al_suta_rate
+
+
+
+
+ 2.7
+
+
+
+
+ 2.7
+
+
+
+
+
+
+ US AL Alabama SIT Tax Rate
+ us_al_sit_tax_rate
+
+
+
+
+ {
+ '0': [
+ ( 500, 2),
+ ( 3000, 4),
+ ('inf', 5),
+ ],
+ 'M': [
+ ( 1000, 2),
+ ( 6000, 4),
+ ('inf', 5),
+ ],
+ }
+
+
+
+
+ {
+ '0' : [
+ ( 500, 2),
+ ( 2500, 4),
+ ('inf', 5),
+ ],
+ 'M': [
+ ( 1000, 2),
+ ( 5000, 4),
+ ('inf', 5),
+ ],
+ }
+
+
+
+
+
+
+ US AL Alabama Dependent Rate
+ us_al_sit_dependent_rate
+
+
+
+
+
+
+ [
+ ( 1000, 20000),
+ ( 500, 100000),
+ ( 300, 'inf'),
+ ]
+
+
+
+
+
+
+ [
+ ( 1000, 20000),
+ ( 500, 100000),
+ ( 300, 'inf'),
+ ]
+
+
+
+
+
+
+ US AL Alabama Standard Deduction Rate
+ us_al_sit_standard_deduction_rate
+
+
+
+
+
+
+ {
+ '0': ((23499.0, 2500.0), (33000.0, 2500.0, 25.0, 500.0), ('inf', 2000.0)),
+ 'S': ((23499.0, 2500.0), (33000.0, 2500.0, 25.0, 500.0), ('inf', 2000.0)),
+ 'MS': ((10749.0, 3750.0), (15500.0, 3750.0, 88.0, 250.0), ('inf', 2000.0)),
+ 'M': ((23499.0, 7500.0), (33000.0, 7500.0, 175.0, 500.0), ('inf', 4000.0)),
+ 'H': ((23499.0, 4700.0), (33000.0, 7500.0, 175.0, 500.0), ('inf', 4000.0)),
+ }
+
+
+
+
+
+
+ {
+ '0': ((23499.0, 2500.0), (33000.0, 2500.0, 25.0, 500.0), ('inf', 2000.0)),
+ 'S': ((23499.0, 2500.0), (33000.0, 2500.0, 25.0, 500.0), ('inf', 2000.0)),
+ 'MS': ((10749.0, 3750.0), (15500.0, 3750.0, 88.0, 250.0), ('inf', 2000.0)),
+ 'M': ((23499.0, 7500.0), (33000.0, 7500.0, 175.0, 500.0), ('inf', 4000.0)),
+ 'H': ((23499.0, 4700.0), (33000.0, 7500.0, 175.0, 500.0), ('inf', 4000.0)),
+ }
+
+
+
+
+
+
+ US AL Alabama Personal Exemption Rate
+ us_al_sit_personal_exemption_rate
+
+
+
+
+
+
+ {
+ '0' : 0,
+ 'S' : 1500,
+ 'MS': 1500,
+ 'M' : 3000,
+ 'H' : 3000,
+ }
+
+
+
+
+
+
+ {
+ '0' : 0,
+ 'S' : 1500,
+ 'MS': 1500,
+ 'M' : 3000,
+ 'H' : 3000,
+ }
+
+
+
+
+
+
+
+ US Alabama - Department of Economic Security (IDES) - Unemployment Tax
+
+
+
+ US Alabama - Department of Revenue (IDOR) - Income Tax
+
+
+
+
+
+
+
+
+
+ ER: US AL Alabama State Unemployment
+ ER_US_AL_SUTA
+ python
+ result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_al_suta_wage_base', rate='us_al_suta_rate', state_code='AL')
+ code
+ result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_al_suta_wage_base', rate='us_al_suta_rate', state_code='AL')
+
+
+
+
+
+
+
+
+ EE: US AL Alabama State Income Tax Withholding
+ EE_US_AL_SIT
+ python
+ result, _ = al_alabama_state_income_withholding(payslip, categories, worked_days, inputs)
+ code
+ result, result_rate = al_alabama_state_income_withholding(payslip, categories, worked_days, inputs)
+
+
+
+
+
\ No newline at end of file
diff --git a/l10n_us_hr_payroll/data/state/ar_arkansas.xml b/l10n_us_hr_payroll/data/state/ar_arkansas.xml
new file mode 100644
index 00000000..3e5b8211
--- /dev/null
+++ b/l10n_us_hr_payroll/data/state/ar_arkansas.xml
@@ -0,0 +1,145 @@
+
+
+
+
+ US AR Arkansas SUTA Wage Base
+ us_ar_suta_wage_base
+
+
+
+
+ 10000.0
+
+
+
+
+ 8000.0
+
+
+
+
+
+
+
+ US AR Arkansas SUTA Rate
+ us_ar_suta_rate
+
+
+
+
+ 3.2
+
+
+
+
+ 2.9
+
+
+
+
+
+
+ US AR Arkansas SIT Tax Rate
+ us_ar_sit_tax_rate
+
+
+
+
+ [
+ ( 4599, 0.0, 0.00),
+ ( 9099, 2.0, 91.98),
+ ( 13699, 3.0, 182.97),
+ ( 22599, 3.4, 237.77),
+ ( 37899, 5.0, 421.46),
+ ( 80800, 5.9, 762.55),
+ ( 81800, 6.6, 1243.40),
+ ( 82800, 6.6, 1143.40),
+ ( 84100, 6.6, 1043.40),
+ ( 85200, 6.6, 943.40),
+ ( 86200, 6.6, 843.40),
+ ( 'inf', 6.6, 803.40),
+ ]
+
+
+
+
+
+
+ [
+ ( 4599, 0.0, 0.00),
+ ( 9099, 2.0, 91.98),
+ ( 13699, 3.0, 182.97),
+ ( 22599, 3.4, 237.77),
+ ( 37899, 5.0, 421.46),
+ ( 80800, 5.9, 762.55),
+ ( 81800, 6.6, 1243.40),
+ ( 82800, 6.6, 1143.40),
+ ( 84100, 6.6, 1043.40),
+ ( 85200, 6.6, 943.40),
+ ( 86200, 6.6, 843.40),
+ ( 'inf', 6.6, 803.40),
+ ]
+
+
+
+
+
+
+ US AR Arkansas Allowances Rate
+ us_ar_sit_standard_deduction_rate
+
+
+
+
+ 2200.0
+
+
+
+
+ 2200.0
+
+
+
+
+
+
+
+ US Arkansas - Department of Workforce Solutions - Unemployment Tax
+
+
+
+ US Arkansas - Department of Financial Administration - Income Tax
+
+
+
+
+
+
+
+
+
+ ER: US AR Arkansas State Unemployment
+ ER_US_AR_SUTA
+ python
+ result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_ar_suta_wage_base', rate='us_ar_suta_rate', state_code='AR')
+ code
+ result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_ar_suta_wage_base', rate='us_ar_suta_rate', state_code='AR')
+
+
+
+
+
+
+
+
+ EE: US AR Arkansas State Income Tax Withholding
+ EE_US_AR_SIT
+ python
+ result, _ = ar_arkansas_state_income_withholding(payslip, categories, worked_days, inputs)
+ code
+ result, result_rate = ar_arkansas_state_income_withholding(payslip, categories, worked_days, inputs)
+
+
+
+
+
\ No newline at end of file
diff --git a/l10n_us_hr_payroll/data/state/az_arizona.xml b/l10n_us_hr_payroll/data/state/az_arizona.xml
new file mode 100644
index 00000000..80b800c1
--- /dev/null
+++ b/l10n_us_hr_payroll/data/state/az_arizona.xml
@@ -0,0 +1,81 @@
+
+
+
+
+ US AZ Arizona SUTA Wage Base
+ us_az_suta_wage_base
+
+
+
+
+ 7000.0
+
+
+
+
+ 7000.0
+
+
+
+
+
+
+
+ US AZ Arizona SUTA Rate
+ us_az_suta_rate
+
+
+
+
+ 2.0
+
+
+
+
+ 2.0
+
+
+
+
+
+
+
+ US Arizona - Department of Economic Security (ADES) - Unemployment Tax
+
+
+
+ US Arizona - Department of Revenue (ADOR) - Income Tax
+
+
+
+
+
+
+
+
+
+ ER: US AZ Arizona State Unemployment
+ ER_US_AZ_SUTA
+ python
+ result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_az_suta_wage_base', rate='us_az_suta_rate', state_code='AZ')
+ code
+ result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_az_suta_wage_base', rate='us_az_suta_rate', state_code='AZ')
+
+
+
+
+
+
+
+
+ EE: US AZ Arizona State Income Tax Withholding
+ EE_US_AZ_SIT
+ python
+ result, _ = az_arizona_state_income_withholding(payslip, categories, worked_days, inputs)
+ code
+ result, result_rate = az_arizona_state_income_withholding(payslip, categories, worked_days, inputs)
+
+
+
+
+
\ No newline at end of file
diff --git a/l10n_us_hr_payroll/data/state/ca_california.xml b/l10n_us_hr_payroll/data/state/ca_california.xml
new file mode 100644
index 00000000..2a907b3a
--- /dev/null
+++ b/l10n_us_hr_payroll/data/state/ca_california.xml
@@ -0,0 +1,829 @@
+
+
+
+
+ US CA California SUTA Wage Base
+ us_ca_suta_wage_base
+
+
+
+
+ 7000.0
+
+
+
+
+ 7000.0
+
+
+
+
+
+
+
+ US CA California SUTA Rate
+ us_ca_suta_rate
+
+
+
+
+ 3.5
+
+
+
+
+ 3.4
+
+
+
+
+
+
+
+ US CA California SUTA ETT Rate
+ us_ca_suta_ett_rate
+
+
+
+
+ 0.1
+
+
+
+
+ 0.1
+
+
+
+
+
+
+
+ US CA California SUTA SDI Rate
+ us_ca_suta_sdi_rate
+
+
+
+
+ 1.0
+
+
+
+
+ 1.0
+
+
+
+
+
+
+ US CA California SIT Tax Rate
+ us_ca_sit_tax_rate
+
+
+
+
+ {
+ 'head_household': {
+ 'weekly': (
+ ( 316, 0.0110, 0.00),
+ ( 750, 0.0220, 3.48),
+ ( 967, 0.0440, 13.03),
+ ( 1196, 0.0660, 22.58),
+ ( 1413, 0.0880, 37.69),
+ ( 7212, 0.1023, 56.79),
+ ( 8654, 0.1133, 650.03),
+ (14423, 0.1243, 813.41),
+ (19231, 0.1353, 1530.50),
+ ('inf', 0.1463, 2181.02),
+ ),
+ 'bi-weekly': (
+ ( 632, 0.0110, 0.00),
+ ( 1500, 0.0220, 6.95),
+ ( 1934, 0.0440, 26.05),
+ ( 2392, 0.0660, 45.15),
+ ( 2826, 0.0880, 75.38),
+ (14424, 0.1023, 113.57),
+ (17308, 0.1133, 1300.05),
+ (28846, 0.1243, 1626.81),
+ (38462, 0.1353, 3060.98),
+ ('inf', 0.1463, 4362.02),
+ ),
+ 'semi-monthly': (
+ ( 686, 0.0110, 0.00),
+ ( 1625, 0.0220, 7.55),
+ ( 2094, 0.0440, 28.21),
+ ( 2592, 0.0660, 48.85),
+ ( 3062, 0.0880, 81.72),
+ (15625, 0.1023, 123.08),
+ (18750, 0.1133, 1408.27),
+ (31250, 0.1243, 1762.33),
+ (41667, 0.1353, 3316.08),
+ ('inf', 0.1463, 4725.50),
+ ),
+ 'monthly': (
+ ( 1372, 0.0110, 0.00),
+ ( 3250, 0.0220, 15.09),
+ ( 4188, 0.0440, 56.41),
+ ( 5184, 0.0660, 97.68),
+ ( 6124, 0.0880, 163.42),
+ (31250, 0.1023, 246.14),
+ (37500, 0.1133, 2816.53),
+ (62500, 0.1243, 3524.66),
+ (83334, 0.1353, 6632.16),
+ ('inf', 0.1463, 9451.00),
+ ),
+ 'quarterly': (
+ ( 4114, 0.0110, 0.00),
+ ( 9748, 0.0220, 45.25),
+ ( 12566, 0.0440, 169.20),
+ ( 15552, 0.0660, 293.19),
+ ( 18369, 0.0880, 490.27),
+ ( 93751, 0.1023, 738.17),
+ (112501, 0.1133, 8449.75),
+ (187501, 0.1243, 10574.13),
+ (250000, 0.1353, 19896.63),
+ ( 'inf', 0.1463, 28352.74),
+ ),
+ 'semi-annual': (
+ ( 8228, 0.0110, 0.00),
+ ( 19496, 0.0220, 90.51),
+ ( 25132, 0.0440, 338.41),
+ ( 31104, 0.0660, 586.39),
+ ( 36738, 0.0880, 980.54),
+ (187502, 0.1023, 1476.33),
+ (225002, 0.1133, 16899.49),
+ (375002, 0.1243, 21148.24),
+ (500000, 0.1353, 39793.24),
+ ( 'inf', 0.1463, 56705.47),
+ ),
+ 'annually': (
+ ( 16457, 0.0110, 0.00),
+ ( 38991, 0.0220, 181.03),
+ ( 50264, 0.0440, 676.78),
+ ( 62206, 0.0660, 1172.79),
+ ( 73477, 0.0880, 1960.96),
+ ( 375002, 0.1023, 2952.81),
+ ( 450003, 0.1133, 33798.82),
+ ( 750003, 0.1243, 42296.43),
+ (1000000, 0.1353, 79586.43),
+ ( 'inf', 0.1463, 113411.02),
+ ),
+ },
+ 'married': {
+ 'weekly': (
+ ( 316, 0.0110, 0.00),
+ ( 750, 0.0220, 3.48),
+ ( 1184, 0.0440, 13.03),
+ ( 1642, 0.0660, 32.13),
+ ( 2076, 0.0880, 62.36),
+ (10606, 0.1023, 100.55),
+ (12726, 0.1133, 973.17),
+ (19231, 0.1243, 1213.37),
+ (21210, 0.1353, 2021.94),
+ ('inf', 0.1463, 2289.70),
+ ),
+ 'bi-weekly': (
+ ( 632, 0.0110, 0.00),
+ ( 1500, 0.0220, 6.95),
+ ( 2368, 0.0440, 26.05),
+ ( 3284, 0.0660, 64.24),
+ ( 4152, 0.0880, 124.70),
+ (21212, 0.1023, 201.08),
+ (25452, 0.1133, 1946.32),
+ (38462, 0.1243, 2426.71),
+ (42420, 0.1353, 4043.85),
+ ('inf', 0.1463, 4579.37),
+ ),
+ 'semi-monthly': (
+ ( 686, 0.0110, 0.00),
+ ( 1624, 0.0220, 7.55),
+ ( 2564, 0.0440, 28.19),
+ ( 3560, 0.0660, 69.55),
+ ( 4498, 0.0880, 135.29),
+ (22978, 0.1023, 217.83),
+ (27574, 0.1133, 2108.33),
+ (41667, 0.1243, 2629.06),
+ (45956, 0.1353, 4380.82),
+ ('inf', 0.1463, 4961.12),
+ ),
+ 'monthly': (
+ ( 1372, 0.0110, 0.00),
+ ( 3248, 0.0220, 15.09),
+ ( 5128, 0.0440, 56.36),
+ ( 7120, 0.0660, 139.08),
+ ( 8996, 0.0880, 270.55),
+ (45956, 0.1023, 435.64),
+ (55148, 0.1133, 4216.65),
+ (83334, 0.1243, 5258.10),
+ (91912, 0.1353, 8761.62),
+ ('inf', 0.1463, 9922.22),
+ ),
+ 'quarterly': (
+ ( 4112, 0.0110, 0.00),
+ ( 9748, 0.0220, 45.23),
+ ( 15384, 0.0440, 169.22),
+ ( 21356, 0.0660, 417.20),
+ ( 26990, 0.0880, 811.35),
+ (137870, 0.1023, 1307.14),
+ (165442, 0.1133, 12650.16),
+ (250000, 0.1243, 15774.07),
+ (275736, 0.1353, 26284.63),
+ ( 'inf', 0.1463, 29766.71),
+ ),
+ 'semi-annual': (
+ ( 8224, 0.0110, 0.00),
+ ( 19496, 0.0220, 90.46),
+ ( 30768, 0.0440, 338.44),
+ ( 42712, 0.0660, 834.41),
+ ( 53980, 0.0880, 1622.71),
+ (275740, 0.1023, 2614.29),
+ (330884, 0.1133, 25300.34),
+ (500000, 0.1243, 31548.16),
+ (551472, 0.1353, 52569.28),
+ ( 'inf', 0.1463, 59533.44),
+ ),
+ 'annually': (
+ ( 16446, 0.0110, 0.00),
+ ( 38990, 0.0220, 180.91),
+ ( 61538, 0.0440, 676.88),
+ ( 85422, 0.0660, 1668.99),
+ ( 107960, 0.0880, 3245.33),
+ ( 551476, 0.1023, 5228.67),
+ ( 661768, 0.1133, 50600.36),
+ (1000000, 0.1243, 63096.44),
+ (1102946, 0.1353, 105138.68),
+ ( 'inf', 0.1463, 119067.26),
+ ),
+ },
+ 'single': {
+ 'weekly': (
+ ( 158, 0.0110, 0.00),
+ ( 375, 0.0220, 1.74),
+ ( 592, 0.0440, 6.51),
+ ( 821, 0.0660, 16.06),
+ ( 1038, 0.0880, 31.17),
+ ( 5303, 0.1023, 50.27),
+ ( 6363, 0.1133, 486.58),
+ (10605, 0.1243, 606.68),
+ (19231, 0.1353, 1133.96),
+ ('inf', 0.1463, 2301.06),
+ ),
+ 'bi-weekly': (
+ ( 316, 0.0110, 0.00),
+ ( 750, 0.0220, 3.48),
+ ( 1184, 0.0440, 13.03),
+ ( 1642, 0.066, 32.13),
+ ( 2076, 0.0880, 62.36),
+ (10606, 0.1023, 100.55),
+ (12726, 0.1133, 973.17),
+ (21210, 0.1243, 1213.37),
+ (38462, 0.1353, 2267.93),
+ ('inf', 0.1463, 4602.13),
+ ),
+ 'semi-monthly': (
+ ( 343, 0.0110, 0.00),
+ ( 812, 0.0220, 3.77),
+ ( 1282, 0.0440, 14.09),
+ ( 1780, 0.0660, 34.77),
+ ( 2249, 0.0880, 67.64),
+ (11489, 0.1023, 108.91),
+ (13787, 0.1133, 1054.16),
+ (22978, 0.1243, 1314.52),
+ (41667, 0.1353, 2456.96),
+ ('inf', 0.1463, 4985.58),
+ ),
+ 'monthly': (
+ ( 686, 0.0110, 0.00),
+ ( 1624, 0.0220, 7.55),
+ ( 2564, 0.0440, 28.19),
+ ( 3560, 0.0660, 69.55),
+ ( 4498, 0.0880, 135.29),
+ (22978, 0.1023, 217.83),
+ (27574, 0.1133, 2108.33),
+ (45956, 0.1243, 2629.06),
+ (83334, 0.1353, 4913.94),
+ ('inf', 0.1463, 9971.18),
+ ),
+ 'quarterly': (
+ ( 2056, 0.0110, 0.00),
+ ( 4874, 0.0220, 22.62),
+ ( 7692, 0.0440, 84.62),
+ ( 10678, 0.066, 208.61),
+ ( 13495, 0.0880, 405.69),
+ ( 68935, 0.1023, 653.59),
+ ( 82721, 0.1133, 6325.10),
+ (137868, 0.1243, 7887.05),
+ (250000, 0.1353, 14741.82),
+ ( 'inf', 0.1463, 29913.28),
+ ),
+ 'semi-annual': (
+ ( 4112, 0.0110, 0.00),
+ ( 9748, 0.0220, 45.23),
+ ( 15384, 0.0440, 169.22),
+ ( 21356, 0.0660, 417.20),
+ ( 26990, 0.0880, 811.35),
+ (137870, 0.1023, 1307.14),
+ (165442, 0.1133, 12650.16),
+ (275736, 0.1243, 15774.07),
+ (500000, 0.1353, 29483.61),
+ ('inf', 0.1463, 59826.53),
+ ),
+ 'annually': (
+ ( 8223, 0.0110, 0.00),
+ ( 19495, 0.0220, 90.45),
+ ( 30769, 0.0440, 338.43),
+ ( 42711, 0.0660, 834.49),
+ ( 53980, 0.0880, 1622.66),
+ ( 275738, 0.1023, 2614.33),
+ ( 330884, 0.1133, 25300.17),
+ ( 551473, 0.1243, 31548.21),
+ (1000000, 0.1353, 58967.42),
+ ( 'inf', 0.1463, 119653.12),
+ ),
+ },
+ }
+
+
+
+
+
+
+ {
+ 'head_household': {
+ 'weekly': (
+ ( 339, 0.0110, 0.00),
+ ( 803, 0.0220, 3.73),
+ ( 1035, 0.0440, 13.93),
+ ( 1281, 0.0660, 24.15),
+ ( 1514, 0.0880, 40.39),
+ ( 7725, 0.1023, 60.89),
+ ( 9270, 0.1133, 696.28),
+ (15450, 0.1243, 871.33),
+ (19231, 0.1353, 1639.50),
+ ('inf', 0.1463, 2151.07),
+ ),
+ 'bi-weekly': (
+ ( 678, 0.0110, 0.00),
+ ( 1606, 0.0220, 7.46),
+ ( 2070, 0.0440, 27.88),
+ ( 2562, 0.0660, 48.30),
+ ( 3028, 0.0880, 80.77),
+ (15450, 0.1023, 121.78),
+ (18540, 0.1133, 1392.55),
+ (30900, 0.1243, 1742.65),
+ (38462, 0.1353, 3279.00),
+ ('inf', 0.1463, 4302.14),
+ ),
+ 'semi-monthly': (
+ ( 735, 0.0110, 0.00),
+ ( 1740, 0.0220, 8.09),
+ ( 2243, 0.0440, 30.20),
+ ( 2777, 0.0660, 52.33),
+ ( 3280, 0.0880, 87.57),
+ (16738, 0.1023, 131.83),
+ (20085, 0.1133, 1508.58),
+ (33475, 0.1243, 1887.80),
+ (41667, 0.1353, 3552.18),
+ ('inf', 0.1463, 4660.56),
+ ),
+ 'monthly': (
+ ( 1470, 0.0110, 0.00),
+ ( 3480, 0.0220, 16.17),
+ ( 4486, 0.0440, 60.39),
+ ( 5554, 0.0660, 104.65),
+ ( 6560, 0.0880, 175.14),
+ (33476, 0.1023, 263.67),
+ (40170, 0.1133, 3017.18),
+ (66950, 0.1243, 3775.61),
+ (83334, 0.1353, 7104.36),
+ ('inf', 0.1463, 9321.12),
+ ),
+ 'quarterly': (
+ ( 4407, 0.0110, 0.00),
+ ( 10442, 0.0220, 48.48),
+ ( 13461, 0.0440, 181.25),
+ ( 16659, 0.0660, 314.09),
+ ( 19678, 0.0880, 525.16),
+ (100426, 0.1023, 790.83),
+ (120512, 0.1133, 9051.35),
+ (200853, 0.1243, 11327.09),
+ (250000, 0.1353, 21313.48),
+ ( 'inf', 0.1463, 27963.07),
+ ),
+ 'semi-annual': (
+ ( 8814, 0.0110, 0.00),
+ ( 20884, 0.0220, 96.95),
+ ( 26922, 0.0440, 362.49),
+ ( 33318, 0.0660, 628.16),
+ ( 39356, 0.0880, 1050.30),
+ (200852, 0.1023, 1581.64),
+ (241024, 0.1133, 18102.68),
+ (401706, 0.1243, 22654.17),
+ (500000, 0.1353, 42626.94),
+ ( 'inf', 0.1463, 55926.12),
+ ),
+ 'annually': (
+ ( 17629, 0.0110, 0.00),
+ ( 41768, 0.0220, 193.92),
+ ( 53843, 0.0440, 724.98),
+ ( 66636, 0.0660, 1256.28),
+ ( 78710, 0.0880, 2100.62),
+ ( 401705, 0.1023, 3163.13),
+ ( 482047, 0.1133, 36205.52),
+ ( 803410, 0.1243, 45308.27),
+ (1000000, 0.1353, 85253.69),
+ ( 'inf', 0.1463, 111852.32),
+ ),
+ },
+ 'married': {
+ 'weekly': (
+ ( 338, 0.0110, 0.00),
+ ( 804, 0.0220, 3.72),
+ ( 1268, 0.0440, 13.97),
+ ( 1760, 0.0660, 34.39),
+ ( 2224, 0.0880, 66.86),
+ (11360, 0.1023, 107.69),
+ (13632, 0.1133, 1042.30),
+ (19231, 0.1243, 1299.72),
+ (22721, 0.1353, 1995.68),
+ ('inf', 0.1463, 2467.88),
+ ),
+ 'bi-weekly': (
+ ( 676, 0.0110, 0.00),
+ ( 1608, 0.0220, 7.44),
+ ( 2536, 0.0440, 27.94),
+ ( 3520, 0.0660, 68.77),
+ ( 4448, 0.0880, 124.70),
+ (21212, 0.1023, 201.08),
+ (25452, 0.1133, 1946.32),
+ (38462, 0.1243, 2426.71),
+ (42420, 0.1353, 4043.85),
+ ('inf', 0.1463, 4579.37),
+ ),
+ 'semi-monthly': (
+ ( 734, 0.0110, 0.00),
+ ( 1740, 0.0220, 8.07),
+ ( 2746, 0.0440, 30.20),
+ ( 3812, 0.0660, 74.46),
+ ( 4818, 0.0880, 144.82),
+ (24614, 0.1023, 233.35),
+ (29538, 0.1133, 2258.48),
+ (41667, 0.1243, 2816.37),
+ (49229, 0.1353, 4324.00),
+ ('inf', 0.1463, 5347.14),
+ ),
+ 'monthly': (
+ ( 1468, 0.0110, 0.00),
+ ( 3480, 0.0220, 16.15),
+ ( 5492, 0.0440, 60.41),
+ ( 7624, 0.0660, 148.94),
+ ( 9636, 0.0880, 289.65),
+ (49228, 0.1023, 466.71),
+ (59076, 0.1133, 4516.97),
+ (83334, 0.1243, 5632.75),
+ (98458, 0.1353, 8648.02),
+ ('inf', 0.1463, 10694.30),
+ ),
+ 'quarterly': (
+ ( 4404, 0.0110, 0.00),
+ ( 10442, 0.0220, 48.44),
+ ( 16480, 0.0440, 181.28),
+ ( 22876, 0.0660, 446.95),
+ ( 28912, 0.0880, 869.09),
+ (147686, 0.1023, 1400.26),
+ (177222, 0.1133, 13550.84),
+ (250000, 0.1243, 16897.27),
+ (295371, 0.1353, 25943.58),
+ ( 'inf', 0.1463, 32082.28),
+ ),
+ 'semi-annual': (
+ ( 8808, 0.0110, 0.00),
+ ( 20884, 0.0220, 96.89),
+ ( 32960, 0.0440, 362.56),
+ ( 45752, 0.0660, 893.90),
+ ( 57824, 0.0880, 1738.17),
+ (295372, 0.1023, 2800.51),
+ (354444, 0.1133, 27101.67),
+ (500000, 0.1243, 33794.53),
+ (590742, 0.1353, 51887.14),
+ ( 'inf', 0.1463, 64164.53),
+ ),
+ 'annually': (
+ ( 17618, 0.0110, 0.00),
+ ( 41766, 0.0220, 193.80),
+ ( 65920, 0.0440, 725.06),
+ ( 91506, 0.0660, 1787.84),
+ ( 115648, 0.0880, 3476.52),
+ ( 590746, 0.1023, 5601.02),
+ ( 708890, 0.1133, 54203.55),
+ (1000000, 0.1243, 67589.27),
+ (1181484, 0.1353, 103774.24),
+ ( 'inf', 0.1463, 128329.03),
+ ),
+ },
+ 'single': {
+ 'weekly': (
+ ( 169, 0.0110, 0.00),
+ ( 402, 0.0220, 1.86),
+ ( 634, 0.0440, 6.99),
+ ( 880, 0.0660, 17.20),
+ ( 1112, 0.0880, 33.44),
+ ( 5680, 0.1023, 53.86),
+ ( 6816, 0.1133, 521.17),
+ (11360, 0.1243, 649.88),
+ (19231, 0.1353, 1214.70),
+ ('inf', 0.1463, 2279.65),
+ ),
+ 'bi-weekly': (
+ ( 338, 0.0110, 0.00),
+ ( 804, 0.0220, 3.72),
+ ( 1268, 0.0440, 13.97),
+ ( 1760, 0.0660, 34.39),
+ ( 2224, 0.0880, 66.86),
+ (11360, 0.1023, 107.69),
+ (13632, 0.1133, 1042.30),
+ (22720, 0.1243, 1299.72),
+ (38462, 0.1353, 2429.36),
+ ('inf', 0.1463, 4559.25),
+ ),
+ 'semi-monthly': (
+ ( 367, 0.0110, 0.00),
+ ( 870, 0.0220, 4.04),
+ ( 1373, 0.0440, 15.11),
+ ( 1906, 0.0660, 37.24),
+ ( 2409, 0.0880, 72.42),
+ (12307, 0.1023, 116.68),
+ (14769, 0.1133, 1129.25),
+ (24614, 0.1243, 1408.19),
+ (41667, 0.1353, 2631.92),
+ ('inf', 0.1463, 4939.19),
+ ),
+ 'monthly': (
+ ( 734, 0.0110, 0.00),
+ ( 1740, 0.0220, 8.07),
+ ( 2746, 0.0440, 30.20),
+ ( 3812, 0.0660, 74.46),
+ ( 4818, 0.0880, 144.82),
+ (24614, 0.1023, 233.35),
+ (29538, 0.1133, 2258.48),
+ (49228, 0.1243, 2816.37),
+ (83334, 0.1353, 5263.84),
+ ('inf', 0.1463, 9878.38),
+ ),
+ 'quarterly': (
+ ( 2202, 0.0110, 0.00),
+ ( 5221, 0.0220, 24.22),
+ ( 8240, 0.0440, 90.64),
+ ( 11438, 0.0660, 223.48),
+ ( 14456, 0.0880, 434.55),
+ ( 73843, 0.1023, 700.13),
+ ( 88611, 0.1133, 6775.42),
+ (147686, 0.1243, 8448.63),
+ (250000, 0.1353, 15791.65),
+ ( 'inf', 0.1463, 29634.73),
+ ),
+ 'semi-annual': (
+ ( 4404, 0.0110, 0.00),
+ ( 10442, 0.0220, 48.44),
+ ( 16480, 0.0440, 181.28),
+ ( 22876, 0.0660, 446.95),
+ ( 28912, 0.0880, 869.09),
+ (147686, 0.1023, 1400.26),
+ (177222, 0.1133, 13550.84),
+ (295372, 0.1243, 16897.27),
+ (500000, 0.1353, 31583.32),
+ ( 'inf', 0.1463, 59269.49),
+ ),
+ 'annually': (
+ ( 8809, 0.0110, 0.00),
+ ( 20883, 0.0220, 96.90),
+ ( 32960, 0.0440, 362.53),
+ ( 45753, 0.0660, 893.92),
+ ( 57824, 0.0880, 1738.26),
+ ( 295373, 0.1023, 2800.51),
+ ( 354445, 0.1133, 27101.77),
+ ( 590742, 0.1243, 33794.63),
+ (1000000, 0.1353, 63166.35),
+ ( 'inf', 0.1463, 118538.96),
+ ),
+ },
+ }
+
+
+
+
+
+
+ US CA California Low Income Exemption Rate
+ us_ca_sit_income_exemption_rate
+
+
+
+
+ {
+ 'weekly': ( 280, 280, 561, 561),
+ 'bi-weekly': ( 561, 561, 1121, 1121),
+ 'semi-monthly': ( 607, 607, 1214, 1214),
+ 'monthly': ( 1214, 1214, 2429, 2429),
+ 'quarterly': ( 3643, 3643, 7287, 7287),
+ 'semi-annual': ( 7287, 7287, 14573, 14573),
+ 'annually': (14573, 14573, 29146, 29146),
+ }
+
+
+
+
+
+
+ {
+ 'weekly': ( 289, 289, 579, 579),
+ 'bi-weekly': ( 579, 579, 1157, 1157),
+ 'semi-monthly': ( 627, 627, 1253, 1253),
+ 'monthly': ( 1254, 1254, 2507, 2507),
+ 'quarterly': ( 3761, 3761, 7521, 7521),
+ 'semi-annual': ( 7521, 7521, 15042, 15042),
+ 'annually': (15042, 15042, 30083, 30083),
+ }
+
+
+
+
+
+
+ US CA California Estimated Deduction Rate
+ us_ca_sit_estimated_deduction_rate
+
+
+
+
+ {
+ 'weekly': ( 19, 38, 58, 77, 96, 115, 135, 154, 173, 192),
+ 'bi-weekly': ( 38, 77, 115, 154, 192, 231, 269, 308, 346, 385),
+ 'semi-monthly': ( 42, 83, 125, 167, 208, 250, 292, 333, 375, 417),
+ 'monthly': ( 83, 167, 250, 333, 417, 500, 583, 667, 750, 833),
+ 'quarterly': ( 250, 500, 750, 1000, 1250, 1500, 1750, 2000, 2250, 2500),
+ 'semi-annual': ( 500, 1000, 1500, 2000, 2500, 3000, 3500, 4000, 4500, 5000),
+ 'annually': (1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000),
+ }
+
+
+
+
+
+
+ {
+ 'weekly': ( 19, 38, 58, 77, 96, 115, 135, 154, 173, 192),
+ 'bi-weekly': ( 38, 77, 115, 154, 192, 231, 269, 308, 346, 385),
+ 'semi-monthly': ( 42, 83, 125, 167, 208, 250, 292, 333, 375, 417),
+ 'monthly': ( 83, 167, 250, 333, 417, 500, 583, 667, 750, 833),
+ 'quarterly': ( 250, 500, 750, 1000, 1250, 1500, 1750, 2000, 2250, 2500),
+ 'semi-annual': ( 500, 1000, 1500, 2000, 2500, 3000, 3500, 4000, 4500, 5000),
+ 'annually': (1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000),
+ }
+
+
+
+
+
+
+ US CA California Standard Deduction Rate
+ us_ca_sit_standard_deduction_rate
+
+
+
+
+ {
+ 'weekly': ( 85, 85, 169, 169),
+ 'bi-weekly': ( 169, 169, 339, 339),
+ 'semi-monthly': ( 183, 183, 367, 367),
+ 'monthly': ( 367, 367, 734, 734),
+ 'quarterly': (1100, 1100, 2201, 2201),
+ 'semi-annual': (2201, 2201, 4401, 4401),
+ 'annually': (4401, 4401, 8802, 8802),
+ }
+
+
+
+
+
+
+ {
+ 'weekly': ( 87, 87, 175, 175),
+ 'bi-weekly': ( 175, 175, 349, 349),
+ 'semi-monthly': ( 189, 189, 378, 378),
+ 'monthly': ( 378, 378, 756, 756),
+ 'quarterly': (1134, 1134, 2269, 2269),
+ 'semi-annual': (2269, 2269, 4537, 4537),
+ 'annually': (4537, 4537, 9074, 9074),
+ }
+
+
+
+
+
+
+ US CA California Exemption Allowance Rate
+ us_ca_sit_exemption_allowance_rate
+
+
+
+
+ {
+ 'weekly': ( 2.41, 4.82, 7.23, 9.65, 12.06, 14.47, 16.88, 19.29, 21.70, 24.12),
+ 'bi-weekly': ( 4.82, 9.65, 14.47, 19.29, 24.12, 28.94, 33.76, 38.58, 43.41, 48.23),
+ 'semi-monthly': ( 5.23, 10.45, 15.68, 20.90, 26.13, 31.35, 36.58, 41.80, 47.03, 52.25),
+ 'monthly': ( 10.45, 20.90, 31.35, 41.80, 52.25, 62.70, 73.15, 83.60, 94.05, 104.50),
+ 'quarterly': ( 31.35, 62.70, 94.05, 125.40, 156.75, 188.10, 219.45, 250.80, 282.15, 313.50),
+ 'semi-annual': ( 62.70, 125.40, 188.10, 250.80, 313.50, 376.20, 438.90, 501.60, 564.30, 627.00),
+ 'annually': (125.40, 250.80, 376.20, 501.60, 627.00, 752.40, 877.80, 1003.20, 1128.60, 1254.00),
+ }
+
+
+
+
+
+
+ {
+ 'weekly': ( 2.58, 5.16, 7.74, 10.32, 12.90, 15.48, 18.07, 20.65, 23.23, 25.81),
+ 'bi-weekly': ( 5.16, 10.32, 15.48, 20.65, 25.81, 30.97, 36.13, 41.29, 46.45, 51.62),
+ 'semi-monthly': ( 5.59, 11.18, 16.78, 22.37, 27.96, 33.55, 39.14, 44.73, 50.33, 55.92),
+ 'monthly': ( 11.18, 22.37, 33.55, 44.73, 55.92, 67.10, 78.28, 89.47, 100.65, 111.83),
+ 'quarterly': ( 33.55, 67.10, 100.65, 134.20, 167.75, 201.30, 234.85, 268.40, 301.95, 335.50),
+ 'semi-annual': ( 67.10, 134.20, 201.30, 268.40, 335.50, 402.60, 469.70, 536.80, 603.90, 671.00),
+ 'annually': (134.20, 268.40, 402.60, 536.80, 671.00, 805.20, 939.40, 1073.60, 1207.80, 1342.00),
+ }
+
+
+
+
+
+
+
+ US California - Department of Taxation (CA DE88) - Unemployment Tax
+
+
+
+ US California - Department of Taxation - Income Tax
+
+
+
+
+
+
+
+
+
+ ER: US CA California State Unemployment
+ ER_US_CA_SUTA
+ python
+ result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_ca_suta_wage_base', rate='us_ca_suta_rate', state_code='CA')
+ code
+ result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_ca_suta_wage_base', rate='us_ca_suta_rate', state_code='CA')
+
+
+
+
+
+
+
+
+ ER: US CA California State Employee Training Tax
+ ER_US_CA_SUTA_ETT
+ python
+ result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_ca_suta_wage_base', rate='us_ca_suta_ett_rate', state_code='CA')
+ code
+ result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_ca_suta_wage_base', rate='us_ca_suta_ett_rate', state_code='CA')
+
+
+
+
+
+
+
+
+ EE: US CA California State Disability Insurance
+ EE_US_CA_SUTA_SDI
+ python
+ result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_ca_suta_wage_base', rate='us_ca_suta_sdi_rate', state_code='CA')
+ code
+ result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_ca_suta_wage_base', rate='us_ca_suta_sdi_rate', state_code='CA')
+
+
+
+
+
+
+
+
+ EE: US CA California State Income Tax Withholding
+ EE_US_CA_SIT
+ python
+ result, _ = ca_california_state_income_withholding(payslip, categories, worked_days, inputs)
+ code
+ result, result_rate = ca_california_state_income_withholding(payslip, categories, worked_days, inputs)
+
+
+
+
+
\ No newline at end of file
diff --git a/l10n_us_hr_payroll/data/state/co_colorado.xml b/l10n_us_hr_payroll/data/state/co_colorado.xml
new file mode 100644
index 00000000..a37ee1b9
--- /dev/null
+++ b/l10n_us_hr_payroll/data/state/co_colorado.xml
@@ -0,0 +1,97 @@
+
+
+
+
+ US CO Colorado SUTA Wage Base
+ us_co_suta_wage_base
+
+
+
+
+ 13600.0
+
+
+
+
+
+
+
+ US CO Colorado SUTA Rate
+ us_co_suta_rate
+
+
+
+
+ 1.7
+
+
+
+
+
+
+ US CO Colorado SIT Tax Rate
+ us_co_sit_tax_rate
+
+
+
+
+ 4.63
+
+
+
+
+
+
+ US CO Colorado SIT Exemption Rate
+ us_co_sit_exemption_rate
+
+
+
+
+ 4000
+
+
+
+
+
+
+
+ US Colorado - Department of Labor and Employment - Unemployment Tax
+
+
+
+ US Colorado - Division of Revenue - Income Tax
+
+
+
+
+
+
+
+
+
+ ER: US CO Colorado State Unemployment
+ ER_US_CO_SUTA
+ python
+ result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_co_suta_wage_base', rate='us_co_suta_rate', state_code='CO')
+ code
+ result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_co_suta_wage_base', rate='us_co_suta_rate', state_code='CO')
+
+
+
+
+
+
+
+
+ EE: US CO Colorado State Income Tax Withholding
+ EE_US_CO_SIT
+ python
+ result, _ = co_colorado_state_income_withholding(payslip, categories, worked_days, inputs)
+ code
+ result, result_rate = co_colorado_state_income_withholding(payslip, categories, worked_days, inputs)
+
+
+
+
+
\ No newline at end of file
diff --git a/l10n_us_hr_payroll/data/state/ct_connecticut.xml b/l10n_us_hr_payroll/data/state/ct_connecticut.xml
new file mode 100644
index 00000000..c4bf1dd1
--- /dev/null
+++ b/l10n_us_hr_payroll/data/state/ct_connecticut.xml
@@ -0,0 +1,1237 @@
+
+
+
+
+ US CT Connecticut SUTA Wage Base
+ us_ct_suta_wage_base
+
+
+
+
+ 15000.0
+
+
+
+
+ 15000.0
+
+
+
+
+
+
+
+ US CT Connecticut SUTA Rate
+ us_ct_suta_rate
+
+
+
+
+ 3.4
+
+
+
+
+ 3.2
+
+
+
+
+
+
+ US CT Connecticut SIT Initial Tax Rate
+ us_ct_sit_initial_tax_rate
+
+
+
+
+ {
+ 'a': [
+ ( 10000, 0, 3.00),
+ ( 50000, 300, 5.00),
+ (100000, 2300, 5.50),
+ (200000, 5050, 6.00),
+ (250000, 11050, 6.50),
+ (500000, 14300, 6.90),
+ ( 'inf', 31550, 6.99),
+ ],
+ 'b': [
+ ( 16000, 0, 3.00),
+ ( 80000, 480, 5.00),
+ (160000, 3680, 5.50),
+ (320000, 8080, 6.00),
+ (400000, 17680, 6.50),
+ (800000, 22880, 6.90),
+ ( 'inf', 50480, 6.99),
+ ],
+ 'c': [
+ ( 20000, 0, 3.00),
+ ( 100000, 600, 5.00),
+ ( 200000, 4600, 5.50),
+ ( 400000, 10100, 6.00),
+ ( 500000, 22100, 6.50),
+ (1000000, 28600, 6.90),
+ ( 'inf', 63100, 6.99),
+ ],
+ 'd': [
+ ( 10000, 0, 3.00),
+ ( 50000, 300, 5.00),
+ (100000, 2300, 5.50),
+ (200000, 5050, 6.00),
+ (250000, 11050, 6.50),
+ (500000, 14300, 6.90),
+ ( 'inf', 31550, 6.99),
+ ],
+ 'f': [
+ ( 10000, 0, 3.00),
+ ( 50000, 300, 5.00),
+ (100000, 2300, 5.50),
+ (200000, 5050, 6.00),
+ (250000, 11050, 6.50),
+ (500000, 14300, 6.90),
+ ( 'inf', 31550, 6.99),
+ ],
+ }
+
+
+
+
+
+
+ {
+ 'a': [
+ ( 10000, 0, 3.00),
+ ( 50000, 300, 5.00),
+ (100000, 2300, 5.50),
+ (200000, 5050, 6.00),
+ (250000, 11050, 6.50),
+ (500000, 14300, 6.90),
+ ( 'inf', 31550, 6.99),
+ ],
+ 'b': [
+ ( 16000, 0, 3.00),
+ ( 80000, 480, 5.00),
+ (160000, 3680, 5.50),
+ (320000, 8080, 6.00),
+ (400000, 17680, 6.50),
+ (800000, 22880, 6.90),
+ ( 'inf', 50480, 6.99),
+ ],
+ 'c': [
+ ( 20000, 0, 3.00),
+ ( 100000, 600, 5.00),
+ ( 200000, 4600, 5.50),
+ ( 400000, 10100, 6.00),
+ ( 500000, 22100, 6.50),
+ (1000000, 28600, 6.90),
+ ( 'inf', 63100, 6.99),
+ ],
+ 'd': [
+ ( 10000, 0, 3.00),
+ ( 50000, 300, 5.00),
+ (100000, 2300, 5.50),
+ (200000, 5050, 6.00),
+ (250000, 11050, 6.50),
+ (500000, 14300, 6.90),
+ ( 'inf', 31550, 6.99),
+ ],
+ 'f': [
+ ( 10000, 0, 3.00),
+ ( 50000, 300, 5.00),
+ (100000, 2300, 5.50),
+ (200000, 5050, 6.00),
+ (250000, 11050, 6.50),
+ (500000, 14300, 6.90),
+ ( 'inf', 31550, 6.99),
+ ],
+ }
+
+
+
+
+
+
+ US CT Connecticut Tax Rate
+ us_ct_sit_tax_rate
+
+
+
+
+ {
+ 'a': [
+ (50250, 0),
+ (52750, 20),
+ (55250, 40),
+ (57750, 60),
+ (60250, 80),
+ (62750, 100),
+ (65250, 120),
+ (67750, 140),
+ (70250, 160),
+ (72750, 180),
+ ('inf', 200),
+ ],
+ 'b': [
+ ( 78500, 0),
+ ( 82500, 32),
+ ( 86500, 64),
+ ( 90500, 96),
+ ( 94500, 128),
+ ( 98500, 160),
+ (102500, 192),
+ (106500, 224),
+ (110500, 256),
+ (114500, 288),
+ ( 'inf', 320),
+ ],
+ 'c': [
+ (100500, 0),
+ (105500, 40),
+ (110500, 80),
+ (115500, 120),
+ (120500, 160),
+ (125500, 200),
+ (130500, 240),
+ (135500, 280),
+ (140500, 320),
+ (145500, 360),
+ ( 'inf', 400),
+
+ ],
+ 'd': [
+ (50250, 0),
+ (52750, 20),
+ (55250, 40),
+ (57750, 60),
+ (60250, 80),
+ (62750, 100),
+ (65250, 120),
+ (67750, 140),
+ (70250, 160),
+ (72750, 180),
+ ('inf', 200),
+ ],
+ 'f': [
+ ( 56500, 0),
+ ( 61500, 20),
+ ( 66500, 40),
+ ( 71500, 60),
+ ( 76500, 80),
+ ( 81500, 100),
+ ( 86500, 120),
+ ( 91500, 140),
+ ( 96500, 160),
+ (101500, 180),
+ ( 'inf', 200),
+ ],
+ }
+
+
+
+
+
+
+ {
+ 'a': [
+ (50250, 0),
+ (52750, 20),
+ (55250, 40),
+ (57750, 60),
+ (60250, 80),
+ (62750, 100),
+ (65250, 120),
+ (67750, 140),
+ (70250, 160),
+ (72750, 180),
+ ('inf', 200),
+ ],
+ 'b': [
+ ( 78500, 0),
+ ( 82500, 32),
+ ( 86500, 64),
+ ( 90500, 96),
+ ( 94500, 128),
+ ( 98500, 160),
+ (102500, 192),
+ (106500, 224),
+ (110500, 256),
+ (114500, 288),
+ ( 'inf', 320),
+ ],
+ 'c': [
+ (100500, 0),
+ (105500, 40),
+ (110500, 80),
+ (115500, 120),
+ (120500, 160),
+ (125500, 200),
+ (130500, 240),
+ (135500, 280),
+ (140500, 320),
+ (145500, 360),
+ ( 'inf', 400),
+
+ ],
+ 'd': [
+ (50250, 0),
+ (52750, 20),
+ (55250, 40),
+ (57750, 60),
+ (60250, 80),
+ (62750, 100),
+ (65250, 120),
+ (67750, 140),
+ (70250, 160),
+ (72750, 180),
+ ('inf', 200),
+ ],
+ 'f': [
+ ( 56500, 0),
+ ( 61500, 20),
+ ( 66500, 40),
+ ( 71500, 60),
+ ( 76500, 80),
+ ( 81500, 100),
+ ( 86500, 120),
+ ( 91500, 140),
+ ( 96500, 160),
+ (101500, 180),
+ ( 'inf', 200),
+ ],
+ }
+
+
+
+
+
+
+ US CT Connecticut Decimal Rate
+ us_ct_sit_decimal_rate
+
+
+
+
+ {
+ 'a': [
+ (15000, 0.75),
+ (15500, 0.70),
+ (16000, 0.65),
+ (16500, 0.60),
+ (17000, 0.55),
+ (17500, 0.50),
+ (18000, 0.45),
+ (18500, 0.40),
+ (20000, 0.35),
+ (20500, 0.30),
+ (21000, 0.25),
+ (21500, 0.20),
+ (25000, 0.15),
+ (25500, 0.14),
+ (26000, 0.13),
+ (26500, 0.12),
+ (27000, 0.11),
+ (48000, 0.10),
+ (48500, 0.09),
+ (49000, 0.08),
+ (49500, 0.08),
+ (50000, 0.06),
+ (50500, 0.05),
+ (51000, 0.03),
+ (51500, 0.03),
+ (52000, 0.02),
+ (52500, 0.01),
+ ('inf', 0.00),
+ ],
+ 'b': [
+ (24000, 0.75),
+ (24500, 0.70),
+ (25000, 0.65),
+ (25500, 0.60),
+ (26000, 0.55),
+ (26500, 0.50),
+ (27000, 0.45),
+ (27500, 0.40),
+ (34000, 0.35),
+ (34500, 0.30),
+ (35000, 0.25),
+ (35500, 0.20),
+ (44000, 0.15),
+ (44500, 0.14),
+ (45000, 0.13),
+ (45500, 0.12),
+ (46000, 0.11),
+ (74000, 0.10),
+ (74500, 0.09),
+ (75000, 0.08),
+ (75500, 0.08),
+ (76000, 0.06),
+ (76500, 0.05),
+ (77000, 0.03),
+ (77500, 0.03),
+ (78000, 0.02),
+ (78500, 0.01),
+ ('inf', 0.00),
+ ],
+ 'c': [
+ (30000, 0.75),
+ (30500, 0.70),
+ (31000, 0.65),
+ (31500, 0.60),
+ (32000, 0.55),
+ (32500, 0.50),
+ (33000, 0.45),
+ (33500, 0.40),
+ (40000, 0.35),
+ (40500, 0.30),
+ (41000, 0.25),
+ (41500, 0.20),
+ (50000, 0.15),
+ (50500, 0.14),
+ (51000, 0.13),
+ (51500, 0.12),
+ (52000, 0.11),
+ (96000, 0.10),
+ (96500, 0.09),
+ (97000, 0.08),
+ (97500, 0.08),
+ (98000, 0.06),
+ (98500, 0.05),
+ (99000, 0.03),
+ (99500, 0.03),
+ (100000, 0.02),
+ (100500, 0.01),
+ ('inf', 0.00),
+ ],
+ 'f': [
+ (18800, 0.75),
+ (19300, 0.70),
+ (19800, 0.65),
+ (20300, 0.60),
+ (20800, 0.55),
+ (21300, 0.50),
+ (21800, 0.45),
+ (22300, 0.40),
+ (25000, 0.35),
+ (25500, 0.30),
+ (26000, 0.25),
+ (26500, 0.20),
+ (31300, 0.15),
+ (31800, 0.14),
+ (32300, 0.13),
+ (32800, 0.12),
+ (33300, 0.11),
+ (60000, 0.10),
+ (60500, 0.09),
+ (61000, 0.08),
+ (61500, 0.08),
+ (62000, 0.06),
+ (62500, 0.05),
+ (63000, 0.03),
+ (63500, 0.03),
+ (64000, 0.02),
+ (64500, 0.01),
+ ('inf', 0.00),
+ ],
+ }
+
+
+
+
+
+
+ {
+ 'a': [
+ (15000, 0.75),
+ (15500, 0.70),
+ (16000, 0.65),
+ (16500, 0.60),
+ (17000, 0.55),
+ (17500, 0.50),
+ (18000, 0.45),
+ (18500, 0.40),
+ (20000, 0.35),
+ (20500, 0.30),
+ (21000, 0.25),
+ (21500, 0.20),
+ (25000, 0.15),
+ (25500, 0.14),
+ (26000, 0.13),
+ (26500, 0.12),
+ (27000, 0.11),
+ (48000, 0.10),
+ (48500, 0.09),
+ (49000, 0.08),
+ (49500, 0.08),
+ (50000, 0.06),
+ (50500, 0.05),
+ (51000, 0.03),
+ (51500, 0.03),
+ (52000, 0.02),
+ (52500, 0.01),
+ ('inf', 0.00),
+ ],
+ 'b': [
+ (24000, 0.75),
+ (24500, 0.70),
+ (25000, 0.65),
+ (25500, 0.60),
+ (26000, 0.55),
+ (26500, 0.50),
+ (27000, 0.45),
+ (27500, 0.40),
+ (34000, 0.35),
+ (34500, 0.30),
+ (35000, 0.25),
+ (35500, 0.20),
+ (44000, 0.15),
+ (44500, 0.14),
+ (45000, 0.13),
+ (45500, 0.12),
+ (46000, 0.11),
+ (74000, 0.10),
+ (74500, 0.09),
+ (75000, 0.08),
+ (75500, 0.08),
+ (76000, 0.06),
+ (76500, 0.05),
+ (77000, 0.03),
+ (77500, 0.03),
+ (78000, 0.02),
+ (78500, 0.01),
+ ('inf', 0.00),
+ ],
+ 'c': [
+ (30000, 0.75),
+ (30500, 0.70),
+ (31000, 0.65),
+ (31500, 0.60),
+ (32000, 0.55),
+ (32500, 0.50),
+ (33000, 0.45),
+ (33500, 0.40),
+ (40000, 0.35),
+ (40500, 0.30),
+ (41000, 0.25),
+ (41500, 0.20),
+ (50000, 0.15),
+ (50500, 0.14),
+ (51000, 0.13),
+ (51500, 0.12),
+ (52000, 0.11),
+ (96000, 0.10),
+ (96500, 0.09),
+ (97000, 0.08),
+ (97500, 0.08),
+ (98000, 0.06),
+ (98500, 0.05),
+ (99000, 0.03),
+ (99500, 0.03),
+ (100000, 0.02),
+ (100500, 0.01),
+ ('inf', 0.00),
+ ],
+ 'f': [
+ (18800, 0.75),
+ (19300, 0.70),
+ (19800, 0.65),
+ (20300, 0.60),
+ (20800, 0.55),
+ (21300, 0.50),
+ (21800, 0.45),
+ (22300, 0.40),
+ (25000, 0.35),
+ (25500, 0.30),
+ (26000, 0.25),
+ (26500, 0.20),
+ (31300, 0.15),
+ (31800, 0.14),
+ (32300, 0.13),
+ (32800, 0.12),
+ (33300, 0.11),
+ (60000, 0.10),
+ (60500, 0.09),
+ (61000, 0.08),
+ (61500, 0.08),
+ (62000, 0.06),
+ (62500, 0.05),
+ (63000, 0.03),
+ (63500, 0.03),
+ (64000, 0.02),
+ (64500, 0.01),
+ ('inf', 0.00),
+ ],
+ }
+
+
+
+
+
+
+ US CT Connecticut Recapture Rate
+ us_ct_sit_recapture_rate
+
+
+
+
+ {
+ 'a': [
+ (200000, 0),
+ (205000, 90),
+ (210000, 180),
+ (215000, 270),
+ (220000, 360),
+ (225000, 450),
+ (230000, 540),
+ (235000, 630),
+ (240000, 720),
+ (245000, 810),
+ (250000, 900),
+ (255000, 990),
+ (260000, 1080),
+ (265000, 1170),
+ (270000, 1260),
+ (275000, 1350),
+ (280000, 1440),
+ (285000, 1530),
+ (290000, 1620),
+ (295000, 1710),
+ (300000, 1800),
+ (305000, 1890),
+ (310000, 1980),
+ (315000, 2070),
+ (320000, 2160),
+ (325000, 2250),
+ (330000, 2340),
+ (335000, 2430),
+ (340000, 2520),
+ (345000, 2610),
+ (500000, 2700),
+ (505000, 2750),
+ (510000, 2800),
+ (515000, 2850),
+ (520000, 2900),
+ (525000, 2950),
+ (530000, 3000),
+ (535000, 3050),
+ (540000, 3100),
+ ( 'inf', 200),
+ ],
+ 'b': [
+ (320000, 0),
+ (328000, 140),
+ (336000, 280),
+ (344000, 420),
+ (352000, 560),
+ (360000, 700),
+ (368000, 840),
+ (376000, 980),
+ (384000, 1120),
+ (392000, 1260),
+ (400000, 1400),
+ (408000, 1540),
+ (416000, 1680),
+ (424000, 1820),
+ (432000, 1960),
+ (440000, 2100),
+ (448000, 2240),
+ (456000, 2380),
+ (464000, 2520),
+ (472000, 2660),
+ (480000, 2800),
+ (488000, 2940),
+ (496000, 3080),
+ (504000, 3220),
+ (512000, 3360),
+ (520000, 3500),
+ (528000, 3640),
+ (536000, 3780),
+ (544000, 3920),
+ (552000, 4060),
+ (800000, 4200),
+ (808000, 4280),
+ (816000, 4360),
+ (824000, 4440),
+ (832000, 4520),
+ (840000, 4600),
+ (848000, 4680),
+ (856000, 4760),
+ (864000, 4840),
+ ( 'inf', 4920),
+ ],
+ 'c': [
+ ( 400000, 0),
+ ( 410000, 180),
+ ( 420000, 360),
+ ( 430000, 540),
+ ( 440000, 720),
+ ( 450000, 900),
+ ( 460000, 1080),
+ ( 470000, 1260),
+ ( 480000, 1440),
+ ( 490000, 1620),
+ ( 500000, 1800),
+ ( 510000, 1980),
+ ( 520000, 2160),
+ ( 530000, 2340),
+ ( 540000, 2520),
+ ( 550000, 2700),
+ ( 560000, 2880),
+ ( 570000, 3060),
+ ( 580000, 3240),
+ ( 590000, 3420),
+ ( 600000, 3600),
+ ( 610000, 3780),
+ ( 620000, 3960),
+ ( 630000, 4140),
+ ( 640000, 4320),
+ ( 650000, 4500),
+ ( 660000, 4680),
+ ( 670000, 4860),
+ ( 680000, 5040),
+ ( 690000, 5220),
+ (1000000, 5400),
+ (1010000, 5500),
+ (1020000, 5600),
+ (1030000, 5700),
+ (1040000, 5800),
+ (1050000, 5900),
+ (1060000, 6000),
+ (1070000, 6100),
+ (1080000, 6200),
+ ( 'inf', 6300),
+ ],
+ 'd': [
+ (200000, 0),
+ (205000, 90),
+ (210000, 180),
+ (215000, 270),
+ (220000, 360),
+ (225000, 450),
+ (230000, 540),
+ (235000, 630),
+ (240000, 720),
+ (245000, 810),
+ (250000, 900),
+ (255000, 990),
+ (260000, 1080),
+ (265000, 1170),
+ (270000, 1260),
+ (275000, 1350),
+ (280000, 1440),
+ (285000, 1530),
+ (290000, 1620),
+ (295000, 1710),
+ (300000, 1800),
+ (305000, 1890),
+ (310000, 1980),
+ (315000, 2070),
+ (320000, 2160),
+ (325000, 2250),
+ (330000, 2340),
+ (335000, 2430),
+ (340000, 2520),
+ (345000, 2610),
+ (500000, 2700),
+ (505000, 2750),
+ (510000, 2800),
+ (515000, 2850),
+ (520000, 2900),
+ (525000, 2950),
+ (530000, 3000),
+ (535000, 3050),
+ (540000, 3100),
+ ( 'inf', 200),
+ ],
+ 'f': [
+ (200000, 0),
+ (205000, 90),
+ (210000, 180),
+ (215000, 270),
+ (220000, 360),
+ (225000, 450),
+ (230000, 540),
+ (235000, 630),
+ (240000, 720),
+ (245000, 810),
+ (250000, 900),
+ (255000, 990),
+ (260000, 1080),
+ (265000, 1170),
+ (270000, 1260),
+ (275000, 1350),
+ (280000, 1440),
+ (285000, 1530),
+ (290000, 1620),
+ (295000, 1710),
+ (300000, 1800),
+ (305000, 1890),
+ (310000, 1980),
+ (315000, 2070),
+ (320000, 2160),
+ (325000, 2250),
+ (330000, 2340),
+ (335000, 2430),
+ (340000, 2520),
+ (345000, 2610),
+ (500000, 2700),
+ (505000, 2750),
+ (510000, 2800),
+ (515000, 2850),
+ (520000, 2900),
+ (525000, 2950),
+ (530000, 3000),
+ (535000, 3050),
+ (540000, 3100),
+ ( 'inf', 200),
+ ],
+ }
+
+
+
+
+
+
+ {
+ 'a': [
+ (200000, 0),
+ (205000, 90),
+ (210000, 180),
+ (215000, 270),
+ (220000, 360),
+ (225000, 450),
+ (230000, 540),
+ (235000, 630),
+ (240000, 720),
+ (245000, 810),
+ (250000, 900),
+ (255000, 990),
+ (260000, 1080),
+ (265000, 1170),
+ (270000, 1260),
+ (275000, 1350),
+ (280000, 1440),
+ (285000, 1530),
+ (290000, 1620),
+ (295000, 1710),
+ (300000, 1800),
+ (305000, 1890),
+ (310000, 1980),
+ (315000, 2070),
+ (320000, 2160),
+ (325000, 2250),
+ (330000, 2340),
+ (335000, 2430),
+ (340000, 2520),
+ (345000, 2610),
+ (500000, 2700),
+ (505000, 2750),
+ (510000, 2800),
+ (515000, 2850),
+ (520000, 2900),
+ (525000, 2950),
+ (530000, 3000),
+ (535000, 3050),
+ (540000, 3100),
+ ( 'inf', 3150),
+ ],
+ 'b': [
+ (320000, 0),
+ (328000, 140),
+ (336000, 280),
+ (344000, 420),
+ (352000, 560),
+ (360000, 700),
+ (368000, 840),
+ (376000, 980),
+ (384000, 1120),
+ (392000, 1260),
+ (400000, 1400),
+ (408000, 1540),
+ (416000, 1680),
+ (424000, 1820),
+ (432000, 1960),
+ (440000, 2100),
+ (448000, 2240),
+ (456000, 2380),
+ (464000, 2520),
+ (472000, 2660),
+ (480000, 2800),
+ (488000, 2940),
+ (496000, 3080),
+ (504000, 3220),
+ (512000, 3360),
+ (520000, 3500),
+ (528000, 3640),
+ (536000, 3780),
+ (544000, 3920),
+ (552000, 4060),
+ (800000, 4200),
+ (808000, 4280),
+ (816000, 4360),
+ (824000, 4440),
+ (832000, 4520),
+ (840000, 4600),
+ (848000, 4680),
+ (856000, 4760),
+ (864000, 4840),
+ ( 'inf', 4920),
+ ],
+ 'c': [
+ ( 400000, 0),
+ ( 410000, 180),
+ ( 420000, 360),
+ ( 430000, 540),
+ ( 440000, 720),
+ ( 450000, 900),
+ ( 460000, 1080),
+ ( 470000, 1260),
+ ( 480000, 1440),
+ ( 490000, 1620),
+ ( 500000, 1800),
+ ( 510000, 1980),
+ ( 520000, 2160),
+ ( 530000, 2340),
+ ( 540000, 2520),
+ ( 550000, 2700),
+ ( 560000, 2880),
+ ( 570000, 3060),
+ ( 580000, 3240),
+ ( 590000, 3420),
+ ( 600000, 3600),
+ ( 610000, 3780),
+ ( 620000, 3960),
+ ( 630000, 4140),
+ ( 640000, 4320),
+ ( 650000, 4500),
+ ( 660000, 4680),
+ ( 670000, 4860),
+ ( 680000, 5040),
+ ( 690000, 5220),
+ (1000000, 5400),
+ (1010000, 5500),
+ (1020000, 5600),
+ (1030000, 5700),
+ (1040000, 5800),
+ (1050000, 5900),
+ (1060000, 6000),
+ (1070000, 6100),
+ (1080000, 6200),
+ ( 'inf', 6300),
+ ],
+ 'd': [
+ (200000, 0),
+ (205000, 90),
+ (210000, 180),
+ (215000, 270),
+ (220000, 360),
+ (225000, 450),
+ (230000, 540),
+ (235000, 630),
+ (240000, 720),
+ (245000, 810),
+ (250000, 900),
+ (255000, 990),
+ (260000, 1080),
+ (265000, 1170),
+ (270000, 1260),
+ (275000, 1350),
+ (280000, 1440),
+ (285000, 1530),
+ (290000, 1620),
+ (295000, 1710),
+ (300000, 1800),
+ (305000, 1890),
+ (310000, 1980),
+ (315000, 2070),
+ (320000, 2160),
+ (325000, 2250),
+ (330000, 2340),
+ (335000, 2430),
+ (340000, 2520),
+ (345000, 2610),
+ (500000, 2700),
+ (505000, 2750),
+ (510000, 2800),
+ (515000, 2850),
+ (520000, 2900),
+ (525000, 2950),
+ (530000, 3000),
+ (535000, 3050),
+ (540000, 3100),
+ ( 'inf', 3150),
+ ],
+ 'f': [
+ (200000, 0),
+ (205000, 90),
+ (210000, 180),
+ (215000, 270),
+ (220000, 360),
+ (225000, 450),
+ (230000, 540),
+ (235000, 630),
+ (240000, 720),
+ (245000, 810),
+ (250000, 900),
+ (255000, 990),
+ (260000, 1080),
+ (265000, 1170),
+ (270000, 1260),
+ (275000, 1350),
+ (280000, 1440),
+ (285000, 1530),
+ (290000, 1620),
+ (295000, 1710),
+ (300000, 1800),
+ (305000, 1890),
+ (310000, 1980),
+ (315000, 2070),
+ (320000, 2160),
+ (325000, 2250),
+ (330000, 2340),
+ (335000, 2430),
+ (340000, 2520),
+ (345000, 2610),
+ (500000, 2700),
+ (505000, 2750),
+ (510000, 2800),
+ (515000, 2850),
+ (520000, 2900),
+ (525000, 2950),
+ (530000, 3000),
+ (535000, 3050),
+ (540000, 3100),
+ ( 'inf', 3150),
+ ],
+ }
+
+
+
+
+
+
+ US CT Connecticut Personal Exemption Rate
+ us_ct_sit_personal_exemption_rate
+
+
+
+
+ {
+ 'a' : [
+ (24000, 12000),
+ (25000, 11000),
+ (26000, 10000),
+ (27000, 9000),
+ (28000, 8000),
+ (29000, 7000),
+ (30000, 6000),
+ (31000, 5000),
+ (32000, 4000),
+ (33000, 3000),
+ (34000, 2000),
+ (35000, 1000),
+ ('inf', 0),
+ ],
+ 'b' : [
+ (38000, 19000),
+ (39000, 18000),
+ (40000, 17000),
+ (41000, 16000),
+ (42000, 15000),
+ (43000, 14000),
+ (44000, 13000),
+ (45000, 12000),
+ (46000, 11000),
+ (47000, 10000),
+ (48000, 9000),
+ (49000, 8000),
+ (50000, 7000),
+ (51000, 6000),
+ (52000, 5000),
+ (53000, 4000),
+ (54000, 3000),
+ (55000, 2000),
+ (56000, 1000),
+ ('inf', 0),
+ ],
+ 'c': [
+ (48000, 24000),
+ (49000, 23000),
+ (50000, 22000),
+ (51000, 21000),
+ (52000, 20000),
+ (53000, 19000),
+ (54000, 18000),
+ (55000, 17000),
+ (56000, 16000),
+ (57000, 15000),
+ (58000, 14000),
+ (59000, 13000),
+ (60000, 12000),
+ (61000, 11000),
+ (62000, 10000),
+ (63000, 9000),
+ (64000, 8000),
+ (65000, 7000),
+ (66000, 6000),
+ (67000, 5000),
+ (68000, 4000),
+ (69000, 3000),
+ (70000, 2000),
+ (71000, 1000),
+ ('inf', 0),
+ ],
+ 'f' : [
+ (30000, 15000),
+ (31000, 14000),
+ (22000, 13000),
+ (33000, 12000),
+ (34000, 11000),
+ (35000, 10000),
+ (36000, 9000),
+ (37000, 8000),
+ (38000, 7000),
+ (39000, 6000),
+ (40000, 5000),
+ (41000, 4000),
+ (42000, 3000),
+ (43000, 2000),
+ (44000, 1000),
+ ('inf', 0),
+ ],
+ }
+
+
+
+
+
+
+ {
+ 'a' : [
+ (24000, 12000),
+ (25000, 11000),
+ (26000, 10000),
+ (27000, 9000),
+ (28000, 8000),
+ (29000, 7000),
+ (30000, 6000),
+ (31000, 5000),
+ (32000, 4000),
+ (33000, 3000),
+ (34000, 2000),
+ (35000, 1000),
+ ('inf', 0),
+ ],
+ 'b' : [
+ (38000, 19000),
+ (39000, 18000),
+ (40000, 17000),
+ (41000, 16000),
+ (42000, 15000),
+ (43000, 14000),
+ (44000, 13000),
+ (45000, 12000),
+ (46000, 11000),
+ (47000, 10000),
+ (48000, 9000),
+ (49000, 8000),
+ (50000, 7000),
+ (51000, 6000),
+ (52000, 5000),
+ (53000, 4000),
+ (54000, 3000),
+ (55000, 2000),
+ (56000, 1000),
+ ('inf', 0),
+ ],
+ 'c': [
+ (48000, 24000),
+ (49000, 23000),
+ (50000, 22000),
+ (51000, 21000),
+ (52000, 20000),
+ (53000, 19000),
+ (54000, 18000),
+ (55000, 17000),
+ (56000, 16000),
+ (57000, 15000),
+ (58000, 14000),
+ (59000, 13000),
+ (60000, 12000),
+ (61000, 11000),
+ (62000, 10000),
+ (63000, 9000),
+ (64000, 8000),
+ (65000, 7000),
+ (66000, 6000),
+ (67000, 5000),
+ (68000, 4000),
+ (69000, 3000),
+ (70000, 2000),
+ (71000, 1000),
+ ('inf', 0),
+ ],
+ 'f' : [
+ (30000, 15000),
+ (31000, 14000),
+ (22000, 13000),
+ (33000, 12000),
+ (34000, 11000),
+ (35000, 10000),
+ (36000, 9000),
+ (37000, 8000),
+ (38000, 7000),
+ (39000, 6000),
+ (40000, 5000),
+ (41000, 4000),
+ (42000, 3000),
+ (43000, 2000),
+ (44000, 1000),
+ ('inf', 0),
+ ],
+ }
+
+
+
+
+
+
+
+ US Connecticut - Department of Labor (CDOL) - Unemployment Tax
+
+
+
+ US Connecticut - Department of Revenue Services (CDRS) - Income Tax
+
+
+
+
+
+
+
+
+
+ ER: US CT Connecticut State Unemployment
+ ER_US_CT_SUTA
+ python
+ result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_ct_suta_wage_base', rate='us_ct_suta_rate', state_code='CT')
+ code
+ result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_ct_suta_wage_base', rate='us_ct_suta_rate', state_code='CT')
+
+
+
+
+
+
+
+
+ EE: US CT Connecticut State Income Tax Withholding
+ EE_US_CT_SIT
+ python
+ result, _ = ct_connecticut_state_income_withholding(payslip, categories, worked_days, inputs)
+ code
+ result, result_rate = ct_connecticut_state_income_withholding(payslip, categories, worked_days, inputs)
+
+
+
+
+
\ No newline at end of file
diff --git a/l10n_us_hr_payroll/data/state/de_delaware.xml b/l10n_us_hr_payroll/data/state/de_delaware.xml
new file mode 100644
index 00000000..fad2abf6
--- /dev/null
+++ b/l10n_us_hr_payroll/data/state/de_delaware.xml
@@ -0,0 +1,119 @@
+
+
+
+
+ US DE Delaware SUTA Wage Base
+ us_de_suta_wage_base
+
+
+
+
+ 16500.0
+
+
+
+
+
+
+
+ US DE Delaware SUTA Rate
+ us_de_suta_rate
+
+
+
+
+ 1.50
+
+
+
+
+
+
+ US DE Delaware SIT Tax Rate
+ us_de_sit_tax_rate
+
+
+
+
+ [
+ ( 2000, 0.0, 0.00),
+ ( 5000, 0.0, 2.20),
+ (10000, 66.0, 3.90),
+ (20000, 261.0, 4.80),
+ (25000, 741.0, 5.20),
+ (60000, 1001.0, 5.55),
+ ('inf', 2943.0, 6.60),
+
+ ]
+
+
+
+
+
+
+ US DE Delaware Standard Deduction Rate
+ us_de_sit_standard_deduction_rate
+
+
+
+
+ 3250
+
+
+
+
+
+
+ US DE Delaware Personal Exemption Rate
+ us_de_sit_personal_exemption_rate
+
+
+
+
+ 110
+
+
+
+
+
+
+
+ US Delaware - Division of Unemployment Insurance - Unemployment Tax
+
+
+
+ US Delaware - Division of Revenue - Income Tax
+
+
+
+
+
+
+
+
+
+ ER: US DE Delaware State Unemployment
+ ER_US_DE_SUTA
+ python
+ result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_de_suta_wage_base', rate='us_de_suta_rate', state_code='DE')
+ code
+ result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_de_suta_wage_base', rate='us_de_suta_rate', state_code='DE')
+
+
+
+
+
+
+
+
+ EE: US DE Delaware State Income Tax Withholding
+ EE_US_DE_SIT
+ python
+ result, _ = de_delaware_state_income_withholding(payslip, categories, worked_days, inputs)
+ code
+ result, result_rate = de_delaware_state_income_withholding(payslip, categories, worked_days, inputs)
+
+
+
+
+
\ No newline at end of file
diff --git a/l10n_us_hr_payroll/data/state/fl_florida.xml b/l10n_us_hr_payroll/data/state/fl_florida.xml
new file mode 100644
index 00000000..8002a2ee
--- /dev/null
+++ b/l10n_us_hr_payroll/data/state/fl_florida.xml
@@ -0,0 +1,63 @@
+
+
+
+
+ US FL Florida SUTA Wage Base
+ us_fl_suta_wage_base
+
+
+
+
+ 7000.00
+
+
+
+
+ 7000.00
+
+
+
+
+
+
+
+ US FL Florida SUTA Rate
+ us_fl_suta_rate
+
+
+
+
+ 2.7
+
+
+
+
+ 2.7
+
+
+
+
+
+
+
+ US Florida - Department of Revenue
+
+
+
+
+
+
+
+
+
+ ER: US FL Florida State Unemployment (RT-6)
+ ER_US_FL_SUTA
+ python
+ result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_fl_suta_wage_base', rate='us_fl_suta_rate', state_code='FL')
+ code
+ result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_fl_suta_wage_base', rate='us_fl_suta_rate', state_code='FL')
+
+
+
+
+
\ No newline at end of file
diff --git a/l10n_us_hr_payroll/data/state/ga_georgia.xml b/l10n_us_hr_payroll/data/state/ga_georgia.xml
new file mode 100644
index 00000000..659515db
--- /dev/null
+++ b/l10n_us_hr_payroll/data/state/ga_georgia.xml
@@ -0,0 +1,943 @@
+
+
+
+
+ US GA Georgia SUTA Wage Base
+ us_ga_suta_wage_base
+
+
+
+
+ 9500.00
+
+
+
+
+ 9500.00
+
+
+
+
+
+
+
+ US GA Georgia SUTA Rate
+ us_ga_suta_rate
+
+
+
+
+ 2.7
+
+
+
+
+ 2.7
+
+
+
+
+
+
+ US GA Georgia SIT Rate Table
+ us_ga_sit_rate
+
+
+
+
+ {
+ 'married filing joint, both spouses working': {
+ 'weekly': (
+ ( 9.50, 0.00, 1.00),
+ (29.00, 0.10, 2.00),
+ (48.00, 0.48, 3.00),
+ (67.50, 1.06, 4.00),
+ (96.00, 1.83, 5.00),
+ ('inf', 3.27, 5.75),
+ ),
+ 'bi-weekly': (
+ ( 19.00, 0.00, 1.00),
+ ( 57.50, 0.19, 2.00),
+ ( 96.00, 0.96, 3.00),
+ (135.00, 2.12, 4.00),
+ (192.00, 3.65, 5.00),
+ ( 'inf', 6.54, 5.75),
+ ),
+ 'semi-monthly': (
+ ( 21.00, 0.00, 1.00),
+ ( 62.50, 0.21, 2.00),
+ (104.00, 1.04, 3.00),
+ (146.00, 2.29, 4.00),
+ (208.00, 3.96, 5.00),
+ ( 'inf', 7.08, 5.75),
+ ),
+ 'monthly': (
+ ( 41.50, 0.00, 1.00),
+ (125.50, 0.42, 2.00),
+ (208.00, 2.08, 3.00),
+ (292.00, 4.58, 4.00),
+ (417.00, 7.92, 5.00),
+ ( 'inf', 14.17, 5.75),
+ ),
+ 'quarterly': (
+ ( 125.00, 0.00, 1.00),
+ ( 375.00, 1.25, 2.00),
+ ( 625.00, 6.25, 3.00),
+ ( 875.00, 13.75, 4.00),
+ (1250.00, 23.75, 5.00),
+ ( 'inf', 42.50, 5.75),
+ ),
+ 'semi-annual': (
+ ( 250.00, 0.00, 1.00),
+ ( 750.00, 2.50, 2.00),
+ (1250.00, 12.50, 3.00),
+ (1750.00, 27.50, 4.00),
+ (2500.00, 47.50, 5.00),
+ ( 'inf', 85.00, 5.75),
+ ),
+ 'annual': (
+ ( 500.00, 0.00, 1.00),
+ (1500.00, 5.00, 2.00),
+ (2500.00, 25.00, 3.00),
+ (3500.00, 55.00, 4.00),
+ (5000.00, 95.00, 5.00),
+ ( 'inf', 170.00, 5.75),
+ ),
+ },
+ 'married filing joint, one spouse working': {
+ 'weekly': (
+ ( 19.00, 0.00, 1.00),
+ ( 57.50, 0.19, 2.00),
+ ( 96.00, 0.96, 3.00),
+ (135.00, 2.12, 4.00),
+ (192.50, 3.65, 5.00),
+ ( 'inf', 6.54, 5.75),
+ ),
+ 'bi-weekly': (
+ ( 38.50, 0.00, 1.00),
+ (115.00, 0.38, 2.00),
+ (192.00, 1.92, 3.00),
+ (269.00, 4.23, 4.00),
+ (385.00, 7.31, 5.00),
+ ( 'inf', 13.08, 5.75),
+ ),
+ 'semi-monthly': (
+ ( 41.50, 0.00, 1.00),
+ (125.00, 0.42, 2.00),
+ (208.00, 2.08, 3.00),
+ (292.00, 4.58, 4.00),
+ (417.00, 7.92, 5.00),
+ ( 'inf', 14.17, 5.75),
+ ),
+ 'monthly': (
+ ( 83.00, 0.00, 1.00),
+ (250.00, 0.83, 2.00),
+ (417.00, 4.17, 3.00),
+ (583.00, 9.17, 4.00),
+ (833.00, 15.83, 5.00),
+ ( 'inf', 28.33, 5.75),
+ ),
+ 'quarterly': (
+ ( 250.00, 0.00, 1.00),
+ ( 750.00, 2.50, 2.00),
+ (1250.00, 12.50, 3.00),
+ (1750.00, 27.50, 4.00),
+ (2500.00, 47.50, 5.00),
+ ( 'inf', 85.00, 5.75),
+ ),
+ 'semi-annual': (
+ ( 500.00, 0.00, 1.00),
+ (1500.00, 5.00, 2.00),
+ (2500.00, 25.00, 3.00),
+ (3500.00, 55.00, 4.00),
+ (5000.00, 95.00, 5.00),
+ ( 'inf', 170.00, 5.75),
+ ),
+ 'annual': (
+ ( 1000.00, 0.00, 1.00),
+ ( 3000.00, 10.00, 2.00),
+ ( 5000.00, 50.00, 3.00),
+ ( 7000.00, 110.00, 4.00),
+ (10000.00, 190.00, 5.00),
+ ( 'inf', 340.00, 5.75),
+ ),
+ },
+ 'single': {
+ 'weekly': (
+ ( 14.50, 0.00, 1.00),
+ ( 43.50, 0.14, 2.00),
+ ( 72.00, 0.72, 3.00),
+ (101.00, 1.59, 4.00),
+ (135.00, 2.74, 5.00),
+ ( 'inf', 4.42, 5.75),
+ ),
+ 'bi-weekly': (
+ ( 29.00, 0.00, 1.00),
+ ( 86.50, 0.29, 2.00),
+ (144.00, 1.44, 3.00),
+ (202.00, 3.17, 4.00),
+ (269.00, 5.48, 5.00),
+ ( 'inf', 8.85, 5.75),
+ ),
+ 'semi-monthly': (
+ ( 31.00, 0.00, 1.00),
+ ( 93.50, 0.31, 2.00),
+ (156.00, 1.56, 3.00),
+ (219.00, 3.34, 4.00),
+ (292.00, 5.94, 5.00),
+ ( 'inf', 9.58, 5.75),
+ ),
+ 'monthly': (
+ ( 62.50, 0.00, 1.00),
+ (187.00, 0.62, 2.00),
+ (312.00, 3.12, 3.00),
+ (437.00, 6.87, 4.00),
+ (583.00, 11.87, 5.00),
+ ( 'inf', 19.17, 5.75),
+ ),
+ 'quarterly': (
+ ( 187.50, 0.00, 1.00),
+ ( 562.50, 1.88, 2.00),
+ ( 937.50, 9.38, 3.00),
+ (1312.00, 20.63, 4.00),
+ (1750.00, 35.63, 5.00),
+ ( 'inf', 57.50, 5.75),
+ ),
+ 'semi-annual': (
+ ( 375.00, 0.00, 1.00),
+ (1125.00, 3.75, 2.00),
+ (1875.00, 18.75, 3.00),
+ (2625.00, 41.25, 4.00),
+ (3500.00, 71.25, 5.00),
+ ( 'inf', 115.00, 5.75),
+ ),
+ 'annual': (
+ ( 750.00, 0.00, 1.00),
+ (2250.00, 7.50, 2.00),
+ (3750.00, 37.50, 3.00),
+ (5250.00, 82.50, 4.00),
+ (7000.00, 142.50, 5.00),
+ ( 'inf', 230.00, 5.75),
+ ),
+ },
+ 'head of household': {
+ 'weekly': (
+ ( 19.00, 0.00, 1.00),
+ ( 57.50, 0.19, 2.00),
+ ( 96.00, 0.96, 3.00),
+ (135.00, 2.12, 4.00),
+ (192.50, 3.65, 5.00),
+ ( 'inf', 6.54, 5.75),
+ ),
+ 'bi-weekly': (
+ ( 38.50, 0.00, 1.00),
+ (115.00, 0.38, 2.00),
+ (192.00, 1.92, 3.00),
+ (269.00, 4.23, 4.00),
+ (385.00, 7.31, 5.00),
+ ( 'inf', 13.08, 5.75),
+ ),
+ 'semi-monthly': (
+ ( 41.50, 0.00, 1.00),
+ (125.00, 0.42, 2.00),
+ (208.00, 2.08, 3.00),
+ (292.00, 4.58, 4.00),
+ (417.00, 7.92, 5.00),
+ ( 'inf', 14.17, 5.75),
+ ),
+ 'monthly': (
+ ( 83.00, 0.00, 1.00),
+ (250.00, 0.83, 2.00),
+ (417.00, 4.17, 3.00),
+ (583.00, 9.17, 4.00),
+ (833.00, 15.83, 5.00),
+ ( 'inf', 28.33, 5.75),
+ ),
+ 'quarterly': (
+ ( 250.00, 0.00, 1.00),
+ ( 750.00, 2.50, 2.00),
+ (1250.00, 12.50, 3.00),
+ (1750.00, 27.50, 4.00),
+ (2500.00, 47.50, 5.00),
+ ( 'inf', 85.00, 5.75),
+ ),
+ 'semi-annual': (
+ ( 500.00, 0.00, 1.00),
+ (1500.00, 5.00, 2.00),
+ (2500.00, 25.00, 3.00),
+ (3500.00, 55.00, 4.00),
+ (5000.00, 95.00, 5.00),
+ ( 'inf', 170.00, 5.75),
+ ),
+ 'annual': (
+ ( 1000.00, 0.00, 1.00),
+ ( 3000.00, 10.00, 2.00),
+ ( 5000.00, 50.00, 3.00),
+ ( 7000.00, 110.00, 4.00),
+ (10000.00, 190.00, 5.00),
+ ( 'inf', 340.00, 5.75),
+ ),
+ },
+ 'married filing separate': {
+ 'weekly': (
+ ( 9.50, 0.00, 1.00),
+ (29.00, 0.10, 2.00),
+ (48.00, 0.48, 3.00),
+ (67.50, 1.06, 4.00),
+ (96.00, 1.83, 5.00),
+ ('inf', 3.27, 5.75),
+ ),
+ 'bi-weekly': (
+ ( 19.00, 0.00, 1.00),
+ ( 57.50, 0.19, 2.00),
+ ( 96.00, 0.96, 3.00),
+ (135.00, 2.12, 4.00),
+ (192.00, 3.65, 5.00),
+ ( 'inf', 6.54, 5.75),
+ ),
+ 'semi-monthly': (
+ ( 21.00, 0.00, 1.00),
+ ( 62.50, 0.21, 2.00),
+ (104.00, 1.04, 3.00),
+ (146.00, 2.29, 4.00),
+ (208.00, 3.96, 5.00),
+ ( 'inf', 7.08, 5.75),
+ ),
+ 'monthly': (
+ ( 41.50, 0.00, 1.00),
+ (125.50, 0.42, 2.00),
+ (208.00, 2.08, 3.00),
+ (292.00, 4.58, 4.00),
+ (417.00, 7.92, 5.00),
+ ( 'inf', 14.17, 5.75),
+ ),
+ 'quarterly': (
+ ( 125.00, 0.00, 1.00),
+ ( 375.00, 1.25, 2.00),
+ ( 625.00, 6.25, 3.00),
+ ( 875.00, 13.75, 4.00),
+ (1250.00, 23.75, 5.00),
+ ( 'inf', 42.50, 5.75),
+ ),
+ 'semi-annual': (
+ ( 250.00, 0.00, 1.00),
+ ( 750.00, 2.50, 2.00),
+ (1250.00, 12.50, 3.00),
+ (1750.00, 27.50, 4.00),
+ (2500.00, 47.50, 5.00),
+ ( 'inf', 85.00, 5.75),
+ ),
+ 'annual': (
+ ( 500.00, 0.00, 1.00),
+ (1500.00, 5.00, 2.00),
+ (2500.00, 25.00, 3.00),
+ (3500.00, 55.00, 4.00),
+ (5000.00, 95.00, 5.00),
+ ( 'inf', 170.00, 5.75),
+ ),
+ },
+ }
+
+
+
+
+
+
+ {
+ 'married filing joint, both spouses working': {
+ 'weekly': (
+ ( 9.50, 0.00, 1.00),
+ (29.00, 0.10, 2.00),
+ (48.00, 0.48, 3.00),
+ (67.50, 1.06, 4.00),
+ (96.00, 1.83, 5.00),
+ ('inf', 3.27, 5.75),
+ ),
+ 'bi-weekly': (
+ ( 19.00, 0.00, 1.00),
+ ( 57.50, 0.19, 2.00),
+ ( 96.00, 0.96, 3.00),
+ (135.00, 2.12, 4.00),
+ (192.00, 3.65, 5.00),
+ ( 'inf', 6.54, 5.75),
+ ),
+ 'semi-monthly': (
+ ( 21.00, 0.00, 1.00),
+ ( 62.50, 0.21, 2.00),
+ (104.00, 1.04, 3.00),
+ (146.00, 2.29, 4.00),
+ (208.00, 3.96, 5.00),
+ ( 'inf', 7.08, 5.75),
+ ),
+ 'monthly': (
+ ( 41.50, 0.00, 1.00),
+ (125.50, 0.42, 2.00),
+ (208.00, 2.08, 3.00),
+ (292.00, 4.58, 4.00),
+ (417.00, 7.92, 5.00),
+ ( 'inf', 14.17, 5.75),
+ ),
+ 'quarterly': (
+ ( 125.00, 0.00, 1.00),
+ ( 375.00, 1.25, 2.00),
+ ( 625.00, 6.25, 3.00),
+ ( 875.00, 13.75, 4.00),
+ (1250.00, 23.75, 5.00),
+ ( 'inf', 42.50, 5.75),
+ ),
+ 'semi-annual': (
+ ( 250.00, 0.00, 1.00),
+ ( 750.00, 2.50, 2.00),
+ (1250.00, 12.50, 3.00),
+ (1750.00, 27.50, 4.00),
+ (2500.00, 47.50, 5.00),
+ ( 'inf', 85.00, 5.75),
+ ),
+ 'annual': (
+ ( 500.00, 0.00, 1.00),
+ (1500.00, 5.00, 2.00),
+ (2500.00, 25.00, 3.00),
+ (3500.00, 55.00, 4.00),
+ (5000.00, 95.00, 5.00),
+ ( 'inf', 170.00, 5.75),
+ ),
+ },
+ 'married filing joint, one spouse working': {
+ 'weekly': (
+ ( 19.00, 0.00, 1.00),
+ ( 57.50, 0.19, 2.00),
+ ( 96.00, 0.96, 3.00),
+ (135.00, 2.12, 4.00),
+ (192.50, 3.65, 5.00),
+ ( 'inf', 6.54, 5.75),
+ ),
+ 'bi-weekly': (
+ ( 38.50, 0.00, 1.00),
+ (115.00, 0.38, 2.00),
+ (192.00, 1.92, 3.00),
+ (269.00, 4.23, 4.00),
+ (385.00, 7.31, 5.00),
+ ( 'inf', 13.08, 5.75),
+ ),
+ 'semi-monthly': (
+ ( 41.50, 0.00, 1.00),
+ (125.00, 0.42, 2.00),
+ (208.00, 2.08, 3.00),
+ (292.00, 4.58, 4.00),
+ (417.00, 7.92, 5.00),
+ ( 'inf', 14.17, 5.75),
+ ),
+ 'monthly': (
+ ( 83.00, 0.00, 1.00),
+ (250.00, 0.83, 2.00),
+ (417.00, 4.17, 3.00),
+ (583.00, 9.17, 4.00),
+ (833.00, 15.83, 5.00),
+ ( 'inf', 28.33, 5.75),
+ ),
+ 'quarterly': (
+ ( 250.00, 0.00, 1.00),
+ ( 750.00, 2.50, 2.00),
+ (1250.00, 12.50, 3.00),
+ (1750.00, 27.50, 4.00),
+ (2500.00, 47.50, 5.00),
+ ( 'inf', 85.00, 5.75),
+ ),
+ 'semi-annual': (
+ ( 500.00, 0.00, 1.00),
+ (1500.00, 5.00, 2.00),
+ (2500.00, 25.00, 3.00),
+ (3500.00, 55.00, 4.00),
+ (5000.00, 95.00, 5.00),
+ ( 'inf', 170.00, 5.75),
+ ),
+ 'annual': (
+ ( 1000.00, 0.00, 1.00),
+ ( 3000.00, 10.00, 2.00),
+ ( 5000.00, 50.00, 3.00),
+ ( 7000.00, 110.00, 4.00),
+ (10000.00, 190.00, 5.00),
+ ( 'inf', 340.00, 5.75),
+ ),
+ },
+ 'single': {
+ 'weekly': (
+ ( 14.50, 0.00, 1.00),
+ ( 43.50, 0.14, 2.00),
+ ( 72.00, 0.72, 3.00),
+ (101.00, 1.59, 4.00),
+ (135.00, 2.74, 5.00),
+ ( 'inf', 4.42, 5.75),
+ ),
+ 'bi-weekly': (
+ ( 29.00, 0.00, 1.00),
+ ( 86.50, 0.29, 2.00),
+ (144.00, 1.44, 3.00),
+ (202.00, 3.17, 4.00),
+ (269.00, 5.48, 5.00),
+ ( 'inf', 8.85, 5.75),
+ ),
+ 'semi-monthly': (
+ ( 31.00, 0.00, 1.00),
+ ( 93.50, 0.31, 2.00),
+ (156.00, 1.56, 3.00),
+ (219.00, 3.34, 4.00),
+ (292.00, 5.94, 5.00),
+ ( 'inf', 9.58, 5.75),
+ ),
+ 'monthly': (
+ ( 62.50, 0.00, 1.00),
+ (187.00, 0.62, 2.00),
+ (312.00, 3.12, 3.00),
+ (437.00, 6.87, 4.00),
+ (583.00, 11.87, 5.00),
+ ( 'inf', 19.17, 5.75),
+ ),
+ 'quarterly': (
+ ( 187.50, 0.00, 1.00),
+ ( 562.50, 1.88, 2.00),
+ ( 937.50, 9.38, 3.00),
+ (1312.00, 20.63, 4.00),
+ (1750.00, 35.63, 5.00),
+ ( 'inf', 57.50, 5.75),
+ ),
+ 'semi-annual': (
+ ( 375.00, 0.00, 1.00),
+ (1125.00, 3.75, 2.00),
+ (1875.00, 18.75, 3.00),
+ (2625.00, 41.25, 4.00),
+ (3500.00, 71.25, 5.00),
+ ( 'inf', 115.00, 5.75),
+ ),
+ 'annual': (
+ ( 750.00, 0.00, 1.00),
+ (2250.00, 7.50, 2.00),
+ (3750.00, 37.50, 3.00),
+ (5250.00, 82.50, 4.00),
+ (7000.00, 142.50, 5.00),
+ ( 'inf', 230.00, 5.75),
+ ),
+ },
+ 'head of household': {
+ 'weekly': (
+ ( 19.00, 0.00, 1.00),
+ ( 57.50, 0.19, 2.00),
+ ( 96.00, 0.96, 3.00),
+ (135.00, 2.12, 4.00),
+ (192.50, 3.65, 5.00),
+ ( 'inf', 6.54, 5.75),
+ ),
+ 'bi-weekly': (
+ ( 38.50, 0.00, 1.00),
+ (115.00, 0.38, 2.00),
+ (192.00, 1.92, 3.00),
+ (269.00, 4.23, 4.00),
+ (385.00, 7.31, 5.00),
+ ( 'inf', 13.08, 5.75),
+ ),
+ 'semi-monthly': (
+ ( 41.50, 0.00, 1.00),
+ (125.00, 0.42, 2.00),
+ (208.00, 2.08, 3.00),
+ (292.00, 4.58, 4.00),
+ (417.00, 7.92, 5.00),
+ ( 'inf', 14.17, 5.75),
+ ),
+ 'monthly': (
+ ( 83.00, 0.00, 1.00),
+ (250.00, 0.83, 2.00),
+ (417.00, 4.17, 3.00),
+ (583.00, 9.17, 4.00),
+ (833.00, 15.83, 5.00),
+ ( 'inf', 28.33, 5.75),
+ ),
+ 'quarterly': (
+ ( 250.00, 0.00, 1.00),
+ ( 750.00, 2.50, 2.00),
+ (1250.00, 12.50, 3.00),
+ (1750.00, 27.50, 4.00),
+ (2500.00, 47.50, 5.00),
+ ( 'inf', 85.00, 5.75),
+ ),
+ 'semi-annual': (
+ ( 500.00, 0.00, 1.00),
+ (1500.00, 5.00, 2.00),
+ (2500.00, 25.00, 3.00),
+ (3500.00, 55.00, 4.00),
+ (5000.00, 95.00, 5.00),
+ ( 'inf', 170.00, 5.75),
+ ),
+ 'annual': (
+ ( 1000.00, 0.00, 1.00),
+ ( 3000.00, 10.00, 2.00),
+ ( 5000.00, 50.00, 3.00),
+ ( 7000.00, 110.00, 4.00),
+ (10000.00, 190.00, 5.00),
+ ( 'inf', 340.00, 5.75),
+ ),
+ },
+ 'married filing separate': {
+ 'weekly': (
+ ( 9.50, 0.00, 1.00),
+ (29.00, 0.10, 2.00),
+ (48.00, 0.48, 3.00),
+ (67.50, 1.06, 4.00),
+ (96.00, 1.83, 5.00),
+ ('inf', 3.27, 5.75),
+ ),
+ 'bi-weekly': (
+ ( 19.00, 0.00, 1.00),
+ ( 57.50, 0.19, 2.00),
+ ( 96.00, 0.96, 3.00),
+ (135.00, 2.12, 4.00),
+ (192.00, 3.65, 5.00),
+ ( 'inf', 6.54, 5.75),
+ ),
+ 'semi-monthly': (
+ ( 21.00, 0.00, 1.00),
+ ( 62.50, 0.21, 2.00),
+ (104.00, 1.04, 3.00),
+ (146.00, 2.29, 4.00),
+ (208.00, 3.96, 5.00),
+ ( 'inf', 7.08, 5.75),
+ ),
+ 'monthly': (
+ ( 41.50, 0.00, 1.00),
+ (125.50, 0.42, 2.00),
+ (208.00, 2.08, 3.00),
+ (292.00, 4.58, 4.00),
+ (417.00, 7.92, 5.00),
+ ( 'inf', 14.17, 5.75),
+ ),
+ 'quarterly': (
+ ( 125.00, 0.00, 1.00),
+ ( 375.00, 1.25, 2.00),
+ ( 625.00, 6.25, 3.00),
+ ( 875.00, 13.75, 4.00),
+ (1250.00, 23.75, 5.00),
+ ( 'inf', 42.50, 5.75),
+ ),
+ 'semi-annual': (
+ ( 250.00, 0.00, 1.00),
+ ( 750.00, 2.50, 2.00),
+ (1250.00, 12.50, 3.00),
+ (1750.00, 27.50, 4.00),
+ (2500.00, 47.50, 5.00),
+ ( 'inf', 85.00, 5.75),
+ ),
+ 'annual': (
+ ( 500.00, 0.00, 1.00),
+ (1500.00, 5.00, 2.00),
+ (2500.00, 25.00, 3.00),
+ (3500.00, 55.00, 4.00),
+ (5000.00, 95.00, 5.00),
+ ( 'inf', 170.00, 5.75),
+ ),
+ },
+ }
+
+
+
+
+
+
+ US GA Georgia SIT Personal Allowance
+ us_ga_sit_personal_allowance
+
+
+
+
+ {
+ 'married filing joint, both spouses working': {
+ 'weekly': 142.30,
+ 'bi-weekly': 284.62,
+ 'semi-monthly': 308.33,
+ 'monthly': 616.67,
+ 'quarterly': 1850.00,
+ 'semi-annual': 3700.00,
+ 'annual': 7400.00,
+ },
+ 'married filing joint, one spouse working': {
+ 'weekly': 142.30,
+ 'bi-weekly': 284.62,
+ 'semi-monthly': 308.33,
+ 'monthly': 616.67,
+ 'quarterly': 1850.00,
+ 'semi-annual': 3700.00,
+ 'annual': 7400.00,
+ },
+ 'single': {
+ 'weekly': 51.92,
+ 'bi-weekly': 103.85,
+ 'semi-monthly': 112.50,
+ 'monthly': 225.00,
+ 'quarterly': 675.00,
+ 'semi-annual': 1350.00,
+ 'annual': 2700.00,
+ },
+ 'head of household': {
+ 'weekly': 51.92,
+ 'bi-weekly': 103.85,
+ 'semi-monthly': 112.50,
+ 'monthly': 225.00,
+ 'quarterly': 675.00,
+ 'semi-annual': 1350.00,
+ 'annual': 2700.00,
+ },
+ 'married filing separate': {
+ 'weekly': 71.15,
+ 'bi-weekly': 142.30,
+ 'semi-monthly': 154.16,
+ 'monthly': 308.33,
+ 'quarterly': 925.00,
+ 'semi-annual': 1850.00,
+ 'annual': 3700.00,
+ },
+ }
+
+
+
+
+
+
+ {
+ 'married filing joint, both spouses working': {
+ 'weekly': 142.30,
+ 'bi-weekly': 284.62,
+ 'semi-monthly': 308.33,
+ 'monthly': 616.67,
+ 'quarterly': 1850.00,
+ 'semi-annual': 3700.00,
+ 'annual': 7400.00,
+ },
+ 'married filing joint, one spouse working': {
+ 'weekly': 142.30,
+ 'bi-weekly': 284.62,
+ 'semi-monthly': 308.33,
+ 'monthly': 616.67,
+ 'quarterly': 1850.00,
+ 'semi-annual': 3700.00,
+ 'annual': 7400.00,
+ },
+ 'single': {
+ 'weekly': 51.92,
+ 'bi-weekly': 103.85,
+ 'semi-monthly': 112.50,
+ 'monthly': 225.00,
+ 'quarterly': 675.00,
+ 'semi-annual': 1350.00,
+ 'annual': 2700.00,
+ },
+ 'head of household': {
+ 'weekly': 51.92,
+ 'bi-weekly': 103.85,
+ 'semi-monthly': 112.50,
+ 'monthly': 225.00,
+ 'quarterly': 675.00,
+ 'semi-annual': 1350.00,
+ 'annual': 2700.00,
+ },
+ 'married filing separate': {
+ 'weekly': 71.15,
+ 'bi-weekly': 142.30,
+ 'semi-monthly': 154.16,
+ 'monthly': 308.33,
+ 'quarterly': 925.00,
+ 'semi-annual': 1850.00,
+ 'annual': 3700.00,
+ },
+ }
+
+
+
+
+
+
+ US GA Georgia SIT Dependent Allowance Rate
+ us_ga_sit_dependent_allowance_rate
+
+
+
+
+ {
+ 'weekly': 57.50,
+ 'bi-weekly': 115.00,
+ 'semi-monthly': 125.00,
+ 'monthly': 250.00,
+ 'quarterly': 750.00,
+ 'semi-annual': 1500.00,
+ 'annual': 3000.00,
+ }
+
+
+
+
+
+
+ {
+ 'weekly': 57.50,
+ 'bi-weekly': 115.00,
+ 'semi-monthly': 125.00,
+ 'monthly': 250.00,
+ 'quarterly': 750.00,
+ 'semi-annual': 1500.00,
+ 'annual': 3000.00,
+ }
+
+
+
+
+
+
+ US GA Georgia SIT Deduction
+ us_ga_sit_deduction
+
+
+
+
+ {
+ 'married filing joint, both spouses working': {
+ 'weekly': 115.50,
+ 'bi-weekly': 230.75,
+ 'semi-monthly': 250.00,
+ 'monthly': 500.00,
+ 'quarterly': 1500.00,
+ 'semi-annual': 3000.00,
+ 'annual': 6000.00,
+ },
+ 'married filing joint, one spouse working': {
+ 'weekly': 115.50,
+ 'bi-weekly': 230.75,
+ 'semi-monthly': 250.00,
+ 'monthly': 500.00,
+ 'quarterly': 1500.00,
+ 'semi-annual': 3000.00,
+ 'annual': 6000.00,
+ },
+ 'single': {
+ 'weekly': 88.50,
+ 'bi-weekly': 177.00,
+ 'semi-monthly': 191.75,
+ 'monthly': 383.50,
+ 'quarterly': 1150.00,
+ 'semi-annual': 2300.00,
+ 'annual': 4600.00,
+ },
+ 'head of household': {
+ 'weekly': 88.50,
+ 'bi-weekly': 177.00,
+ 'semi-monthly': 191.75,
+ 'monthly': 383.50,
+ 'quarterly': 1150.00,
+ 'semi-annual': 2300.00,
+ 'annual': 4600.00,
+ },
+ 'married filing separate': {
+ 'weekly': 57.75,
+ 'bi-weekly': 115.50,
+ 'semi-monthly': 125.00,
+ 'monthly': 250.00,
+ 'quarterly': 750.00,
+ 'semi-annual': 1500.00,
+ 'annual': 3000.00,
+ },
+ }
+
+
+
+
+
+
+ {
+ 'married filing joint, both spouses working': {
+ 'weekly': 115.50,
+ 'bi-weekly': 230.75,
+ 'semi-monthly': 250.00,
+ 'monthly': 500.00,
+ 'quarterly': 1500.00,
+ 'semi-annual': 3000.00,
+ 'annual': 6000.00,
+ },
+ 'married filing joint, one spouse working': {
+ 'weekly': 115.50,
+ 'bi-weekly': 230.75,
+ 'semi-monthly': 250.00,
+ 'monthly': 500.00,
+ 'quarterly': 1500.00,
+ 'semi-annual': 3000.00,
+ 'annual': 6000.00,
+ },
+ 'single': {
+ 'weekly': 88.50,
+ 'bi-weekly': 177.00,
+ 'semi-monthly': 191.75,
+ 'monthly': 383.50,
+ 'quarterly': 1150.00,
+ 'semi-annual': 2300.00,
+ 'annual': 4600.00,
+ },
+ 'head of household': {
+ 'weekly': 88.50,
+ 'bi-weekly': 177.00,
+ 'semi-monthly': 191.75,
+ 'monthly': 383.50,
+ 'quarterly': 1150.00,
+ 'semi-annual': 2300.00,
+ 'annual': 4600.00,
+ },
+ 'married filing separate': {
+ 'weekly': 57.75,
+ 'bi-weekly': 115.50,
+ 'semi-monthly': 125.00,
+ 'monthly': 250.00,
+ 'quarterly': 750.00,
+ 'semi-annual': 1500.00,
+ 'annual': 3000.00,
+ },
+ }
+
+
+
+
+
+
+
+ US Georgia - Department of Taxation - Unemployment Tax
+
+
+
+ US Georgia - Department of Taxation - Income Tax
+
+
+
+
+
+
+
+
+
+ ER: US GA Georgia State Unemployment
+ ER_US_GA_SUTA
+ python
+ result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_ga_suta_wage_base', rate='us_ga_suta_rate', state_code='GA')
+ code
+ result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_ga_suta_wage_base', rate='us_ga_suta_rate', state_code='GA')
+
+
+
+
+
+
+
+
+ EE: US GA Georgia State Income Tax Withholding
+ EE_US_GA_SIT
+ python
+ result, _ = ga_georgia_state_income_withholding(payslip, categories, worked_days, inputs)
+ code
+ result, result_rate = ga_georgia_state_income_withholding(payslip, categories, worked_days, inputs)
+
+
+
+
+
\ No newline at end of file
diff --git a/l10n_us_hr_payroll/data/state/hi_hawaii.xml b/l10n_us_hr_payroll/data/state/hi_hawaii.xml
new file mode 100644
index 00000000..798821f2
--- /dev/null
+++ b/l10n_us_hr_payroll/data/state/hi_hawaii.xml
@@ -0,0 +1,181 @@
+
+
+
+
+ US HI Hawaii SUTA Wage Base
+ us_hi_suta_wage_base
+
+
+
+
+ 46800.0
+
+
+
+
+ 48100.0
+
+
+
+
+
+
+
+ US HI Hawaii SUTA Rate
+ us_hi_suta_rate
+
+
+
+
+ 2.40
+
+
+
+
+ 2.40
+
+
+
+
+
+
+ US HI Hawaii SIT Tax Rate
+ us_hi_sit_tax_rate
+
+
+
+
+ {
+ 'single': (
+ ( 2400, 0.00, 1.40),
+ ( 4800, 34.00, 3.20),
+ ( 9600, 110.00, 5.50),
+ (14400, 374.00, 6.40),
+ (19200, 682.00, 6.80),
+ (24000, 1008.00, 7.20),
+ (36000, 1354.00, 7.60),
+ ('inf', 2266.00, 7.90),
+ ),
+ 'married': (
+ ( 4800, 0.00, 1.40),
+ ( 9600, 67.00, 3.20),
+ (19200, 221.00, 5.50),
+ (28800, 749.00, 6.40),
+ (38400, 1363.00, 6.80),
+ (48000, 2016.00, 7.20),
+ (72000, 2707.00, 7.60),
+ ('inf', 4531.00, 7.90),
+ ),
+ 'head_of_household': (
+ ( 2400, 0.00, 1.40),
+ ( 4800, 34.00, 3.20),
+ ( 9600, 110.00, 5.50),
+ (14400, 374.00, 6.40),
+ (19200, 682.00, 6.80),
+ (24000, 1008.00, 7.20),
+ (36000, 1354.00, 7.60),
+ ('inf', 2266.00, 7.90),
+ ),
+ }
+
+
+
+
+
+
+ {
+ 'single': (
+ ( 2400, 0.00, 1.40),
+ ( 4800, 34.00, 3.20),
+ ( 9600, 110.00, 5.50),
+ (14400, 374.00, 6.40),
+ (19200, 682.00, 6.80),
+ (24000, 1008.00, 7.20),
+ (36000, 1354.00, 7.60),
+ ('inf', 2266.00, 7.90),
+ ),
+ 'married': (
+ ( 4800, 0.00, 1.40),
+ ( 9600, 67.00, 3.20),
+ (19200, 221.00, 5.50),
+ (28800, 749.00, 6.40),
+ (38400, 1363.00, 6.80),
+ (48000, 2016.00, 7.20),
+ (72000, 2707.00, 7.60),
+ ('inf', 4531.00, 7.90),
+ ),
+ 'head_of_household': (
+ ( 2400, 0.00, 1.40),
+ ( 4800, 34.00, 3.20),
+ ( 9600, 110.00, 5.50),
+ (14400, 374.00, 6.40),
+ (19200, 682.00, 6.80),
+ (24000, 1008.00, 7.20),
+ (36000, 1354.00, 7.60),
+ ('inf', 2266.00, 7.90),
+ ),
+ }
+
+
+
+
+
+
+ US HI Hawaii Personal Exemption Rate
+ us_hi_sit_personal_exemption_rate
+
+
+
+
+ 1144
+
+
+
+
+ 1144
+
+
+
+
+
+
+
+ US Hawaii - Department of Labor and Industrial Relations - Unemployment Tax
+
+
+
+ US Hawaii - Department of Taxation - Income Tax
+
+
+
+
+
+
+
+
+
+ ER: US HI Hawaii State Unemployment
+ ER_US_HI_SUTA
+ python
+ result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_hi_suta_wage_base', rate='us_hi_suta_rate', state_code='HI')
+ code
+ result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_hi_suta_wage_base', rate='us_hi_suta_rate', state_code='HI')
+
+
+
+
+
+
+
+
+ EE: US HI Hawaii State Income Tax Withholding
+ EE_US_HI_SIT
+ python
+ result, _ = hi_hawaii_state_income_withholding(payslip, categories, worked_days, inputs)
+ code
+ result, result_rate = hi_hawaii_state_income_withholding(payslip, categories, worked_days, inputs)
+
+
+
+
+
\ No newline at end of file
diff --git a/l10n_us_hr_payroll/data/state/ia_iowa.xml b/l10n_us_hr_payroll/data/state/ia_iowa.xml
new file mode 100644
index 00000000..dcc90009
--- /dev/null
+++ b/l10n_us_hr_payroll/data/state/ia_iowa.xml
@@ -0,0 +1,303 @@
+
+
+
+
+ US IA Iowa SUTA Wage Base
+ us_ia_suta_wage_base
+
+
+
+
+ 30600.0
+
+
+
+
+ 31600.0
+
+
+
+
+
+
+
+ US IA Iowa SUTA Rate
+ us_ia_suta_rate
+
+
+
+
+ 1.0
+
+
+
+
+ 1.0
+
+
+
+
+
+
+ US IA Iowa SIT Tax Rate
+ us_ia_sit_tax_rate
+
+
+
+
+ {
+ 'daily': (
+ ( 5.13, 0.0033, 0.00),
+ ( 10.25, 0.0067, 0.02),
+ ( 20.50, 0.0225, 0.05),
+ ( 46.13, 0.0414, 0.28),
+ ( 76.89, 0.0563, 1.34),
+ (102.52, 0.0596, 3.07),
+ (153.78, 0.0625, 4.60),
+ (230.68, 0.0744, 7.80),
+ ( 'inf', 0.0853, 13.52),
+ ),
+ 'weekly': (
+ ( 25.63, 0.0033, 0.00),
+ ( 51.27, 0.0067, 0.08),
+ ( 102.52, 0.0225, 0.25),
+ ( 230.67, 0.0414, 1.40),
+ ( 384.46, 0.0563, 6.71),
+ ( 512.62, 0.0596, 15.37),
+ ( 768.92, 0.0625, 23.01),
+ (1153.38, 0.0744, 39.03),
+ ( 'inf', 0.0853, 67.63),
+ ),
+ 'bi-weekly': (
+ ( 51.27, 0.0033, 0.00),
+ ( 102.54, 0.0067, 0.17),
+ ( 205.04, 0.0225, 0.51),
+ ( 461.35, 0.0414, 2.82),
+ ( 768.92, 0.0563, 13.43),
+ (1025.23, 0.0596, 30.75),
+ (1537.85, 0.0625, 46.03),
+ (2306.77, 0.0744, 78.07),
+ ( 'inf', 0.0853, 135.28),
+ ),
+ 'semi-monthly': (
+ ( 55.54, 0.0033, 0.00),
+ ( 111.08, 0.0067, 0.18),
+ ( 222.13, 0.0225, 0.55),
+ ( 499.79, 0.0414, 3.05),
+ ( 833.00, 0.0563, 14.59),
+ (1110.67, 0.0596, 33.31),
+ (1666.00, 0.0625, 49.86),
+ (2499.00, 0.0744, 84.57),
+ ( 'inf', 0.0853, 146.55),
+ ),
+ 'monthly': (
+ ( 111.08, 0.0033, 0.00),
+ ( 222.17, 0.0067, 0.37),
+ ( 444.25, 0.0225, 1.11),
+ ( 999.58, 0.0414, 6.11),
+ (1666.00, 0.0563, 29.10),
+ (2221.33, 0.0596, 62.66),
+ (3332.00, 0.0625, 99.72),
+ (4998.00, 0.0744, 169.14),
+ ( 'inf', 0.0853, 293.09),
+ ),
+ 'annual': (
+ ( 1333.00, 0.0033, 0.00),
+ ( 2666.00, 0.0067, 4.40),
+ ( 5331.00, 0.0225, 13.33),
+ (11995.00, 0.0414, 73.29),
+ (19992.00, 0.0563, 349.19),
+ (26656.00, 0.0596, 799.41),
+ (39984.00, 0.0625, 1196.58),
+ (59976.00, 0.0744, 2029.58),
+ ( 'inf', 0.0853, 3516.98),
+ ),
+ }
+
+
+
+
+
+
+ {
+ 'daily': (
+ ( 5.69, 0.0033, 0.00),
+ ( 11.38, 0.0067, 0.02),
+ ( 22.76, 0.0225, 0.06),
+ ( 51.22, 0.0414, 0.32),
+ ( 85.36, 0.0563, 1.50),
+ (113.81, 0.0596, 3.42),
+ (170.71, 0.0625, 5.12),
+ (256.07, 0.0744, 8.68),
+ ( 'inf', 0.0853, 15.03),
+ ),
+ 'weekly': (
+ ( 28.46, 0.0033, 0.00),
+ ( 56.90, 0.0067, 0.09),
+ ( 113.81, 0.0225, 0.28),
+ ( 256.08, 0.0414, 1.56),
+ ( 426.79, 0.0563, 7.45),
+ ( 569.04, 0.0596, 17.06),
+ ( 853.56, 0.0625, 25.54),
+ (1280.35, 0.0744, 43.32),
+ ( 'inf', 0.0853, 75.07),
+ ),
+ 'bi-weekly': (
+ ( 56.92, 0.0033, 0.00),
+ ( 113.81, 0.0067, 0.19),
+ ( 227.62, 0.0225, 0.57),
+ ( 512.15, 0.0414, 3.13),
+ ( 853.58, 0.0563, 14.91),
+ (1138.08, 0.0596, 34.13),
+ (1707.12, 0.0625, 51.09),
+ (2560.69, 0.0744, 86.66),
+ ( 'inf', 0.0853, 150.17),
+ ),
+ 'semi-monthly': (
+ ( 61.67, 0.0033, 0.00),
+ ( 23.29, 0.0067, 0.20),
+ ( 246.58, 0.0225, 0.61),
+ ( 554.83, 0.0414, 3.38),
+ ( 924.71, 0.0563, 16.14),
+ (1232.92, 0.0596, 36.96),
+ (1849.38, 0.0625, 55.33),
+ (2774.08, 0.0744, 93.86),
+ ( 'inf', 0.0853, 162.66),
+ ),
+ 'monthly': (
+ ( 123.33, 0.0033, 0.00),
+ ( 246.58, 0.0067, 0.41),
+ ( 493.17, 0.0225, 1.24),
+ (1109.67, 0.0414, 6.79),
+ (1849.42, 0.0563, 32.31),
+ (2465.83, 0.0596, 73.96),
+ (3698.75, 0.0625, 110.70),
+ (5548.17, 0.0744, 187.76),
+ ( 'inf', 0.0853, 325.36),
+ ),
+ 'annual': (
+ ( 1480.00, 0.0033, 0.00),
+ ( 2959.00, 0.0067, 4.88),
+ ( 5918.00, 0.0225, 14.79),
+ (13316.00, 0.0414, 81.37),
+ (22193.00, 0.0563, 387.65),
+ (29590.00, 0.0596, 887.43),
+ (44385.00, 0.0625, 1328.29),
+ (66578.00, 0.0744, 2252.98),
+ ( 'inf', 0.0853, 3904.14),
+ ),
+ }
+
+
+
+
+
+
+ US IA Iowa Standard Deduction Rate
+ us_ia_sit_standard_deduction_rate
+
+
+
+
+ {
+ 'daily': ( 6.50, 16.00),
+ 'weekly': ( 32.50, 80.00),
+ 'bi-weekly': ( 65.00, 160.00),
+ 'semi-monthly': ( 70.42, 173.33),
+ 'monthly': ( 140.83, 346.67),
+ 'annually': (1690.00, 4160.00),
+ }
+
+
+
+
+
+
+ {
+ 'daily': ( 7.23, 17.81),
+ 'weekly': ( 36.15, 89.04),
+ 'bi-weekly': ( 72.31, 178.08),
+ 'semi-monthly': ( 78.33, 192.92),
+ 'monthly': ( 156.67, 385.83),
+ 'annually': (1880.00, 4630.00),
+ }
+
+
+
+
+
+
+ US IA Iowa Deduction Allowance Rate
+ us_ia_sit_deduction_allowance_rate
+
+
+
+
+ {
+ 'daily': 0.15,
+ 'weekly': 0.77,
+ 'bi-weekly': 1.54,
+ 'semi-monthly': 1.67,
+ 'monthly': 3.33,
+ 'annually': 40.00,
+ }
+
+
+
+
+
+
+ {
+ 'daily': 0.15,
+ 'weekly': 0.77,
+ 'bi-weekly': 1.54,
+ 'semi-monthly': 1.67,
+ 'monthly': 3.33,
+ 'annually': 40.00,
+ }
+
+
+
+
+
+
+
+ US Iowa - Department of Economic Security (IDES) - Unemployment Tax
+
+
+
+ US Iowa - Department of Revenue (IDOR) - Income Tax
+
+
+
+
+
+
+
+
+
+ ER: US IA Iowa State Unemployment
+ ER_US_IA_SUTA
+ python
+ result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_ia_suta_wage_base', rate='us_ia_suta_rate', state_code='IA')
+ code
+ result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_ia_suta_wage_base', rate='us_ia_suta_rate', state_code='IA')
+
+
+
+
+
+
+
+
+ EE: US IA Iowa State Income Tax Withholding
+ EE_US_IA_SIT
+ python
+ result, _ = ia_iowa_state_income_withholding(payslip, categories, worked_days, inputs)
+ code
+ result, result_rate = ia_iowa_state_income_withholding(payslip, categories, worked_days, inputs)
+
+
+
+
+
\ No newline at end of file
diff --git a/l10n_us_hr_payroll/data/state/id_idaho.xml b/l10n_us_hr_payroll/data/state/id_idaho.xml
new file mode 100644
index 00000000..46dcce9d
--- /dev/null
+++ b/l10n_us_hr_payroll/data/state/id_idaho.xml
@@ -0,0 +1,448 @@
+
+
+
+
+ US ID Idaho SUTA Wage Base
+ us_id_suta_wage_base
+
+
+
+
+ 40000.0
+
+
+
+
+ 41600.0
+
+
+
+
+
+
+
+ US ID Idaho SUTA Rate
+ us_id_suta_rate
+
+
+
+
+ 1.0
+
+
+
+
+ 1.0
+
+
+
+
+
+
+ US ID Idaho SIT Tax Rate
+ us_id_sit_tax_rate
+
+
+
+
+ {
+ 'single': {
+ 'weekly': (
+ ( 235, 0.00, 0.000),
+ ( 264, 0.00, 1.125),
+ ( 294, 0.00, 3.125),
+ ( 324, 1.00, 3.625),
+ ( 353, 2.00, 4.625),
+ ( 383, 4.00, 5.625),
+ ( 457, 5.00, 6.625),
+ ('inf', 10.00, 6.925),
+ ),
+ 'bi-weekly': (
+ ( 469, 0.00, 0.000),
+ ( 529, 0.00, 1.125),
+ ( 588, 1.00, 3.125),
+ ( 647, 3.00, 3.625),
+ ( 706, 5.00, 4.625),
+ ( 766, 7.00, 5.625),
+ ( 914, 11.00, 6.625),
+ ('inf', 21.00, 6.925),
+ ),
+ 'semi-monthly': (
+ ( 508, 0.00, 0.000),
+ ( 573, 0.00, 1.125),
+ ( 637, 1.00, 3.125),
+ ( 701, 3.00, 3.625),
+ ( 765, 5.00, 4.625),
+ ( 829, 8.00, 5.625),
+ ( 990, 12.00, 6.625),
+ ('inf', 22.00, 6.925),
+ ),
+ 'monthly': (
+ ( 1017, 0.00, 0.000),
+ ( 1145, 0.00, 1.125),
+ ( 1273, 1.00, 3.125),
+ ( 1402, 5.00, 3.625),
+ ( 1530, 10.00, 4.625),
+ ( 1659, 16.00, 5.625),
+ ( 1980, 23.00, 6.625),
+ ('inf', 45.00, 6.925),
+ ),
+ 'annually': (
+ (12200, 0.00, 0.000),
+ (13741, 0.00, 1.125),
+ (15281, 17.00, 3.125),
+ (16822, 65.00, 3.625),
+ (18362, 121.00, 4.625),
+ (19903, 192.00, 5.625),
+ (23754, 279.00, 6.625),
+ ('inf', 534.00, 6.925),
+ ),
+ },
+ 'married': {
+ 'weekly': (
+ ( 469, 0.00, 0.000),
+ ( 529, 0.00, 1.125),
+ ( 588, 0.00, 3.125),
+ ( 647, 1.00, 3.625),
+ ( 706, 2.00, 4.625),
+ ( 766, 4.00, 5.625),
+ ( 914, 5.00, 6.625),
+ ('inf', 10.00, 6.925),
+ ),
+ 'bi-weekly': (
+ ( 938, 0.00, 0.000),
+ ( 1057, 0.00, 1.125),
+ ( 1175, 1.00, 3.125),
+ ( 1294, 5.00, 3.625),
+ ( 1412, 9.00, 4.625),
+ ( 1531, 15.00, 5.625),
+ ( 1827, 21.00, 6.625),
+ ('inf', 41.00, 6.925),
+ ),
+ 'semi-monthly': (
+ ( 1017, 0.00, 0.000),
+ ( 1145, 0.00, 1.125),
+ ( 1273, 1.00, 3.125),
+ ( 1402, 5.00, 3.625),
+ ( 1530, 10.00, 4.625),
+ ( 1659, 16.00, 5.625),
+ ( 1980, 23.00, 6.625),
+ ('inf', 45.00, 6.925),
+ ),
+ 'monthly': (
+ ( 2033, 0.00, 0.000),
+ ( 2290, 0.00, 1.125),
+ ( 2547, 3.00, 3.125),
+ ( 2804, 11.00, 3.625),
+ ( 3060, 20.00, 4.625),
+ ( 3317, 32.00, 5.625),
+ ( 3959, 47.00, 6.625),
+ ('inf', 89.00, 6.925),
+ ),
+ 'annually': (
+ (24400, 0.00, 0.000),
+ (27482, 0.00, 1.125),
+ (30562, 35.00, 3.125),
+ (33644, 131.00, 3.625),
+ (36724, 243.00, 4.625),
+ (39806, 385.00, 5.625),
+ (47508, 558.00, 6.625),
+ ('inf', 1068.00, 6.925),
+ ),
+ },
+ 'head of household': {
+ 'weekly': (
+ ( 235, 0.00, 0.000),
+ ( 264, 0.00, 1.125),
+ ( 294, 0.00, 3.125),
+ ( 324, 1.00, 3.625),
+ ( 353, 2.00, 4.625),
+ ( 383, 4.00, 5.625),
+ ( 457, 5.00, 6.625),
+ ('inf', 10.00, 6.925),
+ ),
+ 'bi-weekly': (
+ ( 469, 0.00, 0.000),
+ ( 529, 0.00, 1.125),
+ ( 588, 1.00, 3.125),
+ ( 647, 3.00, 3.625),
+ ( 706, 5.00, 4.625),
+ ( 766, 7.00, 5.625),
+ ( 914, 11.00, 6.625),
+ ('inf', 21.00, 6.925),
+ ),
+ 'semi-monthly': (
+ ( 508, 0.00, 0.000),
+ ( 573, 0.00, 1.125),
+ ( 637, 1.00, 3.125),
+ ( 701, 3.00, 3.625),
+ ( 765, 5.00, 4.625),
+ ( 829, 8.00, 5.625),
+ ( 990, 12.00, 6.625),
+ ('inf', 22.00, 6.925),
+ ),
+ 'monthly': (
+ ( 1017, 0.00, 0.000),
+ ( 1145, 0.00, 1.125),
+ ( 1273, 1.00, 3.125),
+ ( 1402, 5.00, 3.625),
+ ( 1530, 10.00, 4.625),
+ ( 1659, 16.00, 5.625),
+ ( 1980, 23.00, 6.625),
+ ('inf', 45.00, 6.925),
+ ),
+ 'annually': (
+ (12200, 0.00, 0.000),
+ (13741, 0.00, 1.125),
+ (15281, 17.00, 3.125),
+ (16822, 65.00, 3.625),
+ (18362, 121.00, 4.625),
+ (19903, 192.00, 5.625),
+ (23754, 279.00, 6.625),
+ ('inf', 534.00, 6.925),
+ ),
+ },
+ }
+
+
+
+
+
+
+ {
+ 'single': {
+ 'weekly': (
+ ( 238, 0.00, 0.000),
+ ( 269, 0.00, 1.125),
+ ( 299, 0.00, 3.125),
+ ( 329, 1.00, 3.625),
+ ( 359, 2.00, 4.625),
+ ( 389, 4.00, 5.625),
+ ( 465, 5.00, 6.625),
+ ('inf', 10.00, 6.925),
+ ),
+ 'bi-weekly': (
+ ( 477, 0.00, 0.000),
+ ( 537, 0.00, 1.125),
+ ( 598, 1.00, 3.125),
+ ( 658, 3.00, 3.625),
+ ( 718, 5.00, 4.625),
+ ( 778, 8.00, 5.625),
+ ( 929, 11.00, 6.625),
+ ('inf', 21.00, 6.925),
+ ),
+ 'semi-monthly': (
+ ( 517, 0.00, 0.000),
+ ( 582, 0.00, 1.125),
+ ( 647, 1.00, 3.125),
+ ( 713, 3.00, 3.625),
+ ( 778, 5.00, 4.625),
+ ( 843, 8.00, 5.625),
+ ( 1007, 12.00, 6.625),
+ ('inf', 23.00, 6.925),
+ ),
+ 'monthly': (
+ ( 1033, 0.00, 0.000),
+ ( 1164, 0.00, 1.125),
+ ( 1295, 1.00, 3.125),
+ ( 1425, 6.00, 3.625),
+ ( 1556, 10.00, 4.625),
+ ( 1687, 16.00, 5.625),
+ ( 2013, 24.00, 6.625),
+ ('inf', 45.00, 6.925),
+ ),
+ 'annually': (
+ (12400, 0.00, 0.000),
+ (13968, 0.00, 1.125),
+ (15536, 18.00, 3.125),
+ (17104, 67.00, 3.625),
+ (18672, 124.00, 4.625),
+ (20240, 197.00, 5.625),
+ (24160, 285.00, 6.625),
+ ('inf', 545.00, 6.925),
+ ),
+ },
+ 'married': {
+ 'weekly': (
+ ( 477, 0.00, 0.000),
+ ( 537, 0.00, 1.125),
+ ( 598, 0.00, 3.125),
+ ( 658, 1.00, 3.625),
+ ( 718, 3.00, 4.625),
+ ( 778, 5.00, 5.625),
+ ( 929, 11.00, 6.625),
+ ('inf', 21.00, 6.925),
+ ),
+ 'bi-weekly': (
+ ( 954, 0.00, 0.000),
+ ( 1074, 0.00, 1.125),
+ ( 1195, 1.00, 3.125),
+ ( 1316, 5.00, 3.625),
+ ( 1436, 9.00, 4.625),
+ ( 1557, 15.00, 5.625),
+ ( 1858, 22.00, 6.625),
+ ('inf', 42.00, 6.925),
+ ),
+ 'semi-monthly': (
+ ( 1033, 0.00, 0.000),
+ ( 1164, 0.00, 1.125),
+ ( 1295, 1.00, 3.125),
+ ( 1425, 6.00, 3.625),
+ ( 1556, 10.00, 4.625),
+ ( 1687, 16.00, 5.625),
+ ( 2013, 24.00, 6.625),
+ ('inf', 45.00, 6.925),
+ ),
+ 'monthly': (
+ ( 2067, 0.00, 0.000),
+ ( 2328, 0.00, 1.125),
+ ( 2589, 3.00, 3.125),
+ ( 2851, 11.00, 3.625),
+ ( 3112, 21.00, 4.625),
+ ( 3373, 33.00, 5.625),
+ ( 4027, 47.00, 6.625),
+ ('inf', 91.00, 6.925),
+ ),
+ 'annually': (
+ (24400, 0.00, 0.000),
+ (27482, 0.00, 1.125),
+ (30562, 35.00, 3.125),
+ (33644, 131.00, 3.625),
+ (36724, 243.00, 4.625),
+ (39806, 385.00, 5.625),
+ (47508, 558.00, 6.625),
+ ('inf', 1068.00, 6.925),
+ ),
+ },
+ 'head of household': {
+ 'weekly': (
+ ( 238, 0.00, 0.000),
+ ( 269, 0.00, 1.125),
+ ( 299, 0.00, 3.125),
+ ( 329, 1.00, 3.625),
+ ( 359, 2.00, 4.625),
+ ( 389, 4.00, 5.625),
+ ( 465, 5.00, 6.625),
+ ('inf', 10.00, 6.925),
+ ),
+ 'bi-weekly': (
+ ( 477, 0.00, 0.000),
+ ( 537, 0.00, 1.125),
+ ( 598, 1.00, 3.125),
+ ( 658, 3.00, 3.625),
+ ( 718, 5.00, 4.625),
+ ( 778, 8.00, 5.625),
+ ( 929, 11.00, 6.625),
+ ('inf', 21.00, 6.925),
+ ),
+ 'semi-monthly': (
+ ( 517, 0.00, 0.000),
+ ( 582, 0.00, 1.125),
+ ( 647, 1.00, 3.125),
+ ( 713, 3.00, 3.625),
+ ( 778, 5.00, 4.625),
+ ( 843, 8.00, 5.625),
+ ( 1007, 12.00, 6.625),
+ ('inf', 23.00, 6.925),
+ ),
+ 'monthly': (
+ ( 1033, 0.00, 0.000),
+ ( 1164, 0.00, 1.125),
+ ( 1295, 1.00, 3.125),
+ ( 1425, 6.00, 3.625),
+ ( 1556, 10.00, 4.625),
+ ( 1687, 16.00, 5.625),
+ ( 2013, 24.00, 6.625),
+ ('inf', 45.00, 6.925),
+ ),
+ 'annually': (
+ (12400, 0.00, 0.000),
+ (13968, 0.00, 1.125),
+ (15536, 18.00, 3.125),
+ (17104, 67.00, 3.625),
+ (18672, 124.00, 4.625),
+ (20240, 197.00, 5.625),
+ (24160, 285.00, 6.625),
+ ('inf', 545.00, 6.925),
+ ),
+ },
+ }
+
+
+
+
+
+
+ US ID Idaho Child Tax Credit Allowance Rate
+ us_id_sit_ictcat_rate
+
+
+
+
+ {
+ 'weekly': 56.92,
+ 'bi-weekly': 113.85,
+ 'semi-monthly': 123.33,
+ 'monthly': 246.67,
+ 'annually': 2960.00,
+ }
+
+
+
+
+
+
+ {
+ 'weekly': 56.92,
+ 'bi-weekly': 113.85,
+ 'semi-monthly': 123.33,
+ 'monthly': 246.67,
+ 'annually': 2960.00,
+ }
+
+
+
+
+
+
+
+
+ US Idaho - Department of Labor (IDOL) - Unemployment Tax
+
+
+
+ US Idaho - State Tax Commission (ISTC) - Income Tax
+
+
+
+
+
+
+
+
+
+ ER: US ID Idaho State Unemployment
+ ER_US_ID_SUTA
+ python
+ result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_id_suta_wage_base', rate='us_id_suta_rate', state_code='ID')
+ code
+ result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_id_suta_wage_base', rate='us_id_suta_rate', state_code='ID')
+
+
+
+
+
+
+
+
+ EE: US ID Idaho State Income Tax Withholding
+ EE_US_ID_SIT
+ python
+ result, _ = id_idaho_state_income_withholding(payslip, categories, worked_days, inputs)
+ code
+ result, result_rate = id_idaho_state_income_withholding(payslip, categories, worked_days, inputs)
+
+
+
+
+
\ No newline at end of file
diff --git a/l10n_us_hr_payroll/data/state/il_illinois.xml b/l10n_us_hr_payroll/data/state/il_illinois.xml
new file mode 100644
index 00000000..840b2a9b
--- /dev/null
+++ b/l10n_us_hr_payroll/data/state/il_illinois.xml
@@ -0,0 +1,117 @@
+
+
+
+
+ US IL Illinois SUTA Wage Base
+ us_il_suta_wage_base
+
+
+
+
+ 12960.0
+
+
+
+
+ 12740.0
+
+
+
+
+
+
+
+ US IL Illinois SUTA Rate
+ us_il_suta_rate
+
+
+
+
+ 3.175
+
+
+
+
+ 3.125
+
+
+
+
+
+
+ US IL Illinois Basic Allowances Rate
+ us_il_sit_basic_allowances_rate
+
+
+
+
+ 2275.0
+
+
+
+
+ 2325.0
+
+
+
+
+
+
+ US IL Illinois Additional Allowances Rate
+ us_il_sit_additional_allowances_rate
+
+
+
+
+ 1000.0
+
+
+
+
+ 1000.0
+
+
+
+
+
+
+
+ US Illinois - Department of Economic Security (IDES) - Unemployment Tax
+
+
+
+ US Illinois - Department of Revenue (IDOR) - Income Tax
+
+
+
+
+
+
+
+
+
+ ER: US IL Illinois State Unemployment
+ ER_US_IL_SUTA
+ python
+ result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_il_suta_wage_base', rate='us_il_suta_rate', state_code='IL')
+ code
+ result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_il_suta_wage_base', rate='us_il_suta_rate', state_code='IL')
+
+
+
+
+
+
+
+
+ EE: US IL Illinois State Income Tax Withholding
+ EE_US_IL_SIT
+ python
+ result, _ = il_illinois_state_income_withholding(payslip, categories, worked_days, inputs)
+ code
+ result, result_rate = il_illinois_state_income_withholding(payslip, categories, worked_days, inputs)
+
+
+
+
+
\ No newline at end of file
diff --git a/l10n_us_hr_payroll/data/state/in_indiana.xml b/l10n_us_hr_payroll/data/state/in_indiana.xml
new file mode 100644
index 00000000..9bda9b1e
--- /dev/null
+++ b/l10n_us_hr_payroll/data/state/in_indiana.xml
@@ -0,0 +1,122 @@
+
+
+
+
+ US IN Indiana SUTA Wage Base
+ us_in_suta_wage_base
+
+
+
+
+ 9500.00
+
+
+
+
+
+
+
+ US IN Indiana SUTA Rate
+ us_in_suta_rate
+
+
+
+
+ 2.5
+
+
+
+
+
+
+ US IN Indiana SUTA Income Rate
+ us_in_suta_income_rate
+
+
+
+
+ 3.23
+
+
+
+
+
+
+ US IN Indiana SIT Personal Exemption Rate
+ us_in_sit_personal_exemption_rate
+
+
+
+
+ {
+ 'daily': ( 2.74, 5.48, 8.22, 10.96, 13.70, 16.44),
+ 'weekly': ( 19.23, 38.46, 57.69, 76.92, 96.15, 115.38),
+ 'bi-weekly': ( 38.46, 76.92, 115.38, 153.85, 192.31, 230.77),
+ 'semi-monthly': ( 41.67, 83.33, 125.00, 166.67, 208.33, 250.00),
+ 'monthly': ( 83.33, 166.67, 250.00, 333.33, 416.67, 500.00),
+ }
+
+
+
+
+
+
+ US IN Indiana SIT Dependent Exemption Rate
+ us_in_sit_dependent_exemption_rate
+
+
+
+
+ {
+ 'daily': ( 4.11, 8.22, 12.33, 16.44, 20.55),
+ 'weekly': ( 28.85, 57.69, 86.54, 115.38, 144.23),
+ 'bi-weekly': ( 57.69, 115.38, 173.08, 230.77, 288.46),
+ 'semi-monthly': ( 62.50, 125.00, 187.50, 250.00, 312.50),
+ 'monthly': (125.00, 250.00, 375.00, 500.00, 625.00),
+ }
+
+
+
+
+
+
+
+ US Indiana - Department of Workforce Development - Unemployment Tax
+
+
+
+ US Indiana - Department of Revenue - Income Tax
+
+
+
+
+
+
+
+
+
+ ER: US IN Indiana State Unemployment
+ ER_US_IN_SUTA
+ python
+ result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_in_suta_wage_base', rate='us_in_suta_rate', state_code='IN')
+ code
+ result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_in_suta_wage_base', rate='us_in_suta_rate', state_code='IN')
+
+
+
+
+
+
+
+
+ EE: US IN Indiana State Income Tax Withholding
+ EE_US_IN_SIT
+ python
+ result, _ = in_indiana_state_income_withholding(payslip, categories, worked_days, inputs)
+ code
+ result, result_rate = in_indiana_state_income_withholding(payslip, categories, worked_days, inputs)
+
+
+
+
+
diff --git a/l10n_us_hr_payroll/data/state/ks_kansas.xml b/l10n_us_hr_payroll/data/state/ks_kansas.xml
new file mode 100644
index 00000000..5b59d2b7
--- /dev/null
+++ b/l10n_us_hr_payroll/data/state/ks_kansas.xml
@@ -0,0 +1,194 @@
+
+
+
+
+ US KS Kansas SUTA Wage Base
+ us_ks_suta_wage_base
+
+
+
+
+ 14000.0
+
+
+
+
+
+
+
+ US KS Kansas SUTA Rate
+ us_ks_suta_rate
+
+
+
+
+ 2.7
+
+
+
+
+
+
+ US KS Kansas Allowances Rate
+ us_ks_sit_allowances_rate
+
+
+
+
+ {
+ 'weekly' : 43.27,
+ 'bi-weekly' : 86.54,
+ 'semi-monthly': 93.75,
+ 'monthly' : 187.50,
+ 'quarterly' : 562.50,
+ 'semi-annual': 1125.00,
+ 'annually': 2250.00,
+ }
+
+
+
+
+
+
+ US KS Kansas SIT Tax Rate
+ us_ks_sit_tax_rate
+
+
+
+
+ {
+ 'single': {
+ 'weekly': (
+ ( 58, 0.00, 0.00),
+ ( 346, 3.10, 0.00),
+ ( 635, 5.25, 8.94),
+ ('inf', 5.70, 24.09),
+ ),
+ 'bi-weekly': (
+ ( 115, 0.00, 0.00),
+ ( 692, 3.10, 0.00),
+ ( 1269, 5.25, 17.88),
+ ('inf', 5.70, 48.17),
+ ),
+ 'semi-monthly': (
+ ( 125, 0.00, 0.00),
+ ( 750, 3.10, 0.00),
+ ( 1375, 5.25, 19.38),
+ ('inf', 5.70, 52.19),
+ ),
+ 'monthly': (
+ ( 250, 0.00, 0.00),
+ ( 1500, 3.10, 0.00),
+ ( 2750, 5.25, 38.75),
+ ('inf', 5.70, 104.38),
+ ),
+ 'quarterly': (
+ ( 750, 0.00, 0.00),
+ ( 4500, 3.10, 0.00),
+ (8250, 5.25, 116.25),
+ ('inf', 5.70, 313.13),
+ ),
+ 'semi-annual': (
+ ( 1500, 0.00, 0.00),
+ ( 9000, 3.10, 0.00),
+ (16500, 5.25, 232.50),
+ ('inf', 5.70, 626.25),
+ ),
+ 'annually': (
+ ( 3000, 0.00, 0.00),
+ (18000, 3.10, 0.00),
+ (33000, 5.25, 465.00),
+ ('inf', 5.70, 1252.50),
+ ),
+ },
+ 'married': {
+ 'weekly': (
+ ( 144, 0.00, 0.00),
+ ( 721, 3.10, 0.00),
+ (1298, 5.25, 17.88),
+ ('inf', 5.70, 48.17),
+ ),
+ 'bi-weekly': (
+ ( 288, 0.00, 0.00),
+ ( 1442, 3.10, 0.00),
+ ( 2596, 5.25, 35.77),
+ ('inf', 5.70, 96.35),
+ ),
+ 'semi-monthly': (
+ ( 313, 0.00, 0.00),
+ ( 1563, 3.10, 0.00),
+ ( 2813, 5.25, 38.75),
+ ('inf', 5.70, 104.38),
+ ),
+ 'monthly': (
+ ( 625, 0.00, 0.00),
+ ( 3125, 3.10, 0.00),
+ ( 5625, 5.25, 77.50),
+ ('inf', 5.70, 208.75),
+ ),
+ 'quarterly': (
+ ( 1875, 0.00, 0.00),
+ ( 9375, 3.10, 0.00),
+ (16875, 5.25, 232.50),
+ ('inf', 5.70, 626.25),
+ ),
+ 'semi-annual': (
+ ( 3750, 0.00, 0.00),
+ (18750, 3.10, 0.00),
+ (33750, 5.25, 465.00),
+ ('inf', 5.70, 1252.50),
+ ),
+ 'annually': (
+ ( 7500, 0.00, 0.00),
+ (37500, 3.10, 0.00),
+ (67500, 5.25, 930.00),
+ ('inf', 5.70, 2505.00),
+ ),
+ },
+ }
+
+
+
+
+
+
+
+ US Kansas - Department of Labor - Unemployment Tax
+
+
+
+ US Kansas - Department of Revenue - Income Tax
+
+
+
+
+
+
+
+
+
+ ER: US KS Kansas State Unemployment
+ ER_US_KS_SUTA
+ python
+ result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_ks_suta_wage_base', rate='us_ks_suta_rate', state_code='KS')
+ code
+ result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_ks_suta_wage_base', rate='us_ks_suta_rate', state_code='KS')
+
+
+
+
+
+
+
+
+ EE: US KS Kansas State Income Tax Withholding
+ EE_US_KS_SIT
+ python
+ result, _ = ks_kansas_state_income_withholding(payslip, categories, worked_days, inputs)
+ code
+ result, result_rate = ks_kansas_state_income_withholding(payslip, categories, worked_days, inputs)
+
+
+
+
+
\ No newline at end of file
diff --git a/l10n_us_hr_payroll/data/state/ky_kentucky.xml b/l10n_us_hr_payroll/data/state/ky_kentucky.xml
new file mode 100644
index 00000000..bcdd2274
--- /dev/null
+++ b/l10n_us_hr_payroll/data/state/ky_kentucky.xml
@@ -0,0 +1,99 @@
+
+
+
+
+ US KY Kentucky SUTA Wage Base
+ us_ky_suta_wage_base
+
+
+
+
+ 10800.0
+
+
+
+
+
+
+
+ US KY Kentucky SUTA Rate
+ us_ky_suta_rate
+
+
+
+
+ 2.7
+
+
+
+
+
+
+ US KY Kentucky Standard Deduction Rate
+ us_ky_sit_standard_deduction_rate
+
+
+
+
+
+ 2650
+
+
+
+
+
+
+ US KY Kentucky SIT Tax Rate
+ us_ky_sit_tax_rate
+
+
+
+
+
+ 5.0
+
+
+
+
+
+
+
+ US Kentucky - Office of Unemployment Insurance - Unemployment Tax
+
+
+
+ US Kentucky - Department of Revenue - Income Tax
+
+
+
+
+
+
+
+
+
+ ER: US KY Kentucky State Unemployment
+ ER_US_KY_SUTA
+ python
+ result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_ky_suta_wage_base', rate='us_ky_suta_rate', state_code='KY')
+ code
+ result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_ky_suta_wage_base', rate='us_ky_suta_rate', state_code='KY')
+
+
+
+
+
+
+
+
+ EE: US KY Kentucky State Income Tax Withholding
+ EE_US_KY_SIT
+ python
+ result, _ = ky_kentucky_state_income_withholding(payslip, categories, worked_days, inputs)
+ code
+ result, result_rate = ky_kentucky_state_income_withholding(payslip, categories, worked_days, inputs)
+
+
+
+
+
\ No newline at end of file
diff --git a/l10n_us_hr_payroll/data/state/la_louisiana.xml b/l10n_us_hr_payroll/data/state/la_louisiana.xml
new file mode 100644
index 00000000..7e4f5fe2
--- /dev/null
+++ b/l10n_us_hr_payroll/data/state/la_louisiana.xml
@@ -0,0 +1,157 @@
+
+
+
+
+ US LA Louisiana SUTA Wage Base
+ us_la_suta_wage_base
+
+
+
+
+ 7700.0
+
+
+
+
+ 7700.0
+
+
+
+
+
+
+
+ US LA Louisiana SUTA Rate
+ us_la_suta_rate
+
+
+
+
+ 1.14
+
+
+
+
+ 1.14
+
+
+
+
+
+
+ US LA Louisiana SIT Tax Rate
+ us_la_sit_tax_rate
+
+
+
+
+ {
+ 'single': (
+ (12500.00, 2.10),
+ (50000.00, 1.60),
+ ( 'inf', 1.35),
+ ),
+ 'married': (
+ ( 25000.00, 2.10),
+ (100000.00, 1.65),
+ ( 'inf', 1.35),
+ ),
+ }
+
+
+
+
+ {
+ 'single': (
+ (12500.00, 2.10),
+ (50000.00, 1.60),
+ ( 'inf', 1.35)
+ ),
+ 'married': (
+ ( 25000.00, 2.10),
+ (100000.00, 1.65),
+ ( 'inf', 1.35)
+ ),
+ }
+
+
+
+
+
+
+ US LA Louisiana Personal Exemption Rate
+ us_la_sit_personal_exemption_rate
+
+
+
+
+ 4500
+
+
+
+
+ 4500
+
+
+
+
+
+
+ US LA Louisiana Dependent Rate
+ us_la_sit_dependent_rate
+
+
+
+
+ 1000.0
+
+
+
+
+ 1000.0
+
+
+
+
+
+
+
+ US Louisiana - Workforce Commission (LWC) - Unemployment Tax
+
+
+
+ US Louisiana - Department of Revenue (LDOR) - Income Tax
+
+
+
+
+
+
+
+
+
+ ER: US LA Louisiana State Unemployment
+ ER_US_LA_SUTA
+ python
+ result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_la_suta_wage_base', rate='us_la_suta_rate', state_code='LA')
+ code
+ result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_la_suta_wage_base', rate='us_la_suta_rate', state_code='LA')
+
+
+
+
+
+
+
+
+ EE: US LA Louisiana State Income Tax Withholding
+ EE_US_LA_SIT
+ python
+ result, _ = la_louisiana_state_income_withholding(payslip, categories, worked_days, inputs)
+ code
+ result, result_rate = la_louisiana_state_income_withholding(payslip, categories, worked_days, inputs)
+
+
+
+
+
diff --git a/l10n_us_hr_payroll/data/state/me_maine.xml b/l10n_us_hr_payroll/data/state/me_maine.xml
new file mode 100644
index 00000000..2c1aaf71
--- /dev/null
+++ b/l10n_us_hr_payroll/data/state/me_maine.xml
@@ -0,0 +1,131 @@
+
+
+
+
+ US ME Maine SUTA Wage Base
+ us_me_suta_wage_base
+
+
+
+
+ 12000.0
+
+
+
+
+
+
+
+ US ME Maine SUTA Rate
+ us_me_suta_rate
+
+
+
+
+ 1.92
+
+
+
+
+
+
+ US ME Maine SIT Tax Rate
+ us_me_sit_tax_rate
+
+
+
+
+ {
+ 'single': (
+ ( 22200, 0, 5.80),
+ ( 52600, 1288, 6.75),
+ ( 'inf', 3340, 7.15),
+ ),
+ 'married': (
+ ( 44450, 0, 5.80),
+ ( 105200, 2578, 6.75),
+ ( 'inf', 6679, 7.15),
+ ),
+ }
+
+
+
+
+
+
+ US ME Maine Standard Deduction Rate
+ us_me_sit_standard_deduction_rate
+
+
+
+
+ {
+ 'single': {
+ ( 82900, 9550),
+ (157900, 75000),
+ },
+ 'married': {
+ (165800, 21950),
+ (315800, 150000),
+ },
+ }
+
+
+
+
+
+
+ US ME Maine Personal Exemption Rate
+ us_me_sit_personal_exemption_rate
+
+
+
+
+ 4300
+
+
+
+
+
+
+
+
+ US Maine - Department Of Labor | ReEmploy - Unemployment Tax
+
+
+
+ US Maine - Department Of Revenue Services - Income Tax
+
+
+
+
+
+
+
+
+
+ ER: US ME Maine State Unemployment
+ ER_US_ME_SUTA
+ python
+ result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_me_suta_wage_base', rate='us_me_suta_rate', state_code='ME')
+ code
+ result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_me_suta_wage_base', rate='us_me_suta_rate', state_code='ME')
+
+
+
+
+
+
+
+
+ EE: US ME Maine State Income Tax Withholding
+ EE_US_ME_SIT
+ python
+ result, _ = me_maine_state_income_withholding(payslip, categories, worked_days, inputs)
+ code
+ result, result_rate = me_maine_state_income_withholding(payslip, categories, worked_days, inputs)
+
+
+
+
+
\ No newline at end of file
diff --git a/l10n_us_hr_payroll/data/state/mi_michigan.xml b/l10n_us_hr_payroll/data/state/mi_michigan.xml
new file mode 100644
index 00000000..1ce32483
--- /dev/null
+++ b/l10n_us_hr_payroll/data/state/mi_michigan.xml
@@ -0,0 +1,99 @@
+
+
+
+
+ US MI Michigan SUTA Wage Base
+ us_mi_suta_wage_base
+
+
+
+
+ 9500.0
+
+
+
+
+ 9000.0
+
+
+
+
+
+
+
+ US MI Michigan SUTA Rate
+ us_mi_suta_rate
+
+
+
+
+ 2.7
+
+
+
+
+ 2.7
+
+
+
+
+
+
+ US MI Michigan Exemption Rate
+ us_mi_sit_exemption_rate
+
+
+
+
+ 4400.0
+
+
+
+
+ 4750.0
+
+
+
+
+
+
+
+ US Michigan - Unemployment Insurance Agency - Unemployment Tax
+
+
+
+ US Michigan - Department of Treasury - Income Tax
+
+
+
+
+
+
+
+
+
+ ER: US MI Michigan State Unemployment
+ ER_US_MI_SUTA
+ python
+ result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_mi_suta_wage_base', rate='us_mi_suta_rate', state_code='MI')
+ code
+ result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_mi_suta_wage_base', rate='us_mi_suta_rate', state_code='MI')
+
+
+
+
+
+
+
+
+ EE: US MI Michigan State Income Tax Withholding
+ EE_US_MI_SIT
+ python
+ result, _ = mi_michigan_state_income_withholding(payslip, categories, worked_days, inputs)
+ code
+ result, result_rate = mi_michigan_state_income_withholding(payslip, categories, worked_days, inputs)
+
+
+
+
+
\ No newline at end of file
diff --git a/l10n_us_hr_payroll/data/state/mn_minnesota.xml b/l10n_us_hr_payroll/data/state/mn_minnesota.xml
new file mode 100644
index 00000000..d1044752
--- /dev/null
+++ b/l10n_us_hr_payroll/data/state/mn_minnesota.xml
@@ -0,0 +1,147 @@
+
+
+
+
+ US MN Minnesota SUTA Wage Base
+ us_mn_suta_wage_base
+
+
+
+
+ 34000.0
+
+
+
+
+ 35000.0
+
+
+
+
+
+
+
+ US MN Minnesota SUTA Rate
+ us_mn_suta_rate
+
+
+
+
+ 1.11
+
+
+
+
+ 1.11
+
+
+
+
+
+
+ US MN Minnesota SIT Tax Rate
+ us_mn_sit_tax_rate
+
+
+
+
+ {
+ 'single': (
+ ( 28920, 2400, 5.35, 0.00),
+ ( 89510, 28920, 7.05, 1418.82),
+ (166290, 89510, 7.85, 5690.42),
+ ( 'inf', 166290, 9.85, 11717.65),
+ ),
+ 'married': (
+ ( 47820, 9050, 5.35, 0.00),
+ ( 163070, 47820, 7.05, 2074.20),
+ ( 282200, 163070, 7.85, 10199.33),
+ ( 'inf', 282200, 9.85, 19551.04),
+ ),
+ }
+
+
+
+
+
+
+ {
+ 'single': (
+ ( 30760, 3800, 5.35, 0.00),
+ ( 92350, 30760, 6.80, 1442.36),
+ (168200, 92350, 7.85, 5630.48),
+ ( 'inf', 168200, 9.85, 11584.71),
+ ),
+ 'married': (
+ ( 51310, 11900, 5.35, 0.00),
+ ( 168470, 51310, 6.80, 2108.44),
+ ( 285370, 168470, 7.85, 10075.32),
+ ( 'inf', 285370, 9.85, 19251.97),
+ ),
+ }
+
+
+
+
+
+
+ US MN Minnesota Allowances Rate
+ us_mn_sit_allowances_rate
+
+
+
+
+ 4250.0
+
+
+
+
+
+
+ 4300.0
+
+
+
+
+
+
+
+ US Minnesota - Unemployment Insurance Agency - Unemployment Tax
+
+
+
+ US Minnesota - Department of Treasury - Income Tax
+
+
+
+
+
+
+
+
+
+ ER: US MN Minnesota State Unemployment
+ ER_US_MN_SUTA
+ python
+ result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_mn_suta_wage_base', rate='us_mn_suta_rate', state_code='MN')
+ code
+ result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_mn_suta_wage_base', rate='us_mn_suta_rate', state_code='MN')
+
+
+
+
+
+
+
+
+ EE: US MN Minnesota State Income Tax Withholding
+ EE_US_MN_SIT
+ python
+ result, _ = mn_minnesota_state_income_withholding(payslip, categories, worked_days, inputs)
+ code
+ result, result_rate = mn_minnesota_state_income_withholding(payslip, categories, worked_days, inputs)
+
+
+
+
+
\ No newline at end of file
diff --git a/l10n_us_hr_payroll/data/state/mo_missouri.xml b/l10n_us_hr_payroll/data/state/mo_missouri.xml
new file mode 100644
index 00000000..73ea5109
--- /dev/null
+++ b/l10n_us_hr_payroll/data/state/mo_missouri.xml
@@ -0,0 +1,151 @@
+
+
+
+
+ US MO Missouri SUTA Wage Base
+ us_mo_suta_wage_base
+
+
+
+
+ 12000.0
+
+
+
+
+ 11500.0
+
+
+
+
+
+
+
+ US MO Missouri SUTA Rate
+ us_mo_suta_rate
+
+
+
+
+ 2.376
+
+
+
+
+ 2.376
+
+
+
+
+
+
+ US MO Missouri SIT Rate Table
+ us_mo_sit_rate
+
+
+
+
+
+ [
+ (1053.0, 1.5),
+ (1053.0, 2.0),
+ (1053.0, 2.5),
+ (1053.0, 3.0),
+ (1053.0, 3.5),
+ (1053.0, 4.0),
+ (1053.0, 4.5),
+ (1053.0, 5.0),
+ ( 'inf', 5.4),
+ ]
+
+
+
+
+
+
+ [
+ (1073.0, 1.5),
+ (1073.0, 2.0),
+ (1073.0, 2.5),
+ (1073.0, 3.0),
+ (1073.0, 3.5),
+ (1073.0, 4.0),
+ (1073.0, 4.5),
+ (1073.0, 5.0),
+ ( 'inf', 5.4),
+ ]
+
+
+
+
+
+
+ US MO Missouri SIT Deduction
+ us_mo_sit_deduction
+
+
+
+
+
+ {
+ 'single': 12400.0,
+ 'married': 24800.0,
+ 'head_of_household': 18650.0,
+ }
+
+
+
+
+
+
+ {
+ 'single': 12400.0,
+ 'married': 24800.0,
+ 'head_of_household': 18650.0,
+ }
+
+
+
+
+
+
+
+ US Missouri - Department of Taxation - Unemployment Tax
+
+
+
+ US Missouri - Department of Taxation - Income Tax
+
+
+
+
+
+
+
+
+
+ ER: US MO Missouri State Unemployment
+ ER_US_MO_SUTA
+ python
+ result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_mo_suta_wage_base', rate='us_mo_suta_rate', state_code='MO')
+ code
+ result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_mo_suta_wage_base', rate='us_mo_suta_rate', state_code='MO')
+
+
+
+
+
+
+
+
+ EE: US MO Missouri State Income Tax Withholding
+ EE_US_MO_SIT
+ python
+ result, _ = mo_missouri_state_income_withholding(payslip, categories, worked_days, inputs)
+ code
+ result, result_rate = mo_missouri_state_income_withholding(payslip, categories, worked_days, inputs)
+
+
+
+
+
\ No newline at end of file
diff --git a/l10n_us_hr_payroll/data/state/ms_mississippi.xml b/l10n_us_hr_payroll/data/state/ms_mississippi.xml
new file mode 100644
index 00000000..a3868e8d
--- /dev/null
+++ b/l10n_us_hr_payroll/data/state/ms_mississippi.xml
@@ -0,0 +1,139 @@
+
+
+
+
+ US MS Mississippi SUTA Wage Base
+ us_ms_suta_wage_base
+
+
+
+
+ 14000.0
+
+
+
+
+ 14000.0
+
+
+
+
+
+
+
+ US MS Mississippi SUTA Rate
+ us_ms_suta_rate
+
+
+
+
+ 1.2
+
+
+
+
+ 1.2
+
+
+
+
+
+
+ US MS Mississippi SIT Rate Table
+ us_ms_sit_rate
+
+
+
+
+ [
+ ( 10000.00, 290.0, 0.05),
+ ( 5000.00, 90.0, 0.04),
+ ( 2000.00, 0.0, 0.03),
+ ]
+
+
+
+
+
+
+ [
+ ( 10000.00, 260.0, 0.05),
+ ( 5000.00, 60.0, 0.04),
+ ( 3000.00, 0.0, 0.03),
+ ]
+
+
+
+
+
+
+ US MS Mississippi SIT Deduction
+ us_ms_sit_deduction
+
+
+
+
+ {
+ 'single': 2300.0,
+ 'head_of_household': 3400.0,
+ 'married_dual': 2300.0,
+ 'married': 4600.0,
+ }
+
+
+
+
+
+
+ {
+ 'single': 2300.0,
+ 'head_of_household': 3400.0,
+ 'married_dual': 2300.0,
+ 'married': 4600.0,
+ }
+
+
+
+
+
+
+
+ US Mississippi - Department of Employment Security (Unemployment)
+
+
+
+ US Mississippi - Mississippi Department of Revenue (Income Tax)
+
+
+
+
+
+
+
+
+
+ ER: US MS Mississippi State Unemployment
+ ER_US_MS_SUTA
+ python
+ result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_ms_suta_wage_base', rate='us_ms_suta_rate', state_code='MS')
+ code
+ result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_ms_suta_wage_base', rate='us_ms_suta_rate', state_code='MS')
+
+
+
+
+
+
+
+
+ EE: US MS Mississippi State Income Tax Withholding
+ EE_US_MS_SIT
+ python
+ result, _ = ms_mississippi_state_income_withholding(payslip, categories, worked_days, inputs)
+ code
+ result, result_rate = ms_mississippi_state_income_withholding(payslip, categories, worked_days, inputs)
+
+
+
+
+
\ No newline at end of file
diff --git a/l10n_us_hr_payroll/data/state/mt_montana.xml b/l10n_us_hr_payroll/data/state/mt_montana.xml
new file mode 100644
index 00000000..b777fb4e
--- /dev/null
+++ b/l10n_us_hr_payroll/data/state/mt_montana.xml
@@ -0,0 +1,227 @@
+
+
+
+
+ US MT Montana SUTA Wage Base
+ us_mt_suta_wage_base
+
+
+
+
+ 33000.00
+
+
+
+
+ 34100.00
+
+
+
+
+
+
+
+ US MT Montana SUTA Rate (UI)
+ us_mt_suta_rate
+
+
+
+
+ 1.18
+
+
+
+
+ 1.18
+
+
+
+
+
+
+ US MT Montana SUTA Administrative Fund Tax Rate
+ us_mt_suta_aft_rate
+
+
+
+
+ 0.13
+
+
+
+
+ 0.13
+
+
+
+
+
+
+ US MT Montana SIT Rate Table
+ us_mt_sit_rate
+
+
+
+
+ {
+ 'weekly': (
+ ( 135.00, 0.0, 1.80),
+ ( 288.00, 2.0, 4.40),
+ ( 2308.00, 9.0, 6.00),
+ ( 'inf', 130.0, 6.60),
+ ),
+ 'bi-weekly': (
+ ( 269.00, 0.0, 1.80),
+ ( 577.00, 5.0, 4.40),
+ ( 4615.00, 18.0, 6.00),
+ ( 'inf', 261.0, 6.60),
+ ),
+ 'semi-monthly': (
+ ( 292.00, 0.0, 1.80),
+ ( 625.00, 5.0, 4.40),
+ ( 5000.00, 20.0, 6.00),
+ ( 'inf', 282.0, 6.60),
+ ),
+ 'monthly': (
+ ( 583.00, 0.0, 1.80),
+ ( 1250.00, 11.0, 4.40),
+ ( 10000.00, 40.0, 6.00),
+ ( 'inf', 565.0, 6.60),
+ ),
+ 'annually': (
+ ( 7000.00, 0.0, 1.80),
+ ( 15000.00, 126.0, 4.40),
+ ( 120000.00, 478.0, 6.00),
+ ( 'inf', 6778.0, 6.60),
+ ),
+ }
+
+
+
+
+
+
+ {
+ 'weekly': (
+ ( 135.00, 0.0, 1.80),
+ ( 288.00, 2.0, 4.40),
+ ( 2308.00, 9.0, 6.00),
+ ( 'inf', 130.0, 6.60),
+ ),
+ 'bi-weekly': (
+ ( 269.00, 0.0, 1.80),
+ ( 577.00, 5.0, 4.40),
+ ( 4615.00, 18.0, 6.00),
+ ( 'inf', 261.0, 6.60),
+ ),
+ 'semi-monthly': (
+ ( 292.00, 0.0, 1.80),
+ ( 625.00, 5.0, 4.40),
+ ( 5000.00, 20.0, 6.00),
+ ( 'inf', 282.0, 6.60),
+ ),
+ 'monthly': (
+ ( 583.00, 0.0, 1.80),
+ ( 1250.00, 11.0, 4.40),
+ ( 10000.00, 40.0, 6.00),
+ ( 'inf', 565.0, 6.60),
+ ),
+ 'annually': (
+ ( 7000.00, 0.0, 1.80),
+ ( 15000.00, 126.0, 4.40),
+ ( 120000.00, 478.0, 6.00),
+ ( 'inf', 6778.0, 6.60),
+ ),
+ }
+
+
+
+
+
+
+ US MT Montana SIT Exemption Rate Table
+ us_mt_sit_exemption_rate
+
+
+
+
+ {
+ 'weekly': 37.0,
+ 'bi-weekly': 73.0,
+ 'semi-monthly': 79.0,
+ 'monthly': 158.0,
+ 'annually': 1900.0,
+ }
+
+
+
+
+
+
+ {
+ 'weekly': 37.0,
+ 'bi-weekly': 73.0,
+ 'semi-monthly': 79.0,
+ 'monthly': 158.0,
+ 'annually': 1900.0,
+ }
+
+
+
+
+
+
+
+ US Montana - Department of Labor & Industries
+
+
+
+ US Montana - Department of Revenue - Income Tax
+
+
+
+
+
+
+
+
+
+ ER: US MT Montana State Unemployment (UI-5)
+ ER_US_MT_SUTA
+ python
+ result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_mt_suta_wage_base', rate='us_mt_suta_rate', state_code='MT')
+ code
+ result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_mt_suta_wage_base', rate='us_mt_suta_rate', state_code='MT')
+
+
+
+
+
+
+
+
+ ER: US MT Montana State Unemployment Administrative Fund Tax (AFT) (UI-5)
+ ER_US_MT_SUTA_AFT
+ python
+ result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_mt_suta_wage_base', rate='us_mt_suta_aft_rate', state_code='MT')
+ code
+ result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_mt_suta_wage_base', rate='us_mt_suta_aft_rate', state_code='MT')
+
+
+
+
+
+
+
+
+ EE: US MT Montana State Income Tax Withholding (MW-3)
+ EE_US_MT_SIT
+ python
+ result, _ = mt_montana_state_income_withholding(payslip, categories, worked_days, inputs)
+ code
+ result, result_rate = mt_montana_state_income_withholding(payslip, categories, worked_days, inputs)
+
+
+
+
+
\ No newline at end of file
diff --git a/l10n_us_hr_payroll/data/state/nc_northcarolina.xml b/l10n_us_hr_payroll/data/state/nc_northcarolina.xml
new file mode 100644
index 00000000..d09f8e3f
--- /dev/null
+++ b/l10n_us_hr_payroll/data/state/nc_northcarolina.xml
@@ -0,0 +1,113 @@
+
+
+
+
+ US NC North Carolina SUTA Wage Base
+ us_nc_suta_wage_base
+
+
+
+
+ 24300.0
+
+
+
+
+ 25200.0
+
+
+
+
+
+
+
+ US NC North Carolina SUTA Rate
+ us_nc_suta_rate
+
+
+
+
+ 1.0
+
+
+
+
+ 1.0
+
+
+
+
+
+
+ US NC North Carolina Allowance Rate
+ us_nc_sit_allowance_rate
+
+
+
+
+
+
+ {
+ 'weekly': {'allowance': 48.08, 'standard_deduction': 192.31, 'standard_deduction_hh': 288.46},
+ 'bi-weekly': {'allowance': 96.15, 'standard_deduction': 384.62, 'standard_deduction_hh': 576.92},
+ 'semi-monthly': {'allowance': 104.17, 'standard_deduction': 416.67, 'standard_deduction_hh': 625.00},
+ 'monthly': {'allowance': 208.33, 'standard_deduction': 833.33, 'standard_deduction_hh': 1250.00},
+ }
+
+
+
+
+
+
+ {
+ 'weekly': {'allowance': 48.08, 'standard_deduction': 206.73, 'standard_deduction_hh': 310.10},
+ 'bi-weekly': {'allowance': 96.15, 'standard_deduction': 413.46, 'standard_deduction_hh': 620.19},
+ 'semi-monthly': {'allowance': 104.17, 'standard_deduction': 447.92, 'standard_deduction_hh': 671.88},
+ 'monthly': {'allowance': 208.33, 'standard_deduction': 895.83, 'standard_deduction_hh': 1343.75},
+ }
+
+
+
+
+
+
+
+ US North Carolina - Department of Taxation - Unemployment Tax
+
+
+
+ US North Carolina - Department of Taxation - Income Tax
+
+
+
+
+
+
+
+
+
+ ER: US NC North Carolina State Unemployment
+ ER_US_NC_SUTA
+ python
+ result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_nc_suta_wage_base', rate='us_nc_suta_rate', state_code='NC')
+ code
+ result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_nc_suta_wage_base', rate='us_nc_suta_rate', state_code='NC')
+
+
+
+
+
+
+
+
+ EE: US NC North Carolina State Income Tax Withholding
+ EE_US_NC_SIT
+ python
+ result, _ = nc_northcarolina_state_income_withholding(payslip, categories, worked_days, inputs)
+ code
+ result, result_rate = nc_northcarolina_state_income_withholding(payslip, categories, worked_days, inputs)
+
+
+
+
+
\ No newline at end of file
diff --git a/l10n_us_hr_payroll/data/state/nd_north_dakota.xml b/l10n_us_hr_payroll/data/state/nd_north_dakota.xml
new file mode 100644
index 00000000..d778c3aa
--- /dev/null
+++ b/l10n_us_hr_payroll/data/state/nd_north_dakota.xml
@@ -0,0 +1,280 @@
+
+
+
+
+ US ND North Dakota SUTA Wage Base
+ us_nd_suta_wage_base
+
+
+
+
+ 37900.0
+
+
+
+
+
+
+
+ US ND North Dakota SUTA Rate
+ us_nd_suta_rate
+
+
+
+
+ 1.02
+
+
+
+
+
+
+ US ND North Dakota SIT Tax Rate
+ us_nd_sit_tax_rate
+
+
+
+
+ {
+ 'single': {
+ 'weekly': (
+ ( 119, 0.00, 0.00),
+ ( 891, 0.00, 1.10),
+ ( 1988, 8.49, 2.04),
+ ( 4016, 30.87, 2.27),
+ ( 8592, 76.91, 2.64),
+ ('inf', 197.71, 2.90),
+ ),
+ 'bi-weekly': (
+ ( 238, 0.00, 0.00),
+ ( 1782, 0.00, 1.10),
+ ( 3975, 16.98, 2.04),
+ ( 8033, 61.72, 2.27),
+ ( 17185, 153.84, 2.64),
+ ( 'inf', 395.45, 2.90),
+ ),
+ 'semi-monthly': (
+ ( 258, 0.00, 0.00),
+ ( 1930, 0.00, 1.10),
+ ( 4306, 18.39, 2.04),
+ ( 8702, 66.86, 2.27),
+ ( 18617, 166.65, 2.64),
+ ( 'inf', 428.41, 2.90),
+ ),
+ 'monthly': (
+ ( 517, 0.00, 0.00),
+ ( 3860, 0.00, 1.10),
+ ( 8613, 36.77, 2.04),
+ ( 17404, 133.73, 2.27),
+ ( 37233, 333.29, 2.64),
+ ( 'inf', 856.78, 2.90),
+ ),
+ 'quarterly': (
+ ( 1550, 0.00, 0.00),
+ ( 11581, 0.00, 1.10),
+ ( 25838, 110.34, 2.04),
+ ( 52213, 401.18, 2.27),
+ ( 111700, 999.90, 2.64),
+ ( 'inf', 2570.35, 2.90),
+ ),
+ 'semi-annual': (
+ ( 3100, 0.00, 0.00),
+ ( 23163, 0.00, 1.10),
+ ( 51675, 220.69, 2.04),
+ ( 104425, 802.34, 2.27),
+ ( 223400, 1999.76, 2.64),
+ ( 'inf', 5140.70, 2.90),
+ ),
+ 'annual': (
+ ( 6200, 0.00, 0.00),
+ ( 46325, 0.00, 1.10),
+ ( 103350, 441.38, 2.04),
+ ( 208850, 1604.69, 2.27),
+ ( 446800, 3999.54, 2.64),
+ ( 'inf', 10281.42, 2.90),
+ ),
+ },
+ 'married': {
+ 'weekly': (
+ ( 238, 0.00, 0.00),
+ ( 883, 0.00, 1.10),
+ ( 1796, 7.10, 2.04),
+ ( 2611, 25.72, 2.27),
+ ( 4475, 44.22, 2.64),
+ ('inf', 93.43, 2.90),
+ ),
+ 'bi-weekly': (
+ ( 477, 0.00, 0.00),
+ ( 1766, 0.00, 1.10),
+ ( 3591, 14.18, 2.04),
+ ( 5221, 51.41, 2.27),
+ ( 8950, 88.41, 2.64),
+ ( 'inf', 186.86, 2.90),
+ ),
+ 'semi-monthly': (
+ ( 517, 0.00, 0.00),
+ ( 1914, 0.00, 1.10),
+ ( 3891, 15.37, 2.04),
+ ( 5656, 55.70, 2.27),
+ ( 9696, 95.76, 2.64),
+ ( 'inf', 202.42, 2.90),
+ ),
+ 'monthly': (
+ ( 1033, 0.00, 0.00),
+ ( 3827, 0.00, 1.10),
+ ( 7781, 30.73, 2.04),
+ ( 11313, 111.40, 2.27),
+ ( 19392, 191.57, 2.64),
+ ( 'inf', 404.86, 2.90),
+ ),
+ 'quarterly': (
+ ( 3100, 0.00, 0.00),
+ ( 11481, 0.00, 1.10),
+ ( 23344, 92.19, 2.04),
+ ( 33938, 334.20, 2.27),
+ ( 58175, 574.68, 2.64),
+ ( 'inf', 1214.54, 2.90),
+ ),
+ 'semi-annual': (
+ ( 6200, 0.00, 0.00),
+ ( 22963, 0.00, 1.10),
+ ( 46688, 184.39, 2.04),
+ ( 67875, 668.38, 2.27),
+ ( 116350, 1149.33, 2.64),
+ ( 'inf', 2429.07, 2.90),
+ ),
+ 'annual': (
+ ( 12400, 0.00, 0.00),
+ ( 45925, 0.00, 1.10),
+ ( 93375, 368.78, 2.04),
+ ( 135750, 1336.76, 2.27),
+ ( 232700, 2298.67, 2.64),
+ ( 'inf', 4858.15, 2.90),
+ ),
+ },
+ 'head_household':{
+ 'weekly': (
+ ( 119, 0.00, 0.00),
+ ( 891, 0.00, 1.10),
+ ( 1988, 8.49, 2.04),
+ ( 4016, 30.87, 2.27),
+ ( 8592, 76.91, 2.64),
+ ('inf', 197.71, 2.90),
+ ),
+ 'bi-weekly': (
+ ( 238, 0.00, 0.00),
+ ( 1782, 0.00, 1.10),
+ ( 3975, 16.98, 2.04),
+ ( 8033, 61.72, 2.27),
+ ( 17185, 153.84, 2.64),
+ ( 'inf', 395.45, 2.90),
+ ),
+ 'semi-monthly': (
+ ( 258, 0.00, 0.00),
+ ( 1930, 0.00, 1.10),
+ ( 4306, 18.39, 2.04),
+ ( 8702, 66.86, 2.27),
+ ( 18617, 166.65, 2.64),
+ ( 'inf', 428.41, 2.90),
+ ),
+ 'monthly': (
+ ( 517, 0.00, 0.00),
+ ( 3860, 0.00, 1.10),
+ ( 8613, 36.77, 2.04),
+ ( 17404, 133.73, 2.27),
+ ( 37233, 333.29, 2.64),
+ ( 'inf', 856.78, 2.90),
+ ),
+ 'quarterly': (
+ ( 1550, 0.00, 0.00),
+ ( 11581, 0.00, 1.10),
+ ( 25838, 110.34, 2.04),
+ ( 52213, 401.18, 2.27),
+ ( 111700, 999.90, 2.64),
+ ( 'inf', 2570.35, 2.90),
+ ),
+ 'semi-annual': (
+ ( 3100, 0.00, 0.00),
+ ( 23163, 0.00, 1.10),
+ ( 51675, 220.69, 2.04),
+ ( 104425, 802.34, 2.27),
+ ( 223400, 1999.76, 2.64),
+ ( 'inf', 5140.70, 2.90),
+ ),
+ 'annual': (
+ ( 6200, 0.00, 0.00),
+ ( 46325, 0.00, 1.10),
+ ( 103350, 441.38, 2.04),
+ ( 208850, 1604.69, 2.27),
+ ( 446800, 3999.54, 2.64),
+ ( 'inf', 10281.42, 2.90),
+ ),
+ },
+ }
+
+
+
+
+
+
+ US ND North Dakota Allowances Rate
+ us_nd_sit_allowances_rate
+
+
+
+
+ {
+ 'weekly' : 83.00,
+ 'bi-weekly' : 165.00,
+ 'semi-monthly': 179.00,
+ 'monthly' : 358.00,
+ 'quarterly' : 1075.00,
+ 'semi-annual': 2150.00,
+ 'annually': 4300.00,
+ }
+
+
+
+
+
+
+
+ US North Dakota - Office of State Tax Commissioner - Unemployment Tax
+
+
+
+ US North Dakota - Taxpayer Access Point - Income Tax
+
+
+
+
+
+
+
+
+
+ ER: US ND North Dakota State Unemployment
+ ER_US_ND_SUTA
+ python
+ result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_nd_suta_wage_base', rate='us_nd_suta_rate', state_code='ND')
+ code
+ result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_nd_suta_wage_base', rate='us_nd_suta_rate', state_code='ND')
+
+
+
+
+
+
+
+
+ EE: US ND North Dakota State Income Tax Withholding
+ EE_US_ND_SIT
+ python
+ result, _ = nd_north_dakota_state_income_withholding(payslip, categories, worked_days, inputs)
+ code
+ result, result_rate = nd_north_dakota_state_income_withholding(payslip, categories, worked_days, inputs)
+
+
+
+
+
\ No newline at end of file
diff --git a/l10n_us_hr_payroll/data/state/ne_nebraska.xml b/l10n_us_hr_payroll/data/state/ne_nebraska.xml
new file mode 100644
index 00000000..1951ff2f
--- /dev/null
+++ b/l10n_us_hr_payroll/data/state/ne_nebraska.xml
@@ -0,0 +1,237 @@
+
+
+
+
+ US NE Nebraska SUTA Wage Base
+ us_ne_suta_wage_base
+
+
+
+
+ 9000.0
+
+
+
+
+
+
+
+ US NE Nebraska SUTA Rate
+ us_ne_suta_rate
+
+
+
+
+ 1.25
+
+
+
+
+
+
+ US NE Nebraska SIT Tax Rate
+ us_ne_sit_tax_rate
+
+
+
+
+ {
+ 'single': {
+ 'weekly': (
+ ( 57, 0.00, 0.00),
+ ( 105, 0.00, 2.26),
+ ( 342, 1.08, 3.22),
+ ( 496, 8.71, 4.91),
+ ( 629, 16.27, 6.20),
+ ( 1182, 24.52, 6.59),
+ ('inf', 60.96, 6.95),
+ ),
+ 'bi-weekly': (
+ ( 114, 0.00, 0.00),
+ ( 211, 0.00, 2.26),
+ ( 684, 2.19, 3.22),
+ ( 992, 17.42, 4.91),
+ ( 1259, 32.54, 6.20),
+ ( 2364, 49.09, 6.59),
+ ('inf', 121.91, 6.95),
+ ),
+ 'semi-monthly': (
+ ( 124, 0.00, 0.00),
+ ( 228, 0.00, 2.26),
+ ( 741, 2.35, 3.22),
+ ( 1074, 18.87, 4.91),
+ ( 1364, 35.22, 6.20),
+ ( 2561, 53.20, 6.59),
+ ('inf', 132.08, 6.95),
+ ),
+ 'monthly': (
+ ( 248, 0.00, 0.00),
+ ( 457, 0.00, 2.26),
+ ( 1483, 4.72, 3.22),
+ ( 2148, 37.76, 4.91),
+ ( 2728, 70.41, 6.20),
+ ( 5123, 106.37, 6.59),
+ ('inf', 264.20, 6.95),
+ ),
+ 'quarterly': (
+ ( 744, 0.00, 0.00),
+ ( 1370, 0.00, 2.26),
+ ( 4448, 14.15, 3.22),
+ ( 6445, 113.26, 4.91),
+ ( 8183, 211.31, 6.20),
+ ( 15368, 319.07, 6.59),
+ ( 'inf', 792.56, 6.95),
+ ),
+ 'semi-annual': (
+ ( 1488, 0.00, 0.00),
+ ( 2740, 0.00, 2.26),
+ ( 8895, 28.30, 3.22),
+ ( 12890, 226.49, 4.91),
+ ( 16365, 422.64, 6.20),
+ ( 30735, 638.09, 6.59),
+ ( 'inf', 1585.07, 6.95),
+ ),
+ 'annually': (
+ ( 2975, 0.00, 0.00),
+ ( 5480, 0.00, 2.26),
+ ( 17790, 56.61, 3.22),
+ ( 25780, 452.99, 4.91),
+ ( 32730, 845.30, 6.20),
+ ( 61470, 1276.20, 6.59),
+ ( 'inf', 3170.17, 6.95),
+ ),
+ },
+ 'married': {
+ 'weekly': (
+ ( 137, 0.00, 0.00),
+ ( 204, 0.00, 2.26),
+ ( 508, 1.51, 3.22),
+ ( 790, 11.30, 4.91),
+ ( 981, 25.15, 6.20),
+ ( 1300, 36.99, 6.59),
+ ('inf', 58.01, 6.95),
+ ),
+ 'bi-weekly': (
+ ( 273, 0.00, 0.00),
+ ( 408, 0.00, 2.26),
+ ( 1016, 3.05, 3.22),
+ ( 1581, 22.63, 4.91),
+ ( 1961, 50.37, 6.20),
+ ( 2601, 73.93, 6.59),
+ ('inf', 116.11, 6.95),
+ ),
+ 'semi-monthly': (
+ ( 296, 0.00, 0.00),
+ ( 442, 0.00, 2.26),
+ ( 1101, 3.30, 3.22),
+ ( 1713, 24.52, 4.91),
+ ( 2125, 54.57, 6.20),
+ ( 2818, 80.11, 6.59),
+ ('inf', 125.78, 6.95),
+ ),
+ 'monthly': (
+ ( 592, 0.00, 0.00),
+ ( 884, 0.00, 2.26),
+ ( 2202, 6.60, 3.22),
+ ( 3425, 49.04, 4.91),
+ ( 4249, 109.09, 6.20),
+ ( 5635, 160.18, 6.59),
+ ('inf', 251.52, 6.95),
+ ),
+ 'quarterly': (
+ ( 1775, 0.00, 0.00),
+ ( 2653, 0.00, 2.26),
+ ( 6605, 19.84, 3.22),
+ ( 10275, 147.09, 4.91),
+ ( 12748, 327.29, 6.20),
+ ( 16905, 480.62, 6.59),
+ ( 'inf', 754.57, 6.95),
+ ),
+ 'semi-annual': (
+ ( 3550, 0.00, 0.00),
+ ( 5305, 0.00, 2.26),
+ ( 13210, 39.66, 3.22),
+ ( 20550, 294.20, 4.91),
+ ( 25495, 654.59, 6.20),
+ ( 33810, 961.18, 6.59),
+ ( 'inf', 1509.14, 6.95),
+ ),
+ 'annually': (
+ ( 7100, 0.00, 0.00),
+ ( 10610, 0.00, 2.26),
+ ( 26420, 79.33, 3.22),
+ ( 41100, 588.41, 4.91),
+ ( 50990, 1309.20, 6.20),
+ ( 67620, 1992.38, 6.59),
+ ( 'inf', 3018.30, 6.95),
+ ),
+ },
+ }
+
+
+
+
+
+
+ US NE Nebraska Allowances Rate
+ us_ne_sit_allowances_rate
+
+
+
+
+ {
+ 'weekly' : 37.69,
+ 'bi-weekly' : 75.38,
+ 'semi-monthly': 81.67,
+ 'monthly' : 163.33,
+ 'quarterly' : 490.00,
+ 'semi-annual': 980.00,
+ 'annually': 1960.00,
+ }
+
+
+
+
+
+
+
+
+ US Nebraska - Nebraska Department of Labor - Unemployment Tax
+
+
+
+ US Nebraska - Nebraska Department of Revenue - Income Tax
+
+
+
+
+
+
+
+
+
+ ER: US NE Nebraska State Unemployment
+ ER_US_NE_SUTA
+ python
+ result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_ne_suta_wage_base', rate='us_ne_suta_rate', state_code='NE')
+ code
+ result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_ne_suta_wage_base', rate='us_ne_suta_rate', state_code='NE')
+
+
+
+
+
+
+
+
+ EE: US NE Nebraska State Income Tax Withholding
+ EE_US_NE_SIT
+ python
+ result, _ = ne_nebraska_state_income_withholding(payslip, categories, worked_days, inputs)
+ code
+ result, result_rate = ne_nebraska_state_income_withholding(payslip, categories, worked_days, inputs)
+
+
+
+
+
\ No newline at end of file
diff --git a/l10n_us_hr_payroll/data/state/nh_new_hampshire.xml b/l10n_us_hr_payroll/data/state/nh_new_hampshire.xml
new file mode 100644
index 00000000..374ff539
--- /dev/null
+++ b/l10n_us_hr_payroll/data/state/nh_new_hampshire.xml
@@ -0,0 +1,51 @@
+
+
+
+
+ US NH New Hampshire SUTA Wage Base
+ us_nh_suta_wage_base
+
+
+
+
+ 14000.00
+
+
+
+
+
+
+
+ US NH New Hampshire SUTA Rate
+ us_nh_suta_rate
+
+
+
+
+ 1.2
+
+
+
+
+
+
+
+ US New Hampshire - Department of Employment Security - Unemployment Tax
+
+
+
+
+
+
+
+ ER: US NH New Hampshire State Unemployment
+ ER_US_NH_SUTA
+ python
+ result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_nh_suta_wage_base', rate='us_nh_suta_rate', state_code='NH')
+ code
+ result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_nh_suta_wage_base', rate='us_nh_suta_rate', state_code='NH')
+
+
+
+
+
diff --git a/l10n_us_hr_payroll/data/state/nj_newjersey.xml b/l10n_us_hr_payroll/data/state/nj_newjersey.xml
new file mode 100644
index 00000000..50c72dcd
--- /dev/null
+++ b/l10n_us_hr_payroll/data/state/nj_newjersey.xml
@@ -0,0 +1,1048 @@
+
+
+
+
+ US NJ NewJersey SUTA Wage Base
+ us_nj_suta_wage_base
+
+
+
+
+ 34400.00
+
+
+
+
+ 35300.00
+
+
+
+
+
+
+
+
+ US NJ New Jersey Employer Unemployment SUTA Rate
+ us_nj_suta_rate
+
+
+
+
+ 2.6825
+
+
+
+
+ 2.6825
+
+
+
+
+
+
+ US NJ New Jersey Employee Unemployment SUTA Rate
+ us_nj_suta_ee_rate
+
+
+
+
+ 0.3825
+
+
+
+
+ 0.3825
+
+
+
+
+
+
+
+ US NJ New Jersey Employer State Disability Insurance Rate
+ us_nj_sdi_rate
+
+
+
+
+ 0.5
+
+
+
+
+ 0.5
+
+
+
+
+
+
+ US NJ New Jersey Employee State Disability Insurance Rate
+ us_nj_sdi_ee_rate
+
+
+
+
+ 0.17
+
+
+
+
+ 0.26
+
+
+
+
+
+
+
+ US NJ New Jersey Employer Workforce Development Rate
+ us_nj_wf_rate
+
+
+
+
+ 0.1175
+
+
+
+
+ 0.1175
+
+
+
+
+
+
+ US NJ New Jersey Employee Workforce Development Rate
+ us_nj_wf_ee_rate
+
+
+
+
+ 0.0425
+
+
+
+
+ 0.0425
+
+
+
+
+
+
+
+ US NJ New Jersey Employer Family Leave Insurance Rate
+ us_nj_fli_rate
+
+
+
+
+ 0.0
+
+
+
+
+ 0.0
+
+
+
+
+
+
+ US NJ New Jersey Employee Family Leave Insurance Rate
+ us_nj_fli_ee_rate
+
+
+
+
+ 0.08
+
+
+
+
+ 0.16
+
+
+
+
+
+
+
+ US NJ NewJersey SIT Rate Table
+ us_nj_sit_rate
+
+
+
+
+ {
+ 'A': {
+ 'weekly': (
+ ( 385, 0.00, 1.50),
+ ( 673, 5.77, 2.00),
+ ( 769, 11.54, 3.90),
+ ( 1442, 15.29, 6.10),
+ ( 9615, 56.34, 7.00),
+ (96154, 628.46, 9.90),
+ ('inf', 9195.77, 11.80),
+ ),
+ 'bi-weekly': (
+ ( 769, 0.00, 1.50),
+ ( 1346, 12.00, 2.00),
+ ( 1538, 23.00, 3.90),
+ ( 2885, 31.00, 6.10),
+ ( 19231, 113.00, 7.00),
+ (192308, 1257.00, 9.90),
+ ( 'inf', 18392.00, 11.80),
+ ),
+ 'semi-monthly': (
+ ( 833, 0.00, 1.50),
+ ( 1458, 13.00, 2.00),
+ ( 1667, 25.00, 3.90),
+ ( 3125, 33.00, 6.10),
+ ( 20833, 122.00, 7.00),
+ (208333, 1362.00, 9.90),
+ ( 'inf', 19924.00, 11.80),
+ ),
+ 'monthly': (
+ ( 1667, 0.00, 1.50),
+ ( 2917, 25.00, 2.00),
+ ( 3333, 50.00, 3.90),
+ ( 6250, 66.00, 6.10),
+ ( 41667, 244.00, 7.00),
+ (416667, 2723.00, 9.90),
+ ( 'inf', 39848.00, 11.80),
+ ),
+ 'quarterly': (
+ ( 5000, 0.00, 1.50),
+ ( 8750, 75.00, 2.00),
+ ( 10000, 150.00, 3.90),
+ ( 18750, 198.75, 6.10),
+ ( 125000, 732.50, 7.00),
+ (1250000, 8170.00, 9.90),
+ ( 'inf', 119545.00, 11.80),
+ ),
+ 'semi-annual': (
+ ( 10000, 0.00, 1.50),
+ ( 17500, 150.00, 2.00),
+ ( 20000, 300.00, 3.90),
+ ( 37500, 397.50, 6.10),
+ ( 250000, 1465.00, 7.00),
+ (2500000, 16340.00, 9.90),
+ ( 'inf', 239090.00, 11.80),
+ ),
+ 'annual': (
+ ( 20000, 0.00, 1.50),
+ ( 35000, 300.00, 2.00),
+ ( 40000, 600.00, 3.90),
+ ( 75000, 795.00, 6.10),
+ ( 500000, 2930.00, 7.00),
+ (5000000, 32680.00, 9.90),
+ ( 'inf', 478180.00, 11.80),
+ ),
+ },
+ 'B': {
+ 'weekly': (
+ ( 385, 0.00, 1.50),
+ ( 962, 5.77, 2.00),
+ ( 1346, 17.31, 2.70),
+ ( 1538, 27.69, 3.90),
+ ( 2885, 35.19, 6.10),
+ ( 9615, 117.31, 7.00),
+ (96154, 588.46, 9.90),
+ ('inf', 9155.77, 11.80),
+ ),
+ 'bi-weekly': (
+ ( 769, 0.00, 1.50),
+ ( 1923, 12.00, 2.00),
+ ( 2692, 35.00, 2.70),
+ ( 3076, 55.00, 3.90),
+ ( 5769, 70.00, 6.10),
+ ( 19231, 235.00, 7.00),
+ (192308, 1177.00, 9.90),
+ ( 'inf', 18312.00, 11.80),
+ ),
+ 'semi-monthly': (
+ ( 833, 0.00, 1.50),
+ ( 2083, 12.50, 2.00),
+ ( 2917, 37.50, 2.70),
+ ( 3333, 59.99, 3.90),
+ ( 6250, 76.25, 6.10),
+ ( 20833, 254.19, 7.00),
+ (208333, 1275.00, 9.90),
+ ( 'inf', 19838.00, 11.80),
+ ),
+ 'monthly': (
+ ( 1667, 0.00, 1.50),
+ ( 4167, 25.00, 2.00),
+ ( 5833, 75.00, 2.70),
+ ( 6667, 120.00, 3.90),
+ ( 12500, 153.00, 6.10),
+ ( 41667, 508.00, 7.00),
+ (416667, 2550.00, 9.90),
+ ( 'inf', 39675.00, 11.80),
+ ),
+ 'quarterly': (
+ ( 5000, 0.00, 1.50),
+ ( 12500, 75.00, 2.00),
+ ( 17500, 225.00, 2.70),
+ ( 20000, 360.00, 3.90),
+ ( 37500, 397.50, 6.10),
+ ( 125000, 1525.00, 7.00),
+ (1250000, 7650.00, 9.90),
+ ( 'inf', 119025.00, 11.80),
+ ),
+ 'semi-annual': (
+ ( 10000, 0.00, 1.50),
+ ( 25000, 150.00, 2.00),
+ ( 35000, 450.00, 2.70),
+ ( 40000, 720.00, 3.90),
+ ( 75000, 915.00, 6.10),
+ ( 250000, 3050.00, 7.00),
+ (2500000, 15300.00, 9.90),
+ ( 'inf', 238050.00, 11.80),
+ ),
+ 'annual': (
+ ( 20000, 0.00, 1.50),
+ ( 50000, 300.00, 2.00),
+ ( 70000, 900.00, 2.70),
+ (80000, 1440.00, 3.90),
+ ( 150000, 1830.00, 6.10),
+ ( 500000, 6100.00, 7.00),
+ (5000000, 30600.00, 9.90),
+ ( 'inf', 476100.00, 11.80),
+ ),
+ },
+ 'C': {
+ 'weekly': (
+ ( 385, 0.00, 1.50),
+ ( 769, 5.77, 2.30),
+ ( 962, 14.62, 2.80),
+ ( 1154, 20.00, 3.50),
+ ( 2885, 26.73, 5.60),
+ ( 9615, 123.65, 6.60),
+ (96154, 567.88, 9.90),
+ ('inf', 9135.19, 11.80),
+ ),
+ 'bi-weekly': (
+ ( 769, 0.00, 1.50),
+ ( 1538, 11.54, 2.30),
+ ( 1923, 29.23, 2.80),
+ ( 2308, 40.00, 3.50),
+ ( 5769, 53.46, 5.60),
+ ( 19231, 247.31, 6.60),
+ (192308, 1135.77, 9.90),
+ ( 'inf', 18270.38, 11.80),
+ ),
+ 'semi-monthly': (
+ ( 833, 0.00, 1.50),
+ ( 1667, 12.50, 2.30),
+ ( 2083, 31.67, 2.80),
+ ( 2500, 43.33, 3.50),
+ ( 6250, 57.92, 5.60),
+ ( 20833, 267.92, 6.60),
+ (208333, 1230.42, 9.90),
+ ( 'inf', 19792.92, 11.80),
+ ),
+ 'monthly': (
+ ( 1667, 0.00, 1.50),
+ ( 3333, 25.00, 2.30),
+ ( 4167, 63.33, 2.80),
+ ( 5000, 86.67, 3.50),
+ ( 12500, 115.83, 5.60),
+ ( 41667, 535.85, 6.60),
+ (416667, 2460.83, 9.90),
+ ( 'inf', 39585.83, 11.80),
+ ),
+ 'quarterly': (
+ ( 5000, 0.00, 1.50),
+ ( 10000, 75.00, 2.30),
+ ( 12500, 190.00, 2.80),
+ ( 15000, 260.00, 3.50),
+ ( 37500, 347.50, 5.60),
+ ( 125000, 1607.50, 6.60),
+ (1250000, 7382.50, 9.90),
+ ( 'inf', 118757.50, 11.80),
+ ),
+ 'semi-annual': (
+ ( 10000, 0.00, 1.50),
+ ( 20000, 150.00, 2.30),
+ ( 25000, 380.00, 2.80),
+ ( 30000, 520.00, 3.50),
+ ( 75000, 695.00, 5.60),
+ ( 250000, 3215.00, 6.60),
+ (2500000, 14765.00, 9.90),
+ ( 'inf', 237515.00, 11.80),
+ ),
+ 'annual': (
+ ( 20000, 0.00, 1.50),
+ ( 40000, 300.00, 2.30),
+ ( 50000, 760.00, 2.80),
+ ( 60000, 1040.00, 3.50),
+ ( 150000, 1390.00, 5.60),
+ ( 500000, 6430.00, 6.60),
+ (5000000, 29530.00, 9.90),
+ ( 'inf', 475030.00, 11.80),
+ ),
+ },
+ 'D': {
+ 'weekly': (
+ ( 385, 0.00, 1.50),
+ ( 769, 5.77, 2.70),
+ ( 962, 16.15, 3.40),
+ ( 1154, 22.69, 4.30),
+ ( 2885, 30.96, 5.60),
+ ( 9615, 127.88, 6.50),
+ ( 96154, 565.38, 9.90),
+ ( 'inf', 9132.69, 11.80),
+ ),
+ 'bi-weekly': (
+ ( 769, 0.00, 1.50),
+ ( 1538, 11.54, 2.70),
+ ( 1923, 32.31, 3.40),
+ ( 2308, 45.38, 4.30),
+ ( 5769, 61.92, 5.60),
+ ( 19231, 255.77, 6.50),
+ (192308, 1130.77, 9.90),
+ ( 'inf', 18265.38, 11.80),
+ ),
+ 'semi-monthly': (
+ ( 833, 0.00, 1.50),
+ ( 1667, 12.50, 2.70),
+ ( 2083, 35.00, 3.40),
+ ( 2500, 49.17, 4.30),
+ ( 6250, 67.08, 5.60),
+ ( 20833, 277.08, 6.50),
+ (208333, 1225.00, 9.90),
+ ( 'inf', 19787.50, 11.80),
+ ),
+ 'monthly': (
+ ( 1667, 0.00, 1.50),
+ ( 3333, 25.00, 2.70),
+ ( 4167, 70.00, 3.40),
+ ( 5000, 98.33, 4.00),
+ ( 12500, 134.17, 5.60),
+ ( 41667, 554.17, 6.50),
+ (416667, 2450.00, 9.90),
+ ( 'inf', 39575.00, 11.80),
+ ),
+ 'quarterly': (
+ ( 5000, 0.00, 1.50),
+ ( 10000, 75.00, 2.07),
+ ( 12500, 210.00, 3.40),
+ ( 15000, 295.00, 4.30),
+ ( 37500, 402.50, 5.60),
+ ( 125000, 1662.50, 6.50),
+ (1250000, 7350.00, 9.90),
+ ( 'inf', 118725.00, 11.80),
+ ),
+ 'semi-annual': (
+ ( 10000, 0.00, 1.50),
+ ( 20000, 150.00, 2.70),
+ ( 25000, 420.00, 3.40),
+ ( 30000, 590.00, 4.30),
+ ( 75000, 805.00, 5.60),
+ ( 250000, 3325.00, 6.50),
+ (2500000, 14700.00, 9.90),
+ ( 'inf', 237450.00, 11.80),
+ ),
+ 'annual': (
+ ( 20000, 0.00, 1.50),
+ ( 40000, 300.00, 2.70),
+ ( 50000, 840.00, 3.40),
+ ( 60000, 1180.00, 4.30),
+ ( 150000, 1610.00, 5.60),
+ ( 250000, 6650.00, 6.50),
+ (2500000, 29400.00, 9.90),
+ ( 'inf', 474900.00, 11.80),
+ ),
+ },
+ 'E': {
+ 'weekly': (
+ ( 385, 0.00, 1.50),
+ ( 673, 5.77, 2.00),
+ ( 1923, 11.54, 5.80),
+ ( 9615, 84.04, 6.50),
+ ( 96154, 584.04, 9.90),
+ ( 'inf', 9151.35, 11.80),
+ ),
+ 'bi-weekly': (
+ ( 769, 0.00, 1.50),
+ ( 1346, 12.00, 2.00),
+ ( 3846, 23.00, 5.80),
+ ( 19231, 168.00, 6.50),
+ (192308, 1168.00, 9.90),
+ ( 'inf', 18303.00, 11.80),
+ ),
+ 'semi-monthly': (
+ ( 833, 0.00, 1.50),
+ ( 1458, 13.00, 2.00),
+ ( 4167, 25.00, 5.80),
+ ( 20833, 182.00, 6.50),
+ (208333, 1265.00, 9.90),
+ ( 'inf', 19828.00, 11.80),
+ ),
+ 'monthly': (
+ ( 1667, 0.00, 1.50),
+ ( 2916, 25.00, 2.00),
+ ( 8333, 50.00, 5.80),
+ ( 41667, 364.00, 6.50),
+ (416667, 2531.00, 9.90),
+ ( 'inf', 39656.00, 11.80),
+ ),
+ 'quarterly': (
+ ( 5000, 0.00, 1.50),
+ ( 8750, 75.00, 2.00),
+ ( 25000, 150.00, 5.80),
+ ( 125000, 1092.50, 6.50),
+ (1250000, 7592.50, 9.90),
+ ( 'inf', 118967.50, 11.80),
+ ),
+ 'semi-annual': (
+ ( 10000, 0.00, 1.50),
+ ( 17500, 150.00, 2.00),
+ ( 50000, 300.00, 5.80),
+ ( 250000, 2185.00, 6.50),
+ (2500000, 15185.00, 9.90),
+ ( 'inf', 237935.00, 11.80),
+ ),
+ 'annual': (
+ ( 20000, 0.00, 1.50),
+ ( 35000, 300.00, 2.00),
+ ( 100000, 600.00, 5.80),
+ ( 500000, 4370.00, 6.50),
+ (5000000, 30370.00, 9.90),
+ ( 'inf', 475870.00, 11.80),
+ ),
+ },
+ }
+
+
+
+
+
+ {
+ 'A': {
+ 'weekly': (
+ ( 385, 0.00, 1.50),
+ ( 673, 5.77, 2.00),
+ ( 769, 11.54, 3.90),
+ ( 1442, 15.29, 6.10),
+ ( 9615, 56.34, 7.00),
+ (96154, 628.46, 9.90),
+ ('inf', 9195.77, 11.80),
+ ),
+ 'bi-weekly': (
+ ( 769, 0.00, 1.50),
+ ( 1346, 12.00, 2.00),
+ ( 1538, 23.00, 3.90),
+ ( 2885, 31.00, 6.10),
+ ( 19231, 113.00, 7.00),
+ (192308, 1257.00, 9.90),
+ ( 'inf', 18392.00, 11.80),
+ ),
+ 'semi-monthly': (
+ ( 833, 0.00, 1.50),
+ ( 1458, 13.00, 2.00),
+ ( 1667, 25.00, 3.90),
+ ( 3125, 33.00, 6.10),
+ ( 20833, 122.00, 7.00),
+ (208333, 1362.00, 9.90),
+ ( 'inf', 19924.00, 11.80),
+ ),
+ 'monthly': (
+ ( 1667, 0.00, 1.50),
+ ( 2917, 25.00, 2.00),
+ ( 3333, 50.00, 3.90),
+ ( 6250, 66.00, 6.10),
+ ( 41667, 244.00, 7.00),
+ (416667, 2723.00, 9.90),
+ ( 'inf', 39848.00, 11.80),
+ ),
+ 'quarterly': (
+ ( 5000, 0.00, 1.50),
+ ( 8750, 75.00, 2.00),
+ ( 10000, 150.00, 3.90),
+ ( 18750, 198.75, 6.10),
+ ( 125000, 732.50, 7.00),
+ (1250000, 8170.00, 9.90),
+ ( 'inf', 119545.00, 11.80),
+ ),
+ 'semi-annual': (
+ ( 10000, 0.00, 1.50),
+ ( 17500, 150.00, 2.00),
+ ( 20000, 300.00, 3.90),
+ ( 37500, 397.50, 6.10),
+ ( 250000, 1465.00, 7.00),
+ (2500000, 16340.00, 9.90),
+ ( 'inf', 239090.00, 11.80),
+ ),
+ 'annual': (
+ ( 20000, 0.00, 1.50),
+ ( 35000, 300.00, 2.00),
+ ( 40000, 600.00, 3.90),
+ ( 75000, 795.00, 6.10),
+ ( 500000, 2930.00, 7.00),
+ (5000000, 32680.00, 9.90),
+ ( 'inf', 478180.00, 11.80),
+ ),
+ },
+ 'B': {
+ 'weekly': (
+ ( 385, 0.00, 1.50),
+ ( 962, 5.77, 2.00),
+ ( 1346, 17.31, 2.70),
+ ( 1538, 27.69, 3.90),
+ ( 2885, 35.19, 6.10),
+ ( 9615, 117.31, 7.00),
+ (96154, 588.46, 9.90),
+ ('inf', 9155.77, 11.80),
+ ),
+ 'bi-weekly': (
+ ( 769, 0.00, 1.50),
+ ( 1923, 12.00, 2.00),
+ ( 2692, 35.00, 2.70),
+ ( 3076, 55.00, 3.90),
+ ( 5769, 70.00, 6.10),
+ ( 19231, 235.00, 7.00),
+ (192308, 1177.00, 9.90),
+ ( 'inf', 18312.00, 11.80),
+ ),
+ 'semi-monthly': (
+ ( 833, 0.00, 1.50),
+ ( 2083, 12.50, 2.00),
+ ( 2917, 37.50, 2.70),
+ ( 3333, 59.99, 3.90),
+ ( 6250, 76.25, 6.10),
+ ( 20833, 254.19, 7.00),
+ (208333, 1275.00, 9.90),
+ ( 'inf', 19838.00, 11.80),
+ ),
+ 'monthly': (
+ ( 1667, 0.00, 1.50),
+ ( 4167, 25.00, 2.00),
+ ( 5833, 75.00, 2.70),
+ ( 6667, 120.00, 3.90),
+ ( 12500, 153.00, 6.10),
+ ( 41667, 508.00, 7.00),
+ (416667, 2550.00, 9.90),
+ ( 'inf', 39675.00, 11.80),
+ ),
+ 'quarterly': (
+ ( 5000, 0.00, 1.50),
+ ( 12500, 75.00, 2.00),
+ ( 17500, 225.00, 2.70),
+ ( 20000, 360.00, 3.90),
+ ( 37500, 397.50, 6.10),
+ ( 125000, 1525.00, 7.00),
+ (1250000, 7650.00, 9.90),
+ ( 'inf', 119025.00, 11.80),
+ ),
+ 'semi-annual': (
+ ( 10000, 0.00, 1.50),
+ ( 25000, 150.00, 2.00),
+ ( 35000, 450.00, 2.70),
+ ( 40000, 720.00, 3.90),
+ ( 75000, 915.00, 6.10),
+ ( 250000, 3050.00, 7.00),
+ (2500000, 15300.00, 9.90),
+ ( 'inf', 238050.00, 11.80),
+ ),
+ 'annual': (
+ ( 20000, 0.00, 1.50),
+ ( 50000, 300.00, 2.00),
+ ( 70000, 900.00, 2.70),
+ (80000, 1440.00, 3.90),
+ ( 150000, 1830.00, 6.10),
+ ( 500000, 6100.00, 7.00),
+ (5000000, 30600.00, 9.90),
+ ( 'inf', 476100.00, 11.80),
+ ),
+ },
+ 'C': {
+ 'weekly': (
+ ( 385, 0.00, 1.50),
+ ( 769, 5.77, 2.30),
+ ( 962, 14.62, 2.80),
+ ( 1154, 20.00, 3.50),
+ ( 2885, 26.73, 5.60),
+ ( 9615, 123.65, 6.60),
+ (96154, 567.88, 9.90),
+ ('inf', 9135.19, 11.80),
+ ),
+ 'bi-weekly': (
+ ( 769, 0.00, 1.50),
+ ( 1538, 11.54, 2.30),
+ ( 1923, 29.23, 2.80),
+ ( 2308, 40.00, 3.50),
+ ( 5769, 53.46, 5.60),
+ ( 19231, 247.31, 6.60),
+ (192308, 1135.77, 9.90),
+ ( 'inf', 18270.38, 11.80),
+ ),
+ 'semi-monthly': (
+ ( 833, 0.00, 1.50),
+ ( 1667, 12.50, 2.30),
+ ( 2083, 31.67, 2.80),
+ ( 2500, 43.33, 3.50),
+ ( 6250, 57.92, 5.60),
+ ( 20833, 267.92, 6.60),
+ (208333, 1230.42, 9.90),
+ ( 'inf', 19792.92, 11.80),
+ ),
+ 'monthly': (
+ ( 1667, 0.00, 1.50),
+ ( 3333, 25.00, 2.30),
+ ( 4167, 63.33, 2.80),
+ ( 5000, 86.67, 3.50),
+ ( 12500, 115.83, 5.60),
+ ( 41667, 535.85, 6.60),
+ (416667, 2460.83, 9.90),
+ ( 'inf', 39585.83, 11.80),
+ ),
+ 'quarterly': (
+ ( 5000, 0.00, 1.50),
+ ( 10000, 75.00, 2.30),
+ ( 12500, 190.00, 2.80),
+ ( 15000, 260.00, 3.50),
+ ( 37500, 347.50, 5.60),
+ ( 125000, 1607.50, 6.60),
+ (1250000, 7382.50, 9.90),
+ ( 'inf', 118757.50, 11.80),
+ ),
+ 'semi-annual': (
+ ( 10000, 0.00, 1.50),
+ ( 20000, 150.00, 2.30),
+ ( 25000, 380.00, 2.80),
+ ( 30000, 520.00, 3.50),
+ ( 75000, 695.00, 5.60),
+ ( 250000, 3215.00, 6.60),
+ (2500000, 14765.00, 9.90),
+ ( 'inf', 237515.00, 11.80),
+ ),
+ 'annual': (
+ ( 20000, 0.00, 1.50),
+ ( 40000, 300.00, 2.30),
+ ( 50000, 760.00, 2.80),
+ ( 60000, 1040.00, 3.50),
+ ( 150000, 1390.00, 5.60),
+ ( 500000, 6430.00, 6.60),
+ (5000000, 29530.00, 9.90),
+ ( 'inf', 475030.00, 11.80),
+ ),
+ },
+ 'D': {
+ 'weekly': (
+ ( 385, 0.00, 1.50),
+ ( 769, 5.77, 2.70),
+ ( 962, 16.15, 3.40),
+ ( 1154, 22.69, 4.30),
+ ( 2885, 30.96, 5.60),
+ ( 9615, 127.88, 6.50),
+ ( 96154, 565.38, 9.90),
+ ( 'inf', 9132.69, 11.80),
+ ),
+ 'bi-weekly': (
+ ( 769, 0.00, 1.50),
+ ( 1538, 11.54, 2.70),
+ ( 1923, 32.31, 3.40),
+ ( 2308, 45.38, 4.30),
+ ( 5769, 61.92, 5.60),
+ ( 19231, 255.77, 6.50),
+ (192308, 1130.77, 9.90),
+ ( 'inf', 18265.38, 11.80),
+ ),
+ 'semi-monthly': (
+ ( 833, 0.00, 1.50),
+ ( 1667, 12.50, 2.70),
+ ( 2083, 35.00, 3.40),
+ ( 2500, 49.17, 4.30),
+ ( 6250, 67.08, 5.60),
+ ( 20833, 277.08, 6.50),
+ (208333, 1225.00, 9.90),
+ ( 'inf', 19787.50, 11.80),
+ ),
+ 'monthly': (
+ ( 1667, 0.00, 1.50),
+ ( 3333, 25.00, 2.70),
+ ( 4167, 70.00, 3.40),
+ ( 5000, 98.33, 4.00),
+ ( 12500, 134.17, 5.60),
+ ( 41667, 554.17, 6.50),
+ (416667, 2450.00, 9.90),
+ ( 'inf', 39575.00, 11.80),
+ ),
+ 'quarterly': (
+ ( 5000, 0.00, 1.50),
+ ( 10000, 75.00, 2.07),
+ ( 12500, 210.00, 3.40),
+ ( 15000, 295.00, 4.30),
+ ( 37500, 402.50, 5.60),
+ ( 125000, 1662.50, 6.50),
+ (1250000, 7350.00, 9.90),
+ ( 'inf', 118725.00, 11.80),
+ ),
+ 'semi-annual': (
+ ( 10000, 0.00, 1.50),
+ ( 20000, 150.00, 2.70),
+ ( 25000, 420.00, 3.40),
+ ( 30000, 590.00, 4.30),
+ ( 75000, 805.00, 5.60),
+ ( 250000, 3325.00, 6.50),
+ (2500000, 14700.00, 9.90),
+ ( 'inf', 237450.00, 11.80),
+ ),
+ 'annual': (
+ ( 20000, 0.00, 1.50),
+ ( 40000, 300.00, 2.70),
+ ( 50000, 840.00, 3.40),
+ ( 60000, 1180.00, 4.30),
+ ( 150000, 1610.00, 5.60),
+ ( 250000, 6650.00, 6.50),
+ (2500000, 29400.00, 9.90),
+ ( 'inf', 474900.00, 11.80),
+ ),
+ },
+ 'E': {
+ 'weekly': (
+ ( 385, 0.00, 1.50),
+ ( 673, 5.77, 2.00),
+ ( 1923, 11.54, 5.80),
+ ( 9615, 84.04, 6.50),
+ ( 96154, 584.04, 9.90),
+ ( 'inf', 9151.35, 11.80),
+ ),
+ 'bi-weekly': (
+ ( 769, 0.00, 1.50),
+ ( 1346, 12.00, 2.00),
+ ( 3846, 23.00, 5.80),
+ ( 19231, 168.00, 6.50),
+ (192308, 1168.00, 9.90),
+ ( 'inf', 18303.00, 11.80),
+ ),
+ 'semi-monthly': (
+ ( 833, 0.00, 1.50),
+ ( 1458, 13.00, 2.00),
+ ( 4167, 25.00, 5.80),
+ ( 20833, 182.00, 6.50),
+ (208333, 1265.00, 9.90),
+ ( 'inf', 19828.00, 11.80),
+ ),
+ 'monthly': (
+ ( 1667, 0.00, 1.50),
+ ( 2916, 25.00, 2.00),
+ ( 8333, 50.00, 5.80),
+ ( 41667, 364.00, 6.50),
+ (416667, 2531.00, 9.90),
+ ( 'inf', 39656.00, 11.80),
+ ),
+ 'quarterly': (
+ ( 5000, 0.00, 1.50),
+ ( 8750, 75.00, 2.00),
+ ( 25000, 150.00, 5.80),
+ ( 125000, 1092.50, 6.50),
+ (1250000, 7592.50, 9.90),
+ ( 'inf', 118967.50, 11.80),
+ ),
+ 'semi-annual': (
+ ( 10000, 0.00, 1.50),
+ ( 17500, 150.00, 2.00),
+ ( 50000, 300.00, 5.80),
+ ( 250000, 2185.00, 6.50),
+ (2500000, 15185.00, 9.90),
+ ( 'inf', 237935.00, 11.80),
+ ),
+ 'annual': (
+ ( 20000, 0.00, 1.50),
+ ( 35000, 300.00, 2.00),
+ ( 100000, 600.00, 5.80),
+ ( 500000, 4370.00, 6.50),
+ (5000000, 30370.00, 9.90),
+ ( 'inf', 475870.00, 11.80),
+ ),
+ },
+ }
+
+
+
+
+
+
+ US NJ NewJersey SIT Allowance Rate
+ us_nj_sit_allowance_rate
+
+
+
+
+ {
+ 'weekly': 19.20,
+ 'bi-weekly': 38.40,
+ 'semi-monthly': 41.60,
+ 'monthly': 83.30,
+ 'quarterly': 250.00,
+ 'semi-annual': 500.00,
+ 'annual': 1000.00,
+ 'daily or miscellaneous': 2.70,
+ }
+
+
+
+
+
+ {
+ 'weekly': 19.20,
+ 'bi-weekly': 38.40,
+ 'semi-monthly': 41.60,
+ 'monthly': 83.30,
+ 'quarterly': 250.00,
+ 'semi-annual': 500.00,
+ 'annual': 1000.00,
+ 'daily or miscellaneous': 2.70,
+ }
+
+
+
+
+
+
+
+ US New Jersey - Division of Taxation - Unemployment Tax
+
+
+
+ US New Jersey - Division of Taxation - Income Tax
+
+
+
+
+
+
+
+
+ ER: US NJ New Jersey State Unemployment
+ ER_US_NJ_SUTA
+ python
+ result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_nj_suta_wage_base', rate='us_nj_suta_rate', state_code='NJ')
+ code
+ result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_nj_suta_wage_base', rate='us_nj_suta_rate', state_code='NJ')
+
+
+
+
+
+
+
+
+ EE: US NJ New Jersey State Unemployment
+ EE_US_NJ_SUTA
+ python
+ result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_nj_suta_wage_base', rate='us_nj_suta_ee_rate', state_code='NJ')
+ code
+ result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_nj_suta_wage_base', rate='us_nj_suta_ee_rate', state_code='NJ')
+
+
+
+
+
+
+
+
+
+ ER: US NJ New Jersey State Disability Insurance
+ ER_US_NJ_SDI
+ python
+ result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_nj_suta_wage_base', rate='us_nj_sdi_rate', state_code='NJ')
+ code
+ result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_nj_suta_wage_base', rate='us_nj_sdi_rate', state_code='NJ')
+
+
+
+
+
+
+
+
+ EE: US NJ New Jersey State Disability Insurance
+ EE_US_NJ_SDI
+ python
+ result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_nj_suta_wage_base', rate='us_nj_sdi_ee_rate', state_code='NJ')
+ code
+ result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_nj_suta_wage_base', rate='us_nj_sdi_ee_rate', state_code='NJ')
+
+
+
+
+
+
+
+
+
+ ER: US NJ New Jersey Workforce Development
+ ER_US_NJ_WF
+ python
+ result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_nj_suta_wage_base', rate='us_nj_wf_rate', state_code='NJ')
+ code
+ result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_nj_suta_wage_base', rate='us_nj_wf_rate', state_code='NJ')
+
+
+
+
+
+
+
+
+ EE: US NJ New Jersey Workforce Development
+ EE_US_NJ_WF
+ python
+ result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_nj_suta_wage_base', rate='us_nj_wf_ee_rate', state_code='NJ')
+ code
+ result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_nj_suta_wage_base', rate='us_nj_wf_ee_rate', state_code='NJ')
+
+
+
+
+
+
+
+
+
+ ER: US NJ New Jersey Family Leave Insurance
+ ER_US_NJ_FLI
+ python
+ result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_nj_suta_wage_base', rate='us_nj_fli_rate', state_code='NJ')
+ code
+ result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_nj_suta_wage_base', rate='us_nj_fli_rate', state_code='NJ')
+
+
+
+
+
+
+
+
+ EE: US NJ New Jersey Family Leave Insurance
+ EE_US_NJ_FLI
+ python
+ result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_nj_suta_wage_base', rate='us_nj_fli_ee_rate', state_code='NJ')
+ code
+ result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_nj_suta_wage_base', rate='us_nj_fli_ee_rate', state_code='NJ')
+
+
+
+
+
+
+
+
+ EE: US NJ New Jersey State Income Tax Withholding
+ EE_US_NJ_SIT
+ python
+ result, _ = nj_newjersey_state_income_withholding(payslip, categories, worked_days, inputs)
+ code
+ result, result_rate = nj_newjersey_state_income_withholding(payslip, categories, worked_days, inputs)
+
+
+
+
+
\ No newline at end of file
diff --git a/l10n_us_hr_payroll/data/state/nm_new_mexico.xml b/l10n_us_hr_payroll/data/state/nm_new_mexico.xml
new file mode 100644
index 00000000..2c4249a0
--- /dev/null
+++ b/l10n_us_hr_payroll/data/state/nm_new_mexico.xml
@@ -0,0 +1,303 @@
+
+
+
+
+ US NM New Mexico SUTA Wage Base
+ us_nm_suta_wage_base
+
+
+
+
+ 25800.0
+
+
+
+
+
+
+
+ US NM New Mexico SUTA Rate
+ us_nm_suta_rate
+
+
+
+
+ 1.0
+
+
+
+
+
+
+ US NM New Mexico SIT Tax Rate
+ us_nm_sit_tax_rate
+
+
+
+
+
+ {
+ 'single': {
+ 'weekly': (
+ ( 119, 0.00, 0.0),
+ ( 225, 0.00, 1.7),
+ ( 331, 1.80, 3.2),
+ ( 427, 5.18, 4.7),
+ ( 619, 9.70, 4.9),
+ ( 927, 19.13, 4.9),
+ ( 1369, 34.20, 4.9),
+ ('inf', 55.88, 4.9),
+ ),
+ 'bi-weekly': (
+ ( 238, 0.00, 0.0),
+ ( 450, 0.00, 1.7),
+ ( 662, 3.60, 3.2),
+ ( 854, 10.37, 4.7),
+ ( 1238, 19.40, 4.9),
+ ( 1854, 38.25, 4.9),
+ ( 2738, 68.40, 4.9),
+ ('inf', 111.75, 4.9),
+ ),
+ 'semi-monthly': (
+ ( 258, 0.00, 0.0),
+ ( 488, 0.00, 1.7),
+ ( 717, 3.90, 3.2),
+ ( 925, 11.23, 4.7),
+ ( 1342, 21.02, 4.9),
+ ( 2008, 41.44, 4.9),
+ ( 2967, 74.10, 4.9),
+ ('inf', 121.06, 4.9),
+ ),
+ 'monthly': (
+ ( 517, 0.00, 0.0),
+ ( 975, 0.00, 1.7),
+ ( 1433, 7.79, 3.2),
+ ( 1850, 22.46, 4.7),
+ ( 2683, 42.04, 4.9),
+ ( 4017, 82.88, 4.9),
+ ( 5933, 148.21, 4.9),
+ ('inf', 242.13, 4.9),
+ ),
+ 'quarterly': (
+ ( 1550, 0.00, 0.0),
+ ( 2925, 0.00, 1.7),
+ ( 4300, 23.38, 3.2),
+ ( 5550, 67.38, 4.7),
+ ( 8050, 126.13, 4.9),
+ ( 12050, 248.63, 4.9),
+ ( 17800, 444.63, 4.9),
+ ( 'inf', 726.38, 4.9),
+ ),
+ 'semi-annual': (
+ ( 3100, 0.00, 0.0),
+ ( 5850, 0.00, 1.7),
+ ( 8600, 46.75, 3.2),
+ (11100, 134.75, 4.7),
+ (16100, 252.25, 4.9),
+ (24100, 497.25, 4.9),
+ (35600, 889.25, 4.9),
+ ('inf', 1452.75, 4.9),
+ ),
+ 'annually': (
+ ( 6200, 0.00, 0.0),
+ (11700, 0.00, 1.7),
+ (17200, 93.50, 3.2),
+ (22200, 269.50, 4.7),
+ (32200, 504.50, 4.9),
+ (48200, 994.50, 4.9),
+ (71200, 1778.50, 4.9),
+ ('inf', 2905.50, 4.9),
+ ),
+ },
+ 'married': {
+ 'weekly': (
+ ( 238, 0.00, 0.0),
+ ( 392, 0.00, 1.7),
+ ( 546, 2.62, 3.2),
+ ( 700, 7.54, 4.7),
+ ( 1008, 14.77, 4.9),
+ ( 1469, 29.85, 4.9),
+ ( 2162, 52.46, 4.9),
+ ('inf', 86.38, 4.9),
+ ),
+ 'bi-weekly': (
+ ( 477, 0.00, 0.0),
+ ( 785, 0.00, 1.7),
+ ( 1092, 5.23, 3.2),
+ ( 1400, 15.08, 4.7),
+ (2015, 29.54, 4.9),
+ ( 2938, 59.69, 4.9),
+ ( 4323, 104.92, 4.9),
+ ('inf', 172.77, 4.9),
+ ),
+ 'semi-monthly': (
+ ( 517, 0.00, 0.0),
+ ( 850, 0.00, 1.7),
+ ( 1183, 5.67, 3.2),
+ ( 1517, 16.33, 4.7),
+ ( 2183, 32.00, 4.9),
+ ( 3183, 64.67, 4.9),
+ ( 4683, 113.67, 4.9),
+ ('inf', 187.17, 4.9),
+ ),
+ 'monthly': (
+ ( 1033, 0.00, 0.0),
+ ( 1700, 0.00, 1.7),
+ ( 2367, 11.33, 3.2),
+ ( 3033, 32.67, 4.7),
+ ( 4367, 64.00, 4.9),
+ ( 6367, 129.33, 4.9),
+ ( 9367, 227.33, 4.9),
+ ('inf', 374.33, 4.9),
+ ),
+ 'quarterly': (
+ ( 3100, 0.00, 0.0),
+ ( 5100, 0.00, 1.7),
+ ( 7100, 34.00, 3.2),
+ ( 9100, 98.00, 4.7),
+ (13100, 192.00, 4.9),
+ (19100, 388.00, 4.9),
+ (28100, 682.00, 4.9),
+ ('inf', 1123.00, 4.9),
+ ),
+ 'semi-annual': (
+ ( 6200, 0.00, 0.0),
+ (10200, 0.00, 1.7),
+ (14200, 68.00, 3.2),
+ (18200, 196.00, 4.7),
+ (26200, 384.00, 4.9),
+ (38200, 776.00, 4.9),
+ (56200, 1364.00, 4.9),
+ ('inf', 2246.00, 4.9),
+ ),
+ 'annually': (
+ ( 12400, 0.00, 0.0),
+ ( 20400, 0.00, 1.7),
+ ( 28400, 136.00, 3.2),
+ ( 36400, 392.00, 4.7),
+ ( 52400, 768.00, 4.9),
+ ( 76400, 1552.00, 4.9),
+ (112400, 2728.00, 4.9),
+ ( 'inf', 4492.00, 4.9),
+ ),
+ },
+ 'married_as_single': {
+ 'weekly': (
+ ( 179, 0.00, 0.0),
+ ( 333, 0.00, 1.7),
+ ( 487, 2.62, 3.2),
+ ( 641, 7.54, 4.7),
+ ( 949, 14.77, 4.9),
+ ( 1410, 29.85, 4.9),
+ ( 2102, 52.46, 4.9),
+ ('inf', 86.38, 4.9),
+ ),
+ 'bi-weekly': (
+ ( 359, 0.00, 0.0),
+ ( 666, 0.00, 1.7),
+ ( 974, 5.23, 3.2),
+ ( 1282, 15.08, 4.7),
+ ( 1897, 29.54, 4.9),
+ ( 2820, 59.69, 4.9),
+ ( 4205, 104.92, 4.9),
+ ('inf', 172.77, 4.9),
+ ),
+ 'semi-monthly': (
+ ( 389, 0.00, 0.0),
+ ( 722, 0.00, 1.7),
+ ( 1055, 5.67, 3.2),
+ ( 1389, 16.33, 4.7),
+ ( 2055, 32.00, 4.9),
+ ( 3055, 64.67, 4.9),
+ ( 4555, 113.67, 4.9),
+ ('inf', 187.17, 4.9),
+ ),
+ 'monthly': (
+ ( 777, 0.00, 0.0),
+ ( 1444, 0.00, 1.7),
+ ( 2110, 11.33, 3.2),
+ ( 2777, 32.67, 4.7),
+ ( 4110, 64.00, 4.9),
+ ( 6110, 129.33, 4.9),
+ ( 9110, 227.33, 4.9),
+ ('inf', 374.33, 4.9),
+ ),
+ 'quarterly': (
+ ( 2331, 0.00, 0.0),
+ ( 4331, 0.00, 1.7),
+ ( 6331, 34.00, 3.2),
+ ( 8331, 98.00, 4.7),
+ ( 12331, 192.00, 4.9),
+ ( 18331, 388.00, 4.9),
+ ( 27331, 682.00, 4.9),
+ ( 'inf', 1123.00, 4.9),
+ ),
+ 'semi-annual': (
+ ( 4663, 0.00, 0.0),
+ ( 8663, 0.00, 1.7),
+ ( 12663, 68.00, 3.2),
+ ( 16663, 196.00, 4.7),
+ ( 24663, 384.00, 4.9),
+ ( 36663, 776.00, 4.9),
+ ( 54663, 1364.00, 4.9),
+ ( 'inf', 2246.00, 4.9),
+ ),
+ 'annually': (
+ ( 9325, 0.00, 0.0),
+ ( 17325, 0.00, 1.7),
+ ( 25325, 136.00, 3.2),
+ ( 33325, 392.00, 4.7),
+ ( 49325, 768.00, 4.9),
+ ( 73325, 1552.00, 4.9),
+ (109325, 2728.00, 4.9),
+ ( 'inf', 4492.00, 4.9),
+ ),
+ }
+ }
+
+
+
+
+
+
+
+
+ US New Mexico - Department of Workforce Solutions - Unemployment Tax
+
+
+
+ US New Mexico - Department of Taxation and Revenue - Income Tax
+
+
+
+
+
+
+
+
+
+ ER: US NM New Mexico State Unemployment
+ ER_US_NM_SUTA
+ python
+ result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_nm_suta_wage_base', rate='us_nm_suta_rate', state_code='NM')
+ code
+ result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_nm_suta_wage_base', rate='us_nm_suta_rate', state_code='NM')
+
+
+
+
+
+
+
+
+ EE: US NM New Mexico State Income Tax Withholding
+ EE_US_NM_SIT
+ python
+ result, _ = nm_new_mexico_state_income_withholding(payslip, categories, worked_days, inputs)
+ code
+ result, result_rate = nm_new_mexico_state_income_withholding(payslip, categories, worked_days, inputs)
+
+
+
+
+
\ No newline at end of file
diff --git a/l10n_us_hr_payroll/data/state/nv_nevada.xml b/l10n_us_hr_payroll/data/state/nv_nevada.xml
new file mode 100644
index 00000000..46e3c2d5
--- /dev/null
+++ b/l10n_us_hr_payroll/data/state/nv_nevada.xml
@@ -0,0 +1,53 @@
+
+
+
+
+ US NV Nevada SUTA Wage Base
+ us_nv_suta_wage_base
+
+
+
+
+ 32500.00
+
+
+
+
+
+
+
+ US NV Nevada SUTA Rate
+ us_nv_suta_rate
+
+
+
+
+ 2.95
+
+
+
+
+
+
+
+ US Nevada - Department of Employment, Training, and Rehabilitation, Employment Security Division - Unemployment Tax
+
+
+
+
+
+
+
+
+
+ ER: US NV Nevada State Unemployment (RT-6)
+ ER_US_NV_SUTA
+ python
+ result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_nv_suta_wage_base', rate='us_nv_suta_rate', state_code='NV')
+ code
+ result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_nv_suta_wage_base', rate='us_nv_suta_rate', state_code='NV')
+
+
+
+
+
\ No newline at end of file
diff --git a/l10n_us_hr_payroll/data/state/ny_new_york.xml b/l10n_us_hr_payroll/data/state/ny_new_york.xml
new file mode 100644
index 00000000..a6f17a70
--- /dev/null
+++ b/l10n_us_hr_payroll/data/state/ny_new_york.xml
@@ -0,0 +1,429 @@
+
+
+
+
+ US NY New York SUTA Wage Base
+ us_ny_suta_wage_base
+
+
+
+
+ 11400.0
+
+
+
+
+ 11600.0
+
+
+
+
+
+
+
+ US NY New York SUTA Rate
+ us_ny_suta_rate
+
+
+
+
+ 2.5
+
+
+
+
+ 2.5
+
+
+
+
+
+
+ US NY New York SUTA RSF Rate
+ us_ny_suta_rsf_rate
+
+
+
+
+ 0.075
+
+
+
+
+ 0.075
+
+
+
+
+
+
+ US NY New York SUTA MCTMT Rate
+ us_ny_suta_mctmt_rate
+
+
+
+
+ 0.0
+
+
+
+
+ 0.0
+
+
+
+
+
+
+ US NY New York SIT Tax Rate
+ us_ny_sit_tax_rate
+
+
+
+
+ {
+ 'single': {
+ 'weekly': ((163, 0.0400, 0.0), (225, 0.0450, 6.54), (267, 0.0525, 9.31), (412, 0.0590, 11.54), (1551, 0.0633, 20.04), (1862, 0.0657, 92.17), (2070, 0.0758, 112.58), (3032, 0.0808, 128.38), (4142, 0.0707, 206.08), (5104, 0.0856, 284.60), (20722, 0.0735, 366.90), (21684, 0.5208, 1514.85), ('inf', 0.0962, 2015.62)),
+ 'bi-weekly': ((327, 0.0400, 0.0), (450, 0.0450, 13.08), (535, 0.0525, 18.62), (823, 0.0590, 23.08), (3102, 0.0633, 40.08), (3723, 0.0657, 184.35), (4140, 0.0758, 225.15), (6063, 0.0808, 256.77), (8285, 0.0707, 412.15), (10208, 0.0856, 569.19), (41444, 0.0735, 733.81), (43367, 0.5208, 3029.69), ('inf', 0.0962, 4021.23)),
+ 'semi-monthly': ((354, 0.0400, 0.0), (488, 0.0450, 14.17), (579, 0.0525, 20.17), (892, 0.0590, 25.00), (3360, 0.0633, 43.42), (4033, 0.0657, 199.71), (4485, 0.0758, 243.92), (6569, 0.0808, 278.17), (8975, 0.0707, 446.50), (11058, 0.0856, 616.63), (44898, 0.0735, 794.96), (46981, 0.5208, 3282.17), ('inf', 0.0962, 4367.17)),
+ 'monthly': ((708, 0.0400, 0.0), (975, 0.0450, 28.33), (1158, 0.0525, 40.33), (1783, 0.0590, 50.00), (6721, 0.0633, 86.83), (8067, 0.0657, 399.42), (8971, 0.0758, 487.83), (13138, 0.0808, 556.33), (17950, 0.0707, 893.00), (22117, 0.0856, 1233.25), (89796, 0.0735, 1589.92), (93963, 0.5208, 6564.33), ('inf', 0.0962, 8734.33)),
+ 'annually': ((8500, 0.0400, 0.0), (11700, 0.0450, 340.00), (13900, 0.0525, 484.00), (21400, 0.0590, 600.00), (80650, 0.0633, 1042.00), (96800, 0.0657, 4793.00), (107650, 0.0758, 5854.00), (157650, 0.0808, 6676.00), (215400, 0.0707, 10716.00), (265400, 0.0856, 14799.00), (1077550, 0.0735, 19079.00), (1127550, 0.5208, 78772.00), ('inf', 0.0962, 104812.00)),
+ },
+ 'married': {
+ 'weekly': ((163, 0.0400, 0.0), (225, 0.0450, 6.54), (267, 0.0525, 9.31), (412, 0.0590, 11.54), (1551, 0.0633, 20.04), (1862, 0.0657, 92.17), (2070, 0.0783, 112.58), (3032, 0.0833, 128.90), (4068, 0.0785, 209.00), (6215, 0.0707, 290.37), (7177, 0.0916, 442.17), (20722, 0.0735, 530.25), (41449, 0.0765, 1525.83), (42411, 0.9454, 3111.42), ('inf', 0.0962, 4020.46)),
+ 'bi-weekly': ((327, 0.0400, 0.0), (450, 0.0450, 13.08), (535, 0.0525, 18.62), (823, 0.0590, 23.08), (3102, 0.0633, 40.08), (3723, 0.0657, 184.35), (4140, 0.0783, 225.15), (6063, 0.0833, 257.81), (8137, 0.0785, 418.00), (12431, 0.0707, 580.73), (14354, 0.0916, 884.35), (41444, 0.0735, 1060.50), (82898, 0.0765, 3051.65), (84821, 0.9454, 6222.85), ('inf', 0.0962, 8040.92)),
+ 'semi-monthly': ((354, 0.0400, 0.0), (488, 0.0450, 14.17), (579, 0.0525, 20.17), (892, 0.0590, 25.00), (3360, 0.0633, 43.42), (4033, 0.0657, 199.71), (4485, 0.0783, 243.92), (6569, 0.0833, 279.29), (8815, 0.0785, 452.83), (13476, 0.0707, 629.13), (15550, 0.0916, 958.04), (44898, 0.0735, 1148.88), (89806, 0.0765, 3305.96), (91890, 0.9454, 6741.42), ('inf', 0.0962, 8711.00)),
+ 'monthly': ((708, 0.0400, 0.0), (975, 0.0450, 28.33), (1158, 0.0525, 40.33), (1783, 0.0590, 50.00), (6721, 0.0633, 86.83), (8067, 0.0657, 399.42), (8971, 0.0783, 487.83), (13138, 0.0833, 558.58), (17629, 0.0785, 905.67), (26933, 0.0707, 1258.25), (31100, 0.0916, 1916.08), (89796, 0.0735, 2297.75), (179613, 0.0765, 6611.92), (183779, 0.9454, 13482.83), ('inf', 0.0962, 17422.00)),
+ 'annually': ((8500, 0.0400, 0.0), (11700, 0.0450, 340.00), (13900, 0.0525, 484.00), (21400, 0.0590, 600.00), (80650, 0.0633, 1042.00), (96800, 0.0657, 4793.00), (107650, 0.0783, 5854.00), (157650, 0.0833, 6703.00), (211550, 0.0785, 10868.00), (323200, 0.0707, 15099.00), (373200, 0.0916, 22993.00), (1077550, 0.0735, 27573.00), (2155350, 0.0765, 79343.00), (2205350, 0.9454, 161794.00), ('inf', 0.0962, 209064.00)),
+ }
+ }
+
+
+
+
+ {
+ 'single': {
+ 'weekly': (
+ ( 163, 0.0400, 0.00),
+ ( 225, 0.0450, 6.54),
+ ( 267, 0.0525, 9.31),
+ ( 412, 0.0590, 11.54),
+ ( 1551, 0.0609, 20.04),
+ ( 1862, 0.0641, 89.42),
+ ( 2070, 0.0745, 109.35),
+ ( 3032, 0.0795, 124.88),
+ ( 4142, 0.0691, 201.33),
+ ( 5104, 0.0925, 278.06),
+ (20722, 0.0735, 367.00),
+ (21684, 0.5208, 1514.94),
+ ('inf', 0.0962, 2015.71),
+ ),
+ 'bi-weekly': (
+ ( 327, 0.0400, 0.00),
+ ( 450, 0.0450, 13.08),
+ ( 535, 0.0525, 18.62),
+ ( 823, 0.0590, 23.08),
+ ( 3102, 0.0609, 40.08),
+ ( 3723, 0.0641, 178.85),
+ ( 4140, 0.0745, 218.69),
+ ( 6063, 0.0795, 249.77),
+ ( 8285, 0.0691, 402.65),
+ (10208, 0.0925, 556.12),
+ (41444, 0.0735, 734.00),
+ (43367, 0.5208, 3029.88),
+ ('inf', 0.0962, 4031.42),
+ ),
+ 'semi-monthly': (
+ ( 354, 0.0400, 0.00),
+ ( 488, 0.0450, 14.17),
+ ( 579, 0.0525, 20.17),
+ ( 892, 0.0590, 25.00),
+ ( 3360, 0.0609, 43.42),
+ ( 4033, 0.0641, 193.75),
+ ( 4485, 0.0745, 236.92),
+ ( 6569, 0.0795, 270.58),
+ ( 8975, 0.0691, 436.21),
+ (11058, 0.0925, 602.46),
+ (44898, 0.0735, 795.17),
+ (46981, 0.5208, 3282.38),
+ ('inf', 0.0962, 4367.38),
+ ),
+ 'monthly': (
+ ( 708, 0.0400, 0.00),
+ ( 975, 0.0450, 28.33),
+ ( 1158, 0.0525, 40.33),
+ ( 1783, 0.0590, 50.00),
+ ( 6721, 0.0609, 86.83),
+ ( 8067, 0.0641, 387.50),
+ ( 8971, 0.0745, 473.83),
+ (13138, 0.0795, 541.17),
+ (17950, 0.0691, 872.42),
+ (22117, 0.0925, 1204.92),
+ (89796, 0.0735, 1590.33),
+ (93963, 0.5208, 6564.75),
+ ('inf', 0.0962, 8734.75),
+ ),
+ 'annually': (
+ ( 8500, 0.0400, 0.00),
+ ( 11700, 0.0450, 340.00),
+ ( 13900, 0.0525, 484.00),
+ ( 21400, 0.0590, 600.00),
+ ( 80650, 0.0609, 1042.00),
+ ( 96800, 0.0641, 4650.00),
+ ( 107650, 0.0745, 5686.00),
+ ( 157650, 0.0795, 6494.00),
+ ( 215400, 0.0691, 10469.00),
+ ( 265400, 0.0925, 14459.00),
+ (1077550, 0.0735, 19084.00),
+ (1127550, 0.5208, 78777.00),
+ ( 'inf', 0.0962, 104817.00),
+ ),
+ },
+ 'married': {
+ 'weekly': (
+ ( 163, 0.0400, 0.00),
+ ( 225, 0.0450, 6.54),
+ ( 267, 0.0525, 9.31),
+ ( 412, 0.0590, 11.54),
+ ( 1551, 0.0609, 20.04),
+ ( 1862, 0.0641, 89.42),
+ ( 2070, 0.0746, 109.35),
+ ( 3032, 0.0796, 124.90),
+ ( 4068, 0.0794, 201.44),
+ ( 6215, 0.0691, 283.75),
+ ( 7177, 0.1019, 432.12),
+ (20722, 0.0735, 530.10),
+ (41449, 0.0765, 1525.65),
+ (42411, 0.9454, 3111.27),
+ ('inf', 0.0962, 4020.31),
+ ),
+ 'bi-weekly': (
+ ( 327, 0.0400, 0.00),
+ ( 450, 0.0450, 13.08),
+ ( 535, 0.0525, 18.62),
+ ( 823, 0.0590, 23.08),
+ ( 3102, 0.0609, 40.08),
+ ( 3723, 0.0641, 178.85),
+ ( 4140, 0.0746, 218.69),
+ ( 6063, 0.0796, 249.81),
+ ( 8137, 0.0794, 402.88),
+ (12431, 0.0691, 567.50),
+ (14354, 0.1019, 864.23),
+ (41444, 0.0735, 1060.19),
+ (82898, 0.0765, 3051.31),
+ (84821, 0.9454, 6222.54),
+ ('inf', 0.0962, 8040.62),
+ ),
+ 'semi-monthly': (
+ ( 354, 0.0400, 0.00),
+ ( 488, 0.0450, 14.17),
+ ( 579, 0.0525, 20.17),
+ ( 892, 0.0590, 25.00),
+ ( 3360, 0.0609, 43.42),
+ ( 4033, 0.0641, 193.75),
+ ( 4485, 0.0746, 236.92),
+ ( 6569, 0.0796, 270.63),
+ ( 8815, 0.0794, 436.46),
+ (13467, 0.0691, 614.79),
+ (15550, 0.1019, 936.25),
+ (44898, 0.0735, 1148.54),
+ (89806, 0.0765, 3305.58),
+ (91890, 0.9454, 6741.08),
+ ('inf', 0.0962, 8710.67),
+ ),
+ 'monthly': (
+ ( 708, 0.0400, 0.00),
+ ( 975, 0.0450, 28.33),
+ ( 1158, 0.0525, 40.33),
+ ( 1783, 0.0590, 50.00),
+ ( 6721, 0.0609, 86.83),
+ ( 8067, 0.0641, 387.50),
+ ( 8971, 0.0746, 473.83),
+ ( 13138, 0.0796, 541.25),
+ ( 17629, 0.0794, 872.92),
+ ( 26933, 0.0691, 1229.58),
+ ( 31100, 0.1019, 1872.50),
+ ( 89796, 0.0735, 2297.08),
+ (179613, 0.0765, 6611.17),
+ (183779, 0.9454, 13482.17),
+ ( 'inf', 0.0962, 17421.33),
+ ),
+ 'annually': (
+ ( 8500, 0.0400, 0.00),
+ ( 11700, 0.0450, 340.00),
+ ( 13900, 0.0525, 484.00),
+ ( 21400, 0.0590, 600.00),
+ ( 80650, 0.0609, 1042.00),
+ ( 96800, 0.0641, 4650.00),
+ ( 107650, 0.0746, 5686.00),
+ ( 157650, 0.0796, 6495.00),
+ ( 211550, 0.0794, 10475.00),
+ ( 323200, 0.0691, 14755.00),
+ ( 373200, 0.1019, 22470.00),
+ (1077550, 0.0735, 27565.00),
+ (2155350, 0.0765, 79334.00),
+ (2205350, 0.9454, 161786.00),
+ ( 'inf', 0.0962, 209056.00),
+ ),
+ }
+ }
+
+
+
+
+
+
+ US NY New York Over 10 Exemption Rate
+ us_ny_sit_over_10_exemption_rate
+
+
+
+
+ {
+ 'weekly': (142.30, 152.90, 19.25),
+ 'bi-weekly': (284.60, 305.80, 38.50),
+ 'semi-monthly': (308.35, 331.25, 41.65),
+ 'monthly': (616.70, 662.50, 83.30),
+ 'annual': (7400, 7950, 1000),
+ }
+
+
+
+
+ {
+ 'weekly': (142.30, 152.90, 19.25),
+ 'bi-weekly': (284.60, 305.80, 38.50),
+ 'semi-monthly': (308.35, 331.25, 41.65),
+ 'monthly': (616.70, 662.50, 83.30),
+ 'annual': (7400, 7950, 1000),
+ }
+
+
+
+
+
+
+ US NY New York Deduction Exemption Rate
+ us_ny_sit_deduction_exemption_rate
+
+
+
+
+ {
+ 'single': {
+ 'weekly': (142.30, 161.55, 180.80, 200.05, 219.30, 238.55, 257.80, 277.05, 296.30, 315.55, 334.80),
+ 'bi-weekly': (284.60, 323.10, 361.60, 400.10, 438.60, 477.10, 515.60, 544.10, 592.60, 631.10, 669.60),
+ 'semi-monthly': (308.35, 350.0, 391.65, 433.30, 474.95, 516.60, 558.25, 599.90, 641.55, 683.20, 724.85),
+ 'monthly': (616.70, 700, 783.30, 866.60, 949.90, 1033.20, 1116.50, 1199.80, 1283.10, 1366.40, 1449.70),
+ 'annually': (7400, 8400, 9400, 10400, 11400, 12400, 13400, 14400, 15400, 16400, 17400),
+ },
+ 'married': {
+ 'weekly': (152.90, 172.15, 191.40, 210.65, 229.90, 249.15, 268.40, 287.65, 306.90, 326.15, 345.40),
+ 'bi-weekly': (305.80, 344.30, 382.80, 421.30, 459.80, 498.30, 536.80, 575.30, 613.80, 652.30, 690.80),
+ 'semi-monthly': (331.25, 372.90, 414.55, 456.20, 497.85, 539.50, 581.15, 622.80, 664.45, 706.10, 747.75),
+ 'monthly': (662.50, 745.80, 829.10, 912.40, 995.70, 1079.00, 1162.30, 1245.60, 1328.90, 1412.20, 1495.50),
+ 'annually': (7950, 8950, 9950, 10950, 11950, 12950, 13950, 14950, 15950, 16950, 17950),
+ },
+ }
+
+
+
+
+ {
+ 'single': {
+ 'weekly': (142.30, 161.55, 180.80, 200.05, 219.30, 238.55, 257.80, 277.05, 296.30, 315.55, 334.80),
+ 'bi-weekly': (284.60, 323.10, 361.60, 400.10, 438.60, 477.10, 515.60, 544.10, 592.60, 631.10, 669.60),
+ 'semi-monthly': (308.35, 350.0, 391.65, 433.30, 474.95, 516.60, 558.25, 599.90, 641.55, 683.20, 724.85),
+ 'monthly': (616.70, 700, 783.30, 866.60, 949.90, 1033.20, 1116.50, 1199.80, 1283.10, 1366.40, 1449.70),
+ 'annually': (7400, 8400, 9400, 10400, 11400, 12400, 13400, 14400, 15400, 16400, 17400),
+ },
+ 'married': {
+ 'weekly': (152.90, 172.15, 191.40, 210.65, 229.90, 249.15, 268.40, 287.65, 306.90, 326.15, 345.40),
+ 'bi-weekly': (305.80, 344.30, 382.80, 421.30, 459.80, 498.30, 536.80, 575.30, 613.80, 652.30, 690.80),
+ 'semi-monthly': (331.25, 372.90, 414.55, 456.20, 497.85, 539.50, 581.15, 622.80, 664.45, 706.10, 747.75),
+ 'monthly': (662.50, 745.80, 829.10, 912.40, 995.70, 1079.00, 1162.30, 1245.60, 1328.90, 1412.20, 1495.50),
+ 'annually': (7950, 8950, 9950, 10950, 11950, 12950, 13950, 14950, 15950, 16950, 17950),
+ },
+ }
+
+
+
+
+
+
+
+ US New York - Department of Taxation and Finance - Unemployment Tax
+
+
+
+ US New York - Department of Taxation and Finance - Re-employment Service Fund
+
+
+
+ US New York - Department of Taxation and Finance - Metropolitan Commuter Transportation Mobility Tax
+
+
+
+ US New York - Department of Taxation and Finance - Income Tax
+
+
+
+
+
+
+
+
+
+ ER: US NY New York State Unemployment
+ ER_US_NY_SUTA
+ python
+ result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_ny_suta_wage_base', rate='us_ny_suta_rate', state_code='NY')
+ code
+ result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_ny_suta_wage_base', rate='us_ny_suta_rate', state_code='NY')
+
+
+
+
+
+
+
+
+ ER: US NY New York State Re-employment Service Fund
+ ER_US_NY_SUTA_RSF
+ python
+ result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_ny_suta_wage_base', rate='us_ny_suta_rsf_rate', state_code='NY')
+ code
+ result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_ny_suta_wage_base', rate='us_ny_suta_rsf_rate', state_code='NY')
+
+
+
+
+
+
+
+
+ ER: US NY New York State Metropolitan Commuter Transportation Mobility Tax
+ ER_US_NY_SUTA_MCTMT
+ python
+ result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_ny_suta_wage_base', rate='us_ny_suta_mctmt_rate', state_code='NY')
+ code
+ result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_ny_suta_wage_base', rate='us_ny_suta_mctmt_rate', state_code='NY')
+
+
+
+
+
+
+
+
+ EE: US NY New York State Income Tax Withholding
+ EE_US_NY_SIT
+ python
+ result, _ = ny_new_york_state_income_withholding(payslip, categories, worked_days, inputs)
+ code
+ result, result_rate = ny_new_york_state_income_withholding(payslip, categories, worked_days, inputs)
+
+
+
+
+
diff --git a/l10n_us_hr_payroll/data/state/oh_ohio.xml b/l10n_us_hr_payroll/data/state/oh_ohio.xml
new file mode 100644
index 00000000..e6db8eb8
--- /dev/null
+++ b/l10n_us_hr_payroll/data/state/oh_ohio.xml
@@ -0,0 +1,157 @@
+
+
+
+
+ US OH Ohio SUTA Wage Base
+ us_oh_suta_wage_base
+
+
+
+
+ 9500.00
+
+
+
+
+ 9000.00
+
+
+
+
+
+
+
+ US OH Ohio SUTA Rate
+ us_oh_suta_rate
+
+
+
+
+ 2.7
+
+
+
+
+ 2.7
+
+
+
+
+
+
+ US OH Ohio SIT Rate Table
+ us_oh_sit_rate
+
+
+
+
+
+
+ [
+ ( 5000.00, 0.0, 0.005),
+ ( 10000.00, 25.0, 0.010),
+ ( 15000.00, 75.0, 0.020),
+ ( 20000.00, 175.0, 0.025),
+ ( 40000.00, 300.0, 0.030),
+ ( 80000.00, 900.0, 0.035),
+ ( 100000.00, 2300.0, 0.040),
+ ( 'inf', 3100.0, 0.050),
+ ]
+
+
+
+
+
+
+ [
+ ( 5000.00, 0.0, 0.005),
+ ( 10000.00, 25.0, 0.010),
+ ( 15000.00, 75.0, 0.020),
+ ( 20000.00, 175.0, 0.025),
+ ( 40000.00, 300.0, 0.030),
+ ( 80000.00, 900.0, 0.035),
+ ( 100000.00, 2300.0, 0.040),
+ ( 'inf', 3100.0, 0.050),
+ ]
+
+
+
+
+
+
+ US OH Ohio SIT Exemption Rate
+ us_oh_sit_exemption_rate
+
+
+
+
+ 650.0
+
+
+
+
+ 650.0
+
+
+
+
+
+
+ US OH Ohio SIT Multiplier Value
+ us_oh_sit_multiplier
+
+
+
+
+ 1.075
+
+
+
+
+ 1.032
+
+
+
+
+
+
+
+ US Ohio - OBG - Unemployment
+
+
+
+ US Ohio - OBG - Income Withholding
+
+
+
+
+
+
+
+
+
+ ER: US OH Ohio State Unemployment (JFS-20125)
+ ER_US_OH_SUTA
+ python
+ result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_oh_suta_wage_base', rate='us_oh_suta_rate', state_code='OH')
+ code
+ result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_oh_suta_wage_base', rate='us_oh_suta_rate', state_code='OH')
+
+
+
+
+
+
+
+
+ EE: US OH Ohio State Income Tax Withholding (IT 501)
+ EE_US_OH_SIT
+ python
+ result, _ = oh_ohio_state_income_withholding(payslip, categories, worked_days, inputs)
+ code
+ result, result_rate = oh_ohio_state_income_withholding(payslip, categories, worked_days, inputs)
+
+
+
+
+
\ No newline at end of file
diff --git a/l10n_us_hr_payroll/data/state/ok_oklahoma.xml b/l10n_us_hr_payroll/data/state/ok_oklahoma.xml
new file mode 100644
index 00000000..04aa8380
--- /dev/null
+++ b/l10n_us_hr_payroll/data/state/ok_oklahoma.xml
@@ -0,0 +1,301 @@
+
+
+
+
+ US OK Oklahoma SUTA Wage Base
+ us_ok_suta_wage_base
+
+
+
+
+ 18700.0
+
+
+
+
+
+
+
+ US OK Oklahoma SUTA Rate
+ us_ok_suta_rate
+
+
+
+
+ 1.5
+
+
+
+
+
+
+ US OK Oklahoma Allowances Rate
+ us_ok_sit_allowances_rate
+
+
+
+
+ {
+ 'weekly' : 19.23,
+ 'bi-weekly' : 38.46,
+ 'semi-monthly': 41.67,
+ 'monthly' : 83.33,
+ 'quarterly' : 250.00,
+ 'semi-annual': 500.00,
+ 'annually': 1000.00,
+ }
+
+
+
+
+
+
+ US OK Oklahoma SIT Tax Rate
+ us_ok_sit_tax_rate
+
+
+
+
+ {
+ 'single': {
+ 'weekly': (
+ ( 122, 0.00, 0.00),
+ ( 141, 0.50, 0.00),
+ ( 170, 1.00, 0.10),
+ ( 194, 2.00, 0.38),
+ ( 216, 3.00, 0.87),
+ ( 261, 4.00, 1.53),
+ ('inf', 5.00, 3.30),
+ ),
+ 'bi-weekly': (
+ ( 244, 0.00, 0.00),
+ ( 283, 0.50, 0.00),
+ ( 340, 1.00, 0.19),
+ ( 388, 2.00, 0.77),
+ ( 433, 3.00, 1.73),
+ ( 521, 4.00, 3.06),
+ ('inf', 5.00, 6.60),
+ ),
+ 'semi-monthly': (
+ ( 265, 0.00, 0.00),
+ ( 306, 0.50, 0.00),
+ ( 369, 1.00, 0.21),
+ ( 421, 2.00, 0.83),
+ ( 469, 3.00, 1.88),
+ ( 565, 4.00, 3.31),
+ ('inf', 5.00, 7.15),
+ ),
+ 'monthly': (
+ ( 529, 0.00, 0.00),
+ ( 613, 0.50, 0.00),
+ ( 738, 1.00, 0.42),
+ ( 842, 2.00, 1.67),
+ ( 938, 3.00, 3.75),
+ (1129, 4.00, 6.63),
+ ('inf', 5.00, 14.29),
+ ),
+ 'quarterly': (
+ ( 1588, 0.00, 0.00),
+ ( 1838, 0.50, 0.00),
+ ( 2213, 1.00, 1.25),
+ ( 2525, 2.00, 5.00),
+ ( 2813, 3.00, 11.25),
+ ( 3388, 4.00, 19.88),
+ ('inf', 5.00, 42.88),
+ ),
+ 'semi-annual': (
+ ( 3175, 0.00, 0.00),
+ ( 3675, 0.50, 0.00),
+ ( 4425, 1.00, 2.50),
+ ( 5050, 2.00, 10.00),
+ (5625, 3.00, 22.50),
+ ( 6775, 4.00, 39.75),
+ ('inf', 5.00, 85.75),
+ ),
+ 'annually': (
+ ( 6350, 0.00, 0.00),
+ ( 7350, 0.50, 0.00),
+ ( 8850, 1.00, 5.00),
+ (10100, 2.00, 20.00),
+ (11250, 3.00, 45.00),
+ (13550, 4.00, 79.50),
+ ('inf', 5.00, 171.50),
+ ),
+ },
+ 'married': {
+ 'weekly': (
+ ( 244, 0.00, 0.00),
+ ( 283, 0.50, 0.00),
+ ( 340, 1.00, 0.19),
+ ( 388, 2.00, 0.77),
+ ( 433, 3.00, 1.73),
+ ( 479, 4.00, 3.06),
+ ('inf', 5.00, 4.90),
+ ),
+ 'bi-weekly': (
+ ( 488, 0.00, 0.00),
+ ( 565, 0.50, 0.00),
+ ( 681, 1.00, 0.38),
+ ( 777, 2.00, 1.54),
+ ( 865, 3.00, 3.46),
+ ( 958, 4.00, 6.12),
+ ('inf', 5.00, 9.81),
+ ),
+ 'semi-monthly': (
+ ( 529, 0.00, 0.00),
+ ( 613, 0.50, 0.00),
+ ( 738, 1.00, 0.42),
+ ( 842, 2.00, 1.67),
+ ( 938, 3.00, 3.75),
+ ( 1038, 4.00, 6.63),
+ ('inf', 5.00, 10.63),
+ ),
+ 'monthly': (
+ ( 1058, 0.00, 0.00),
+ ( 1225, 0.50, 0.00),
+ ( 1475, 1.00, 0.83),
+ ( 1683, 2.00, 3.33),
+ ( 1875, 3.00, 7.50),
+ ( 2075, 4.00, 13.25),
+ ('inf', 5.00, 21.25),
+ ),
+ 'quarterly': (
+ ( 3175, 0.00, 0.00),
+ ( 3675, 0.50, 0.00),
+ ( 4425, 1.00, 2.50),
+ ( 5050, 2.00, 10.00),
+ ( 5625, 3.00, 22.50),
+ ( 6225, 4.00, 39.75),
+ ('inf', 5.00, 63.75),
+ ),
+ 'semi-annual': (
+ ( 6350, 0.00, 0.00),
+ ( 7350, 0.50, 0.00),
+ ( 8850, 1.00, 5.00),
+ ( 10100, 2.00, 20.00),
+ ( 11250, 3.00, 45.00),
+ ( 12450, 4.00, 79.50),
+ ( 'inf', 5.00, 127.50),
+ ),
+ 'annually': (
+ ( 12700, 0.00, 0.00),
+ ( 14700, 0.50, 0.00),
+ ( 17700, 1.00, 10.00),
+ ( 20200, 2.00, 40.00),
+ ( 22500, 3.00, 90.00),
+ ( 24900, 4.00, 159.00),
+ ( 'inf', 5.00, 255.00),
+ ),
+ },
+ 'head_household': {
+ 'weekly': (
+ ( 122, 0.00, 0.00),
+ ( 141, 0.50, 0.00),
+ ( 170, 1.00, 0.10),
+ ( 194, 2.00, 0.38),
+ ( 216, 3.00, 0.87),
+ ( 261, 4.00, 1.53),
+ ('inf', 5.00, 3.30),
+ ),
+ 'bi-weekly': (
+ ( 244, 0.00, 0.00),
+ ( 283, 0.50, 0.00),
+ ( 340, 1.00, 0.19),
+ ( 388, 2.00, 0.77),
+ ( 433, 3.00, 1.73),
+ ( 521, 4.00, 3.06),
+ ('inf', 5.00, 6.60),
+ ),
+ 'semi-monthly': (
+ ( 265, 0.00, 0.00),
+ ( 306, 0.50, 0.00),
+ ( 369, 1.00, 0.21),
+ ( 421, 2.00, 0.83),
+ ( 469, 3.00, 1.88),
+ ( 565, 4.00, 3.31),
+ ('inf', 5.00, 7.15),
+ ),
+ 'monthly': (
+ ( 529, 0.00, 0.00),
+ ( 613, 0.50, 0.00),
+ ( 738, 1.00, 0.42),
+ ( 842, 2.00, 1.67),
+ ( 938, 3.00, 3.75),
+ ( 1129, 4.00, 6.63),
+ ('inf', 5.00, 14.29),
+ ),
+ 'quarterly': (
+ ( 1588, 0.00, 0.00),
+ ( 1838, 0.50, 0.00),
+ ( 2213, 1.00, 1.25),
+ ( 2525, 2.00, 5.00),
+ ( 2813, 3.00, 11.25),
+ ( 3388, 4.00, 19.88),
+ ('inf', 5.00, 42.88),
+ ),
+ 'semi-annual': (
+ ( 3175, 0.00, 0.00),
+ ( 3675, 0.50, 0.00),
+ ( 4425, 1.00, 2.50),
+ ( 5050, 2.00, 10.00),
+ ( 5625, 3.00, 22.50),
+ ( 6775, 4.00, 39.75),
+ ('inf', 5.00, 85.75),
+ ),
+ 'annually': (
+ ( 6350, 0.00, 0.00),
+ ( 7350, 0.50, 0.00),
+ ( 8850, 1.00, 5.00),
+ (10100, 2.00, 20.00),
+ (11250, 3.00, 45.00),
+ (13550, 4.00, 79.50),
+ ('inf', 5.00, 171.50),
+ ),
+ },
+ }
+
+
+
+
+
+
+
+ US Oklahoma - Employment Security Commission - Unemployment Tax
+
+
+
+ US Oklahoma - Tax Commission - Income Tax
+
+
+
+
+
+
+
+
+
+ ER: US OK Oklahoma State Unemployment
+ ER_US_OK_SUTA
+ python
+ result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_ok_suta_wage_base', rate='us_ok_suta_rate', state_code='OK')
+ code
+ result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_ok_suta_wage_base', rate='us_ok_suta_rate', state_code='OK')
+
+
+
+
+
+
+
+
+ EE: US OK Oklahoma State Income Tax Withholding
+ EE_US_OK_SIT
+ python
+ result, _ = ok_oklahoma_state_income_withholding(payslip, categories, worked_days, inputs)
+ code
+ result, result_rate = ok_oklahoma_state_income_withholding(payslip, categories, worked_days, inputs)
+
+
+
+
+
\ No newline at end of file
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..f46a92b4
--- /dev/null
+++ b/l10n_us_hr_payroll/data/state/pa_pennsylvania.xml
@@ -0,0 +1,131 @@
+
+
+
+
+ US PA Pennsylvania SUTA Wage Base (ER)
+ us_pa_suta_wage_base
+
+
+
+
+ 10000.00
+
+
+
+
+ 10000.00
+
+
+
+
+
+
+
+ US PA Pennsylvania SUTA Rate
+ us_pa_suta_rate
+
+
+
+
+ 3.6890
+
+
+
+
+ 3.6890
+
+
+
+
+
+
+ US PA Pennsylvania SUTA Employee Rate
+ us_pa_suta_ee_rate
+
+
+
+
+ 0.06
+
+
+
+
+ 0.06
+
+
+
+
+
+
+ US PA Pennsylvania SIT Rate
+ us_pa_sit_rate
+
+
+
+
+ 3.07
+
+
+
+
+ 3.07
+
+
+
+
+
+
+
+ US Pennsylvania - Department of Revenue - Unemployment 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/data/state/ri_rhode_island.xml b/l10n_us_hr_payroll/data/state/ri_rhode_island.xml
new file mode 100644
index 00000000..155a2c86
--- /dev/null
+++ b/l10n_us_hr_payroll/data/state/ri_rhode_island.xml
@@ -0,0 +1,141 @@
+
+
+
+
+ US RI Rhode Island SUTA Wage Base
+ us_ri_suta_wage_base
+
+
+
+
+ 24000.0
+
+
+
+
+
+
+
+ US RI Rhode Island SUTA Rate
+ us_ri_suta_rate
+
+
+
+
+ 1.06
+
+
+
+
+
+
+ US RI Rhode Island Exemption Rate
+ us_ri_sit_exemption_rate
+
+
+
+
+ {
+ 'weekly' : (( 0.00, 19.23), ( 4451.92, 0.00)),
+ 'bi-weekly' : (( 0.00, 38.46), ( 8903.85, 0.00)),
+ 'semi-monthly': (( 0.00, 41.67), ( 9645.83, 0.00)),
+ 'monthly' : (( 0.00, 83.33), ( 19291.67, 0.00)),
+ 'quarterly' : (( 0.00, 250.00), ( 57875.00, 0.00)),
+ 'semi-annually': (( 0.00, 500.00), ( 115750.00, 0.00)),
+ 'annually': (( 0.00, 1000.0), ( 231500.00, 0000)),
+ }
+
+
+
+
+
+
+ US RI Rhode Island SIT Tax Rate
+ us_ri_sit_tax_rate
+
+
+
+
+ {
+ 'weekly': (
+ ( 1255, 0.00, 3.75),
+ ( 2853, 47.06, 4.75),
+ ('inf', 122.97, 5.99),
+ ),
+ 'bi-weekly': (
+ ( 2510, 0.00, 3.75),
+ ( 5706, 94.13, 4.75),
+ ('inf', 245.94, 5.99),
+ ),
+ 'semi-monthly': (
+ ( 2719, 0.00, 3.75),
+ ( 6181, 101.96, 4.75),
+ ('inf', 266.41, 5.99),
+ ),
+ 'monthly': (
+ ( 5438, 0.00, 3.75),
+ (12363, 203.93, 4.75),
+ ('inf', 532.87, 5.99),
+ ),
+ 'quarterly': (
+ (16313, 0.00, 3.75),
+ (37088, 611.74, 4.75),
+ ('inf', 1598.55, 5.99),
+ ),
+ 'semi-annually': (
+ (32625, 0.00, 3.75),
+ (74175, 1223.44, 4.75),
+ ('inf', 3197.07, 5.99),
+ ),
+ 'annually': (
+ ( 65250, 0.00, 3.75),
+ (148350, 2446.88, 4.75),
+ ( 'inf', 6394.13, 5.99),
+ ),
+ }
+
+
+
+
+
+
+
+ US Rhode Island - Department of Labor and Training - Unemployment Tax
+
+
+
+ US Rhode Island - Division of Taxations - Income Tax
+
+
+
+
+
+
+
+
+
+ ER: US RI Rhode Island State Unemployment
+ ER_US_RI_SUTA
+ python
+ result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_ri_suta_wage_base', rate='us_ri_suta_rate', state_code='RI')
+ code
+ result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_ri_suta_wage_base', rate='us_ri_suta_rate', state_code='RI')
+
+
+
+
+
+
+
+
+ EE: US RI Rhode Island State Income Tax Withholding
+ EE_US_RI_SIT
+ python
+ result, _ = ri_rhode_island_state_income_withholding(payslip, categories, worked_days, inputs)
+ code
+ result, result_rate = ri_rhode_island_state_income_withholding(payslip, categories, worked_days, inputs)
+
+
+
+
+
\ No newline at end of file
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..b2a46192
--- /dev/null
+++ b/l10n_us_hr_payroll/data/state/sc_south_carolina.xml
@@ -0,0 +1,151 @@
+
+
+
+
+ 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
+
+
+
+
+
+ 0.55
+
+
+
+
+
+
+ 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/data/state/sd_south_dakota.xml b/l10n_us_hr_payroll/data/state/sd_south_dakota.xml
new file mode 100644
index 00000000..95165ae5
--- /dev/null
+++ b/l10n_us_hr_payroll/data/state/sd_south_dakota.xml
@@ -0,0 +1,51 @@
+
+
+
+
+ US SD South Dakota SUTA Wage Base
+ us_sd_suta_wage_base
+
+
+
+
+ 15000.00
+
+
+
+
+
+
+
+ US SD South Dakota SUTA Rate
+ us_sd_suta_rate
+
+
+
+
+ 1.75
+
+
+
+
+
+
+
+ US South Dakota - Department of Labor - Unemployment Tax
+
+
+
+
+
+
+
+ ER: US SD South Dakota State Unemployment
+ ER_US_SD_SUTA
+ python
+ result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_sd_suta_wage_base', rate='us_sd_suta_rate', state_code='SD')
+ code
+ result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_sd_suta_wage_base', rate='us_sd_suta_rate', state_code='SD')
+
+
+
+
+
diff --git a/l10n_us_hr_payroll/data/state/tn_tennessee.xml b/l10n_us_hr_payroll/data/state/tn_tennessee.xml
new file mode 100644
index 00000000..31465dee
--- /dev/null
+++ b/l10n_us_hr_payroll/data/state/tn_tennessee.xml
@@ -0,0 +1,51 @@
+
+
+
+
+ US TN Tennessee SUTA Wage Base
+ us_tn_suta_wage_base
+
+
+
+
+ 7000.00
+
+
+
+
+
+
+
+ US TN Tennessee SUTA Rate
+ us_tn_suta_rate
+
+
+
+
+ 2.7
+
+
+
+
+
+
+
+ US Tennessee - Department of Revenue - Unemployment Tax
+
+
+
+
+
+
+
+ ER: US TN Tennessee State Unemployment
+ ER_US_TN_SUTA
+ python
+ result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_tn_suta_wage_base', rate='us_tn_suta_rate', state_code='TN')
+ code
+ result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_tn_suta_wage_base', rate='us_tn_suta_rate', state_code='TN')
+
+
+
+
+
diff --git a/l10n_us_hr_payroll/data/state/tx_texas.xml b/l10n_us_hr_payroll/data/state/tx_texas.xml
new file mode 100644
index 00000000..5d9c5772
--- /dev/null
+++ b/l10n_us_hr_payroll/data/state/tx_texas.xml
@@ -0,0 +1,127 @@
+
+
+
+
+ US TX Texas SUTA Wage Base
+ us_tx_suta_wage_base
+
+
+
+
+ 9000.0
+
+
+
+
+ 9000.0
+
+
+
+
+
+
+
+ US TX Texas SUTA Rate
+ us_tx_suta_rate
+
+
+
+
+ 2.7
+
+
+
+
+ 2.7
+
+
+
+
+
+
+ US TX Texas Obligation Assessment Rate
+ us_tx_suta_oa_rate
+
+
+
+
+ 0.0
+
+
+
+
+ 0.0
+
+
+
+
+
+
+ US TX Texas Employment & Training Investment Assessment Rate
+ us_tx_suta_etia_rate
+
+
+
+
+ 0.1
+
+
+
+
+ 0.1
+
+
+
+
+
+
+
+ US Texas - Workforce Commission (Unemployment)
+
+
+
+
+
+
+
+
+
+ ER: US TX Texas State Unemployment (C-3)
+ ER_US_TX_SUTA
+ python
+ result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_tx_suta_wage_base', rate='us_tx_suta_rate', state_code='TX')
+ code
+ result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_tx_suta_wage_base', rate='us_tx_suta_rate', state_code='TX')
+
+
+
+
+
+
+
+
+ ER: US TX Texas Obligation Assessment (C-3)
+ ER_US_TX_SUTA_OA
+ python
+ result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_tx_suta_wage_base', rate='us_tx_suta_oa_rate', state_code='TX')
+ code
+ result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_tx_suta_wage_base', rate='us_tx_suta_oa_rate', state_code='TX')
+
+
+
+
+
+
+
+
+ ER: US TX Texas Employment & Training Investment Assessment (C-3)
+ ER_US_TX_SUTA_ETIA
+ python
+ result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_tx_suta_wage_base', rate='us_tx_suta_etia_rate', state_code='TX')
+ code
+ result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_tx_suta_wage_base', rate='us_tx_suta_etia_rate', state_code='TX')
+
+
+
+
+
\ No newline at end of file
diff --git a/l10n_us_hr_payroll/data/state/ut_utah.xml b/l10n_us_hr_payroll/data/state/ut_utah.xml
new file mode 100644
index 00000000..cb57813c
--- /dev/null
+++ b/l10n_us_hr_payroll/data/state/ut_utah.xml
@@ -0,0 +1,166 @@
+
+
+
+
+ US UT Utah SUTA Wage Base
+ us_ut_suta_wage_base
+
+
+
+
+ 36600.0
+
+
+
+
+
+
+
+ US UT Utah SUTA Rate
+ us_ut_suta_rate
+
+
+
+
+ 0.1
+
+
+
+
+
+
+ US UT Utah TAX Rate
+ us_ut_tax_rate
+
+
+
+
+ 0.0495
+
+
+
+
+
+
+ US UT Utah Allowances Rate
+ us_ut_sit_allowances_rate
+
+
+
+
+ {
+ 'single': {
+ 'weekly' : 7,
+ 'bi-weekly' : 14,
+ 'semi-monthly': 15,
+ 'monthly' : 30,
+ 'quarterly' : 90,
+ 'semi-annual': 180,
+ 'annually': 360,
+ },
+ 'married': {
+ 'weekly' : 14,
+ 'bi-weekly' : 28,
+ 'semi-monthly': 30,
+ 'monthly' : 60,
+ 'quarterly' : 180,
+ 'semi-annual': 360,
+ 'annually': 720,
+ },
+ 'head_household': {
+ 'weekly' : 7,
+ 'bi-weekly' : 14,
+ 'semi-monthly': 15,
+ 'monthly' : 30,
+ 'quarterly' : 90,
+ 'semi-annual': 180,
+ 'annually': 360,
+ },
+ }
+
+
+
+
+
+
+ US UT Utah SIT Tax Rate
+ us_ut_sit_tax_rate
+
+
+
+
+ {
+ 'single': {
+ 'weekly': ((137, 1.3)),
+ 'bi-weekly': ((274, 1.3)),
+ 'semi-monthly': ((297, 1.3)),
+ 'monthly': ((594, 1.3)),
+ 'quarterly': ((1782, 1.3)),
+ 'semi-annual': ((3564, 1.3)),
+ 'annually': ((7128, 1.3)),
+ },
+ 'married': {
+ 'weekly': ((274, 1.3)),
+ 'bi-weekly': (548, 1.3),
+ 'semi-monthly': ((594, 1.3)),
+ 'monthly': ((1188, 1.3)),
+ 'quarterly': ((3564, 1.3)),
+ 'semi-annual': ((7128, 1.3)),
+ 'annually': ((14256, 1.3)),
+ },
+ 'head_household': {
+ 'weekly': ((137, 1.3)),
+ 'bi-weekly': ((274, 1.3)),
+ 'semi-monthly': ((297, 1.3)),
+ 'monthly': ((594, 1.3)),
+ 'quarterly': ((1782, 1.3)),
+ 'semi-annual': ((3564, 1.3)),
+ 'annually': ((7128, 1.3)),
+ },
+ }
+
+
+
+
+
+
+
+ US Utah - Employment Security Commission - Unemployment Tax
+
+
+
+ US Utah - Tax Commission - Income Tax
+
+
+
+
+
+
+
+
+
+ ER: US UT Utah State Unemployment
+ ER_US_UT_SUTA
+ python
+ result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_ut_suta_wage_base', rate='us_ut_suta_rate', state_code='UT')
+ code
+ result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_ut_suta_wage_base', rate='us_ut_suta_rate', state_code='UT')
+
+
+
+
+
+
+
+
+ EE: US UT Utah State Income Tax Withholding
+ EE_US_UT_SIT
+ python
+ result, _ = ut_utah_state_income_withholding(payslip, categories, worked_days, inputs)
+ code
+ result, result_rate = ut_utah_state_income_withholding(payslip, categories, worked_days, inputs)
+
+
+
+
+
\ No newline at end of file
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..5d19384a
--- /dev/null
+++ b/l10n_us_hr_payroll/data/state/va_virginia.xml
@@ -0,0 +1,138 @@
+
+
+
+
+ US VA Virginia SUTA Wage Base
+ us_va_suta_wage_base
+
+
+
+
+ 8000.0
+
+
+
+
+ 8000.0
+
+
+
+
+
+
+
+ US VA Virginia SUTA Rate
+ us_va_suta_rate
+
+
+
+
+ 2.51
+
+
+
+
+ 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 - 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/data/state/vt_vermont.xml b/l10n_us_hr_payroll/data/state/vt_vermont.xml
new file mode 100644
index 00000000..74f1a491
--- /dev/null
+++ b/l10n_us_hr_payroll/data/state/vt_vermont.xml
@@ -0,0 +1,193 @@
+
+
+
+
+ US VT Vermont SUTA Wage Base
+ us_vt_suta_wage_base
+
+
+
+
+ 16100.0
+
+
+
+
+
+
+
+ US VT Vermont SUTA Rate
+ us_vt_suta_rate
+
+
+
+
+ 1.0
+
+
+
+
+
+
+ US VT Vermont Allowances Rate
+ us_vt_sit_allowances_rate
+
+
+
+
+ {
+ 'weekly' : 83.65,
+ 'bi-weekly' : 167.31,
+ 'semi-monthly': 181.25,
+ 'monthly' : 362.50,
+ 'quarterly' : 1087.50,
+ 'annually': 4350.00,
+ }
+
+
+
+
+
+
+ US VT Vermont SIT Tax Rate
+ us_vt_sit_tax_rate
+
+
+
+
+ {
+ 'single': {
+ 'weekly': (
+ ( 60, 0.00, 0.00),
+ ( 836, 0.00, 3.35),
+ ( 1941, 26.00, 6.60),
+ ( 3983, 98.93, 7.60),
+ ('inf', 254.12, 8.75),
+ ),
+ 'bi-weekly': (
+ ( 120, 0.00, 0.00),
+ ( 1672, 0.00, 3.35),
+ ( 3882, 51.99, 6.60),
+ ( 7966, 197.85, 7.60),
+ ('inf', 508.24, 8.75),
+ ),
+ 'semi-monthly': (
+ ( 130, 0.00, 0.00),
+ ( 1811, 0.00, 3.35),
+ ( 4205, 56.31, 6.60),
+ ( 8630, 214.32, 7.60),
+ ('inf', 550.62, 8.75),
+ ),
+ 'monthly': (
+ ( 260, 0.00, 0.00),
+ ( 3623, 0.00, 3.35),
+ ( 8410, 112.66, 6.60),
+ (17260, 428.60, 7.60),
+ ('inf', 1101.20, 8.75),
+ ),
+ 'quarterly': (
+ ( 781, 0.00, 0.00),
+ (10869, 0.00, 3.35),
+ (25231, 337.95, 6.60),
+ (51781, 1285.84, 7.60),
+ ('inf', 3303.64, 8.75),
+ ),
+ 'annually': (
+ ( 3125, 0.00, 0.00),
+ ( 43475, 0.00, 3.35),
+ (100925, 1351.73, 6.60),
+ (207125, 5143.43, 7.60),
+ ( 'inf', 13214.63, 8.75),
+ ),
+ },
+ 'married': {
+ 'weekly': (
+ ( 180, 0.00, 0.00),
+ ( 1477, 0.00, 3.35),
+ ( 3315, 43.45, 6.60),
+ ( 4956, 164.76, 7.60),
+ ('inf', 289.47, 8.75),
+ ),
+ 'bi-weekly': (
+ ( 361, 0.00, 0.00),
+ ( 2955, 0.00, 3.35),
+ ( 6630, 86.90, 6.60),
+ (9913, 329.45, 7.60),
+ ('inf', 578.96, 8.75),
+ ),
+ 'semi-monthly': (
+ ( 391, 0.00, 0.00),
+ ( 3201, 0.00, 3.35),
+ ( 7182, 94.14, 6.60),
+ (10739, 356.88, 7.60),
+ ('inf', 627.21, 8.75),
+ ),
+ 'monthly': (
+ ( 781, 0.00, 0.00),
+ ( 6402, 0.00, 3.35),
+ (14365, 188.30, 6.60),
+ (21477, 713.86, 7.60),
+ ('inf', 1254.37, 8.75),
+ ),
+ 'quarterly': (
+ ( 2344, 0.00, 0.00),
+ (19206, 0.00, 3.35),
+ (43094, 564.88, 6.60),
+ (64431, 2141.49, 7.60),
+ ('inf', 3763.10, 8.75),
+ ),
+ 'annually': (
+ ( 9375, 0.00, 0.00),
+ ( 76825, 0.00, 3.35),
+ (172375, 2259.58, 6.60),
+ (257725, 8565.88, 7.60),
+ ( 'inf', 15052.48, 8.75),
+ ),
+ },
+ }
+
+
+
+
+
+
+
+ US Vermont - Employment Security Commission - Unemployment Tax
+
+
+
+ US Vermont - Tax Commission - Income Tax
+
+
+
+
+
+
+
+
+
+ ER: US VT Vermont State Unemployment
+ ER_US_VT_SUTA
+ python
+ result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_vt_suta_wage_base', rate='us_vt_suta_rate', state_code='VT')
+ code
+ result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_vt_suta_wage_base', rate='us_vt_suta_rate', state_code='VT')
+
+
+
+
+
+
+
+
+ EE: US VT Vermont State Income Tax Withholding
+ EE_US_VT_SIT
+ python
+ result, _ = vt_vermont_state_income_withholding(payslip, categories, worked_days, inputs)
+ code
+ result, result_rate = vt_vermont_state_income_withholding(payslip, categories, worked_days, inputs)
+
+
+
+
+
\ No newline at end of file
diff --git a/l10n_us_hr_payroll/data/state/wa_washington.xml b/l10n_us_hr_payroll/data/state/wa_washington.xml
new file mode 100644
index 00000000..3307689c
--- /dev/null
+++ b/l10n_us_hr_payroll/data/state/wa_washington.xml
@@ -0,0 +1,200 @@
+
+
+
+
+ US WA Washington SUTA Wage Base
+ us_wa_suta_wage_base
+
+
+
+
+ 49800.0
+
+
+
+
+ 52700.00
+
+
+
+
+
+
+ US WA Washington FML Wage Base
+ us_wa_fml_wage_base
+
+
+
+
+ 132900.00
+
+
+
+
+ 137700.00
+
+
+
+
+
+
+
+ US WA Washington SUTA Rate
+ us_wa_suta_rate
+
+
+
+
+ 1.18
+
+
+
+
+ 1.0
+
+
+
+
+
+
+ US WA Washington FML Rate (Total)
+ us_wa_fml_rate
+
+
+
+
+ 0.4
+
+
+
+
+ 0.4
+
+
+
+
+
+
+ US WA Washington FML Rate (Employee)
+ us_wa_fml_rate_ee
+
+
+
+
+ 66.33
+
+
+
+
+ 66.33
+
+
+
+
+
+
+ US WA Washington FML Rate (Employer)
+ us_wa_fml_rate_er
+
+
+
+
+ 33.67
+
+
+
+
+ 33.67
+
+
+
+
+
+
+
+ US Washington - Employment Security Department (Unemployment)
+
+
+
+ US Washington - Department of Labor & Industries
+
+
+
+ US Washington - Employment Security Department (PFML)
+
+
+
+
+
+
+
+
+
+ ER: US WA Washington State Unemployment (5208A/B)
+ ER_US_WA_SUTA
+ python
+ result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_wa_suta_wage_base', rate='us_wa_suta_rate', state_code='WA')
+ code
+ result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_wa_suta_wage_base', rate='us_wa_suta_rate', state_code='WA')
+
+
+
+
+
+
+
+
+ ER: US WA Washington State Family Medical Leave
+ ER_US_WA_FML
+ python
+ result, _ = wa_washington_fml_er(payslip, categories, worked_days, inputs)
+ code
+ result, result_rate = wa_washington_fml_er(payslip, categories, worked_days, inputs)
+
+
+
+
+
+
+
+
+ EE: US WA Washington State Family Medical Leave
+ EE_US_WA_FML
+ python
+ result, _ = wa_washington_fml_ee(payslip, categories, worked_days, inputs)
+ code
+ result, result_rate = wa_washington_fml_ee(payslip, categories, worked_days, inputs)
+
+
+
+
+
+
+
+
+
+ ER: US WA Washington State LNI
+ ER_US_WA_LNI
+ python
+ result = is_us_state(payslip, 'WA') and payslip.contract_id.us_payroll_config_value('workers_comp_ee_code') and worked_days.WORK100 and worked_days.WORK100.number_of_hours and payslip.rule_parameter(payslip.contract_id.us_payroll_config_value('workers_comp_er_code'))
+ code
+ result, result_rate = worked_days.WORK100.number_of_hours, -payslip.rule_parameter(payslip.contract_id.us_payroll_config_value('workers_comp_er_code'))
+
+
+
+
+
+
+
+
+ EE: US WA Washington State LNI
+ EE_US_WA_LNI
+ python
+ result = is_us_state(payslip, 'WA') and payslip.contract_id.us_payroll_config_value('workers_comp_ee_code') and worked_days.WORK100 and worked_days.WORK100.number_of_hours and payslip.rule_parameter(payslip.contract_id.us_payroll_config_value('workers_comp_ee_code'))
+ code
+ result, result_rate = worked_days.WORK100.number_of_hours, -payslip.rule_parameter(payslip.contract_id.us_payroll_config_value('workers_comp_ee_code'))
+
+
+
+
+
\ No newline at end of file
diff --git a/l10n_us_hr_payroll/data/state/wi_wisconsin.xml b/l10n_us_hr_payroll/data/state/wi_wisconsin.xml
new file mode 100644
index 00000000..b62dc3f4
--- /dev/null
+++ b/l10n_us_hr_payroll/data/state/wi_wisconsin.xml
@@ -0,0 +1,118 @@
+
+
+
+
+ US WI Wisconsin SUTA Wage Base
+ us_wi_suta_wage_base
+
+
+
+
+ 14000.00
+
+
+
+
+
+
+
+
+ US WI Wisconsin SUTA Rate
+ us_wi_suta_rate
+
+
+
+
+ 3.05
+
+
+
+
+
+
+ US WI Wisconsin Exemption Rate
+ us_wi_sit_exemption_rate
+
+
+
+
+ 22
+
+
+
+
+
+
+
+ US WI Wisconsin SIT Tax Rate
+ us_wi_sit_tax_rate
+
+
+
+
+ {
+ 'single': (
+ ( 5730, 0.0000, 0.00),
+ ( 15200, 4.0000, 0.00),
+ ( 16486, 4.4800, 378.80),
+ ( 26227, 6.5408, 436.41),
+ ( 62950, 7.0224, 1073.55),
+ (240190, 6.2700, 3652.39),
+ ( 'inf', 7.6500, 14765.34),
+ ),
+ 'married': (
+ ( 7870, 0.0000, 0.00),
+ ( 18780, 4.0000, 0.00),
+ ( 21400, 5.8400, 436.40),
+ ( 28308, 7.0080, 589.41),
+ ( 60750, 7.5240, 1073.52),
+ (240190, 6.2700, 3514.46),
+ ( 'inf', 7.6500, 14765.35),
+ ),
+ }
+
+
+
+
+
+
+
+ US Wisconsin - Department of Workforce Development - Unemployment Tax
+
+
+
+ US Wisconsin - Department of Revenue - Income Tax
+
+
+
+
+
+
+
+
+
+ ER: US WI Wisconsin State Unemployment
+ ER_US_WI_SUTA
+ python
+ result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_wi_suta_wage_base', rate='us_wi_suta_rate', state_code='WI')
+ code
+ result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_wi_suta_wage_base', rate='us_wi_suta_rate', state_code='WI')
+
+
+
+
+
+
+
+
+ EE: US WI Wisconsin State Income Tax Withholding
+ EE_US_WI_SIT
+ python
+ result, _ = wi_wisconsin_state_income_withholding(payslip, categories, worked_days, inputs)
+ code
+ result, result_rate = wi_wisconsin_state_income_withholding(payslip, categories, worked_days, inputs)
+
+
+
+
+
diff --git a/l10n_us_hr_payroll/data/state/wv_west_virginia.xml b/l10n_us_hr_payroll/data/state/wv_west_virginia.xml
new file mode 100644
index 00000000..ee542c4a
--- /dev/null
+++ b/l10n_us_hr_payroll/data/state/wv_west_virginia.xml
@@ -0,0 +1,215 @@
+
+
+
+
+ US WV West Virginia SUTA Wage Base
+ us_wv_suta_wage_base
+
+
+
+
+ 12000.0
+
+
+
+
+
+
+
+ US WV West Virginia SUTA Rate
+ us_wv_suta_rate
+
+
+
+
+ 2.7
+
+
+
+
+
+
+ US WV West Virginia Exemption Rate
+ us_wv_sit_exemption_rate
+
+
+
+
+ {
+ 'weekly' : 38.46,
+ 'bi-weekly' : 76.92,
+ 'semi-monthly': 83.33,
+ 'monthly' : 166.67,
+ 'annually': 2000.00,
+ }
+
+
+
+
+
+
+ US WV West Virginia SIT Tax Rate
+ us_wv_sit_tax_rate
+
+
+
+
+ {
+ 'single': {
+ 'weekly':(
+ ( 192, 0.00, 3.0),
+ ( 481, 5.76, 4.0),
+ ( 769, 17.32, 4.5),
+ ( 1154, 30.28, 6.0),
+ ('inf', 53.38, 6.5),
+ ),
+ 'bi-weekly':(
+ ( 385, 0.00, 3.0),
+ ( 962, 11.55, 4.0),
+ ( 1538, 34.63, 4.5),
+ ( 2308, 60.55, 6.0),
+ ('inf', 106.75, 6.5),
+ ),
+ 'semi-monthly':(
+ ( 417, 0.00, 3.0),
+ ( 1042 , 12.51, 4.0),
+ ( 1667, 37.51, 4.5),
+ ( 2500, 65.64, 6.0),
+ ('inf', 115.62, 6.5),
+ ),
+ 'monthly':(
+ ( 833, 0.00, 3.0),
+ ( 2083, 24.99, 4.0),
+ ( 3333, 74.99, 4.5),
+ ( 5000, 131.24, 6.0),
+ ('inf', 231.26, 6.5),
+ ),
+ 'annually':(
+ ( 10000, 0.00, 3.0),
+ ( 25000, 300.00, 4.0),
+ ( 40000, 900.00, 4.5),
+ ( 60000, 1575.00, 6.0),
+ ( 'inf', 2775.00, 6.5),
+ ),
+ },
+ 'married': {
+ 'weekly':(
+ ( 115, 0.00, 3.0),
+ ( 288, 3.45, 4.0),
+ ( 462, 10.37, 4.5),
+ ( 692, 18.20, 6.0),
+ ('inf', 32.00, 6.5),
+ ),
+ 'bi-weekly':(
+ ( 231, 0.00, 3.0),
+ ( 577, 6.93, 4.0),
+ ( 923, 20.77, 4.5),
+ ( 1385, 36.34, 6.0),
+ ('inf', 64.06, 6.5),
+ ),
+ 'semi-monthly':(
+ ( 250, 0.00, 3.0),
+ ( 625, 7.50, 4.0),
+ ( 1000, 22.50, 4.5),
+ ( 1500, 39.38, 6.0),
+ ('inf', 69.38, 6.5),
+ ),
+ 'monthly':(
+ ( 500, 0.00, 3.0),
+ ( 1250, 15.00, 4.0),
+ ( 2000, 45.00, 4.5),
+ ( 3000, 78.75, 6.0),
+ ('inf', 138.75, 6.5),
+ ),
+ 'annually':(
+ ( 6000, 0.00, 3.0),
+ (15000, 180.00, 4.0),
+ (24000, 540.00, 4.5),
+ (36000, 945.00, 6.0),
+ ('inf', 1665.00, 6.5),
+ ),
+ },
+ 'head_household': {
+ 'weekly':(
+ ( 192, 0.00, 3.0),
+ ( 481, 5.76, 4.0),
+ ( 769, 17.32, 4.5),
+ ( 1154, 30.28, 6.0),
+ ('inf', 53.38, 6.5),
+ ),
+ 'bi-weekly':(
+ ( 385, 0.00, 3.0),
+ ( 962, 11.55, 4.0),
+ ( 1538, 34.63, 4.5),
+ ( 2308, 60.55, 6.0),
+ ('inf', 106.75, 6.5),
+ ),
+ 'semi-monthly':(
+ ( 417, 0.00, 3.0),
+ ( 1042, 12.51, 4.0),
+ ( 1667, 37.51, 4.5),
+ ( 2500, 65.64, 6.0),
+ ('inf', 115.62, 6.5),
+ ),
+ 'monthly':(
+ ( 833, 0.00, 3.0),
+ ( 2083, 24.99, 4.0),
+ ( 3333, 74.99, 4.5),
+ ( 5000, 131.24, 6.0),
+ ('inf', 231.26, 6.5),
+ ),
+ 'annually':(
+ ( 10000, 0.00, 3.0),
+ ( 25000, 300.00, 4.0),
+ ( 40000, 900.00, 4.5),
+ ( 60000, 1575.00, 6.0),
+ ( 'inf', 2775.00, 6.5),
+ ),
+ },
+ }
+
+
+
+
+
+
+
+ US West Virginia - WorkForce - Unemployment Tax
+
+
+
+ US West Virginia - Department of Revenue - Income Tax
+
+
+
+
+
+
+
+
+
+ ER: US WV West Virginia State Unemployment
+ ER_US_WV_SUTA
+ python
+ result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_wv_suta_wage_base', rate='us_wv_suta_rate', state_code='WV')
+ code
+ result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_wv_suta_wage_base', rate='us_wv_suta_rate', state_code='WV')
+
+
+
+
+
+
+
+
+ EE: US WV West Virginia State Income Tax Withholding
+ EE_US_WV_SIT
+ python
+ result, _ = wv_west_virginia_state_income_withholding(payslip, categories, worked_days, inputs)
+ code
+ result, result_rate = wv_west_virginia_state_income_withholding(payslip, categories, worked_days, inputs)
+
+
+
+
+
\ No newline at end of file
diff --git a/l10n_us_hr_payroll/data/state/wy_wyoming.xml b/l10n_us_hr_payroll/data/state/wy_wyoming.xml
new file mode 100644
index 00000000..247603c5
--- /dev/null
+++ b/l10n_us_hr_payroll/data/state/wy_wyoming.xml
@@ -0,0 +1,61 @@
+
+
+
+
+ US WY Wyoming SUTA Wage Base
+ us_wy_suta_wage_base
+
+
+
+
+ 25400.00
+
+
+
+
+ 26400.00
+
+
+
+
+
+
+
+ US WY Wyoming SUTA Rate
+ us_wy_suta_rate
+
+
+
+
+ 2.10
+
+
+
+
+ 8.5
+
+
+
+
+
+
+
+ US Wyoming - Department of Workforce Services (WDWS) - Unemployment Tax
+
+
+
+
+
+
+
+ ER: US WY Wyoming State Unemployment
+ ER_US_WY_SUTA
+ python
+ result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_wy_suta_wage_base', rate='us_wy_suta_rate', state_code='WY')
+ code
+ result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_wy_suta_wage_base', rate='us_wy_suta_rate', state_code='WY')
+
+
+
+
+
diff --git a/l10n_us_hr_payroll/migrations/13.0.0.0.1/post-migration.py b/l10n_us_hr_payroll/migrations/13.0.0.0.1/post-migration.py
new file mode 100644
index 00000000..406055d7
--- /dev/null
+++ b/l10n_us_hr_payroll/migrations/13.0.0.0.1/post-migration.py
@@ -0,0 +1,30 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+import odoo
+
+
+def migrate(cr, version):
+ """
+ Post-migration no contracts will have any structure types.
+ Unfortunately, we have no way of knowing if they used USA in the past
+ so we have to just assume they did (knowing of course that l10n_us_hr_payroll was installed)...
+ """
+ env = odoo.api.Environment(cr, odoo.SUPERUSER_ID, {})
+ structure_type = env.ref('l10n_us_hr_payroll.structure_type_employee')
+ cr.execute("UPDATE hr_contract "
+ "SET structure_type_id = %s "
+ "WHERE structure_type_id is null AND state in ('draft', 'open')", (structure_type.id, ))
+
+ """
+ Additionally, it is known that post-migration databases will have bad
+ work entry record states (and you will spend time trying to fix them
+ before you could run a payroll batch).
+ """
+ default_work_entry_type = env.ref('hr_work_entry.work_entry_type_attendance', raise_if_not_found=False)
+ if default_work_entry_type:
+ cr.execute("UPDATE hr_work_entry "
+ "SET work_entry_type_id = %s "
+ "WHERE work_entry_type_id is null", (default_work_entry_type.id, ))
+ cr.execute("UPDATE hr_work_entry "
+ "SET state = 'draft' "
+ "WHERE state = 'conflict'")
diff --git a/l10n_us_hr_payroll/migrations/13.0.0.0.1/pre-migration.py b/l10n_us_hr_payroll/migrations/13.0.0.0.1/pre-migration.py
new file mode 100644
index 00000000..e71317a4
--- /dev/null
+++ b/l10n_us_hr_payroll/migrations/13.0.0.0.1/pre-migration.py
@@ -0,0 +1,26 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+import odoo
+
+
+def migrate(cr, version):
+ """
+ Salary Rules can be archived by Odoo S.A. during migration.
+ This leaves them archived after the migration, and even un-archiving them
+ is not enough because they will then be pointed to a "migrated" structure.
+ """
+ env = odoo.api.Environment(cr, odoo.SUPERUSER_ID, {})
+ xml_refs = env['ir.model.data'].search([
+ ('module', '=', 'l10n_us_hr_payroll'),
+ ('model', '=', 'hr.salary.rule'),
+ ])
+ # I don't know why Odoo makes these non-updatable...
+ xml_refs.write({'noupdate': False})
+
+ rule_ids = xml_refs.mapped('res_id')
+ rules = env['hr.salary.rule'].browse(rule_ids)
+ rules.write({'active': True})
+
+ # Cannot add new selection type without fixing missing
+ cr.execute('UPDATE hr_payroll_structure SET schedule_pay = \'monthly\' WHERE schedule_pay is null;')
+ cr.execute('UPDATE hr_payroll_structure_type SET default_schedule_pay = \'monthly\' WHERE default_schedule_pay is null;')
diff --git a/l10n_us_hr_payroll/models/__init__.py b/l10n_us_hr_payroll/models/__init__.py
index c6d607ff..bb27e002 100644
--- a/l10n_us_hr_payroll/models/__init__.py
+++ b/l10n_us_hr_payroll/models/__init__.py
@@ -1,5 +1,8 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+from . import browsable_object
from . import hr_contract
from . import hr_payslip
+from . import res_config_settings
+from . import update
from . import us_payroll_config
diff --git a/l10n_us_hr_payroll/models/browsable_object.py b/l10n_us_hr_payroll/models/browsable_object.py
new file mode 100644
index 00000000..bed067fd
--- /dev/null
+++ b/l10n_us_hr_payroll/models/browsable_object.py
@@ -0,0 +1,148 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from odoo import fields
+from odoo.addons.hr_payroll.models import browsable_object
+
+
+class BrowsableObject(object):
+ def __init__(self, employee_id, dict, env):
+ self.employee_id = employee_id
+ self.dict = dict
+ self.env = env
+ # Customization to allow changing the behavior of the discrete browsable objects.
+ # you can think of this as 'compiling' the query based on the configuration.
+ sum_field = env['ir.config_parameter'].sudo().get_param('hr_payroll.payslip.sum_behavior', 'date_from')
+ if sum_field == 'date' and 'date' not in env['hr.payslip']:
+ # missing attribute, closest by definition
+ sum_field = 'date_to'
+ if not sum_field:
+ sum_field = 'date_from'
+ self._compile_browsable_query(sum_field)
+
+ def __getattr__(self, attr):
+ return attr in self.dict and self.dict.__getitem__(attr) or 0.0
+
+ def _compile_browsable_query(self, sum_field):
+ pass
+
+
+class InputLine(BrowsableObject):
+ """a class that will be used into the python code, mainly for usability purposes"""
+ def _compile_browsable_query(self, sum_field):
+ self.__browsable_query = """
+ SELECT sum(amount) as sum
+ FROM hr_payslip as hp, hr_payslip_input as pi
+ WHERE hp.employee_id = %s AND hp.state = 'done'
+ AND hp.{sum_field} >= %s AND hp.date_to <= %s AND hp.id = pi.payslip_id AND pi.code = %s""".format(sum_field=sum_field)
+
+ def sum(self, code, from_date, to_date=None):
+ if to_date is None:
+ to_date = fields.Date.today()
+ self.env.cr.execute(self.__browsable_query, (self.employee_id, from_date, to_date, code))
+ return self.env.cr.fetchone()[0] or 0.0
+
+
+class WorkedDays(BrowsableObject):
+ """a class that will be used into the python code, mainly for usability purposes"""
+ def _compile_browsable_query(self, sum_field):
+ self.__browsable_query = """
+ SELECT sum(number_of_days) as number_of_days, sum(number_of_hours) as number_of_hours
+ FROM hr_payslip as hp, hr_payslip_worked_days as pi
+ WHERE hp.employee_id = %s AND hp.state = 'done'
+ AND hp.{sum_field} >= %s AND hp.date_to <= %s AND hp.id = pi.payslip_id AND pi.code = %s""".format(sum_field=sum_field)
+
+ def _sum(self, code, from_date, to_date=None):
+ if to_date is None:
+ to_date = fields.Date.today()
+ self.env.cr.execute(self.__browsable_query, (self.employee_id, from_date, to_date, code))
+ return self.env.cr.fetchone()
+
+ def sum(self, code, from_date, to_date=None):
+ res = self._sum(code, from_date, to_date)
+ return res and res[0] or 0.0
+
+ def sum_hours(self, code, from_date, to_date=None):
+ res = self._sum(code, from_date, to_date)
+ return res and res[1] or 0.0
+
+
+class Payslips(BrowsableObject):
+ """a class that will be used into the python code, mainly for usability purposes"""
+ def _compile_browsable_query(self, sum_field):
+ # Note that the core odoo has this as `hp.credit_note = False` but what if it is NULL?
+ # reverse of the desired behavior.
+ self.__browsable_query_rule = """
+ SELECT sum(case when hp.credit_note is not True then (pl.total) else (-pl.total) end)
+ FROM hr_payslip as hp, hr_payslip_line as pl
+ WHERE hp.employee_id = %s AND hp.state = 'done'
+ AND hp.{sum_field} >= %s AND hp.date_to <= %s AND hp.id = pl.slip_id AND pl.code = %s""".format(sum_field=sum_field)
+ # Original (non-recursive)
+ # self.__browsable_query_category = """
+ # SELECT sum(case when hp.credit_note is not True then (pl.total) else (-pl.total) end)
+ # FROM hr_payslip as hp, hr_payslip_line as pl, hr_salary_rule_category as rc
+ # WHERE hp.employee_id = %s AND hp.state = 'done'
+ # AND hp.{sum_field} >= %s AND hp.date_to <= %s AND hp.id = pl.slip_id
+ # AND rc.id = pl.category_id AND rc.code = %s""".format(sum_field=sum_field)
+
+ # Hibou Recursive version
+ self.__browsable_query_category = """
+ WITH RECURSIVE
+ category_by_code as (
+ SELECT id
+ FROM hr_salary_rule_category
+ WHERE code = %s
+ ),
+ category_ids as (
+ SELECT COALESCE((SELECT id FROM category_by_code), -1) AS id
+ UNION ALL
+ SELECT rc.id
+ FROM hr_salary_rule_category AS rc
+ JOIN category_ids AS rcs ON rcs.id = rc.parent_id
+ )
+
+ SELECT sum(case when hp.credit_note is not True then (pl.total) else (-pl.total) end)
+ FROM hr_payslip as hp, hr_payslip_line as pl
+ WHERE hp.employee_id = %s AND hp.state = 'done'
+ AND hp.{sum_field} >= %s AND hp.date_to <= %s AND hp.id = pl.slip_id
+ AND pl.category_id in (SELECT id from category_ids)""".format(sum_field=sum_field)
+
+ def sum(self, code, from_date, to_date=None):
+ if to_date is None:
+ to_date = fields.Date.today()
+ self.env.cr.execute(self.__browsable_query_rule, (self.employee_id, from_date, to_date, code))
+ res = self.env.cr.fetchone()
+ return res and res[0] or 0.0
+
+ def rule_parameter(self, code):
+ return self.env['hr.rule.parameter']._get_parameter_from_code(code, self.dict.date_to)
+
+ def sum_category(self, code, from_date, to_date=None):
+ if to_date is None:
+ to_date = fields.Date.today()
+
+ self.env['hr.payslip'].flush(['credit_note', 'employee_id', 'state', 'date_from', 'date_to'])
+ self.env['hr.payslip.line'].flush(['total', 'slip_id', 'category_id'])
+ self.env['hr.salary.rule.category'].flush(['code'])
+
+ # standard version
+ # self.env.cr.execute(self.__browsable_query_category, (self.employee_id, from_date, to_date, code))
+ # recursive category version
+ self.env.cr.execute(self.__browsable_query_category, (code, self.employee_id, from_date, to_date))
+ res = self.env.cr.fetchone()
+ return res and res[0] or 0.0
+
+ @property
+ def paid_amount(self):
+ return self.dict._get_paid_amount()
+
+
+# Patch over Core
+browsable_object.BrowsableObject.__init__ = BrowsableObject.__init__
+browsable_object.BrowsableObject._compile_browsable_query = BrowsableObject._compile_browsable_query
+browsable_object.InputLine._compile_browsable_query = InputLine._compile_browsable_query
+browsable_object.InputLine.sum = InputLine.sum
+browsable_object.WorkedDays._compile_browsable_query = WorkedDays._compile_browsable_query
+browsable_object.WorkedDays.sum = WorkedDays.sum
+browsable_object.Payslips._compile_browsable_query = Payslips._compile_browsable_query
+browsable_object.Payslips.sum = Payslips.sum
+browsable_object.Payslips.sum_category = Payslips.sum_category
diff --git a/l10n_us_hr_payroll/models/federal/fed_940.py b/l10n_us_hr_payroll/models/federal/fed_940.py
index 1cf042c7..bbd4be17 100644
--- a/l10n_us_hr_payroll/models/federal/fed_940.py
+++ b/l10n_us_hr_payroll/models/federal/fed_940.py
@@ -1,9 +1,53 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+def futa_wage(payslip, categories):
+ """
+ Returns FUTA eligible wage for current Payslip (no wage_base, just by categories)
+ WAGE = GROSS - ALW_FUTA_EXEMPT + DED_FUTA_EXEMPT
+ :return: wage
+ """
+ wage = categories.GROSS
+
+ wage -= categories.ALW_FUTA_EXEMPT + \
+ categories.ALW_FIT_FUTA_EXEMPT + \
+ categories.ALW_FIT_FICA_FUTA_EXEMPT + \
+ categories.ALW_FICA_FUTA_EXEMPT
+
+ wage += categories.DED_FUTA_EXEMPT + \
+ categories.DED_FIT_FUTA_EXEMPT + \
+ categories.DED_FIT_FICA_FUTA_EXEMPT + \
+ categories.DED_FICA_FUTA_EXEMPT
+
+ return wage
+
+
+def futa_wage_ytd(payslip, categories):
+ """
+ Returns Year to Date FUTA eligible wages
+ WAGE = GROSS - ALW_FUTA_EXEMPT + DED_FUTA_EXEMPT
+ :return: 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('ALW_FUTA_EXEMPT', str(year) + '-01-01', str(year+1) + '-01-01') + \
+ payslip.sum_category('ALW_FIT_FUTA_EXEMPT', str(year) + '-01-01', str(year+1) + '-01-01') + \
+ payslip.sum_category('ALW_FIT_FICA_FUTA_EXEMPT', str(year) + '-01-01', str(year+1) + '-01-01') + \
+ payslip.sum_category('ALW_FICA_FUTA_EXEMPT', str(year) + '-01-01', str(year+1) + '-01-01')
+
+ ytd_wage += payslip.sum_category('DED_FUTA_EXEMPT', str(year) + '-01-01', str(year+1) + '-01-01') + \
+ payslip.sum_category('DED_FIT_FUTA_EXEMPT', str(year) + '-01-01', str(year+1) + '-01-01') + \
+ payslip.sum_category('DED_FIT_FICA_FUTA_EXEMPT', str(year) + '-01-01', str(year+1) + '-01-01') + \
+ payslip.sum_category('DED_FICA_FUTA_EXEMPT', str(year) + '-01-01', str(year+1) + '-01-01')
+
+ ytd_wage += payslip.contract_id.external_wages
+ return ytd_wage
+
+
def er_us_940_futa(payslip, categories, worked_days, inputs):
"""
Returns FUTA eligible wage and rate.
- WAGE = GROSS - WAGE_US_940_FUTA_EXEMPT
:return: result, result_rate (wage, percent)
"""
@@ -17,16 +61,14 @@ def er_us_940_futa(payslip, categories, worked_days, inputs):
result_rate = -payslip.rule_parameter('fed_940_futa_rate_normal')
# 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.contract_id.external_wages
+ wage = futa_wage(payslip, categories)
+ if not wage:
+ return 0.0, 0.0
+ ytd_wage = futa_wage_ytd(payslip, categories)
wage_base = payslip.rule_parameter('fed_940_futa_wage_base')
remaining = wage_base - ytd_wage
- wage = categories.GROSS - categories.WAGE_US_940_FUTA_EXEMPT
-
if remaining < 0.0:
result = 0.0
elif remaining < wage:
diff --git a/l10n_us_hr_payroll/models/federal/fed_941.py b/l10n_us_hr_payroll/models/federal/fed_941.py
index e6288f88..009a93c6 100644
--- a/l10n_us_hr_payroll/models/federal/fed_941.py
+++ b/l10n_us_hr_payroll/models/federal/fed_941.py
@@ -4,10 +4,54 @@
# _logger = logging.getLogger(__name__)
+def fica_wage(payslip, categories):
+ """
+ Returns FICA eligible wage for current Payslip (no wage_base, just by categories)
+ WAGE = GROSS - ALW_FICA_EXEMPT + DED_FICA_EXEMPT
+ :return: wage
+ """
+ wage = categories.GROSS
+
+ less_exempt = categories.ALW_FICA_EXEMPT + \
+ categories.ALW_FIT_FICA_EXEMPT + \
+ categories.ALW_FIT_FICA_FUTA_EXEMPT + \
+ categories.ALW_FICA_FUTA_EXEMPT
+
+ plus_exempt = categories.DED_FICA_EXEMPT + \
+ categories.DED_FIT_FICA_EXEMPT + \
+ categories.DED_FIT_FICA_FUTA_EXEMPT + \
+ categories.DED_FICA_FUTA_EXEMPT
+ # _logger.info('fica wage GROSS: %0.2f less exempt ALW: %0.2f plus exempt DED: %0.2f' % (wage, less_exempt, plus_exempt))
+ return wage - less_exempt + plus_exempt
+
+
+def fica_wage_ytd(payslip, categories):
+ """
+ Returns Year to Date FICA eligible wages
+ WAGE = GROSS - ALW_FICA_EXEMPT + DED_FICA_EXEMPT
+ :return: wage
+ """
+ year = payslip.dict.get_year()
+ ytd_wage = payslip.sum_category('GROSS', str(year) + '-01-01', str(year+1) + '-01-01')
+
+ less_exempt = payslip.sum_category('ALW_FICA_EXEMPT', str(year) + '-01-01', str(year+1) + '-01-01') + \
+ payslip.sum_category('ALW_FIT_FICA_EXEMPT', str(year) + '-01-01', str(year+1) + '-01-01') + \
+ payslip.sum_category('ALW_FIT_FICA_FUTA_EXEMPT', str(year) + '-01-01', str(year+1) + '-01-01') + \
+ payslip.sum_category('ALW_FICA_FUTA_EXEMPT', str(year) + '-01-01', str(year+1) + '-01-01')
+
+ plus_exempt = payslip.sum_category('DED_FICA_EXEMPT', str(year) + '-01-01', str(year+1) + '-01-01') + \
+ payslip.sum_category('DED_FIT_FICA_EXEMPT', str(year) + '-01-01', str(year+1) + '-01-01') + \
+ payslip.sum_category('DED_FIT_FICA_FUTA_EXEMPT', str(year) + '-01-01', str(year+1) + '-01-01') + \
+ payslip.sum_category('DED_FICA_FUTA_EXEMPT', str(year) + '-01-01', str(year+1) + '-01-01')
+
+ external_wages = payslip.dict.contract_id.external_wages
+ # _logger.info('fica ytd wage GROSS: %0.2f less exempt ALW: %0.2f plus exempt DED: %0.2f plus external: %0.2f' % (ytd_wage, less_exempt, plus_exempt, external_wages))
+ return ytd_wage - less_exempt + plus_exempt + external_wages
+
+
def ee_us_941_fica_ss(payslip, categories, worked_days, inputs):
"""
Returns FICA Social Security eligible wage and rate.
- WAGE = GROSS - WAGE_US_941_FICA_EXEMPT
:return: result, result_rate (wage, percent)
"""
exempt = payslip.contract_id.us_payroll_config_value('fed_941_fica_exempt')
@@ -18,16 +62,14 @@ def ee_us_941_fica_ss(payslip, categories, worked_days, inputs):
result_rate = -payslip.rule_parameter('fed_941_fica_ss_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_941_FICA_EXEMPT', str(year) + '-01-01', str(year+1) + '-01-01')
- ytd_wage += payslip.contract_id.external_wages
+ wage = fica_wage(payslip, categories)
+ if not wage:
+ return 0.0, 0.0
+ ytd_wage = fica_wage_ytd(payslip, categories)
wage_base = payslip.rule_parameter('fed_941_fica_ss_wage_base')
remaining = wage_base - ytd_wage
- wage = categories.GROSS - categories.WAGE_US_941_FICA_EXEMPT
-
if remaining < 0.0:
result = 0.0
elif remaining < wage:
@@ -44,7 +86,6 @@ er_us_941_fica_ss = ee_us_941_fica_ss
def ee_us_941_fica_m(payslip, categories, worked_days, inputs):
"""
Returns FICA Medicare eligible wage and rate.
- WAGE = GROSS - WAGE_US_941_FICA_EXEMPT
:return: result, result_rate (wage, percent)
"""
exempt = payslip.contract_id.us_payroll_config_value('fed_941_fica_exempt')
@@ -55,16 +96,14 @@ def ee_us_941_fica_m(payslip, categories, worked_days, inputs):
result_rate = -payslip.rule_parameter('fed_941_fica_m_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_941_FICA_EXEMPT', str(year) + '-01-01', str(year+1) + '-01-01')
- ytd_wage += payslip.contract_id.external_wages
+ wage = fica_wage(payslip, categories)
+ if not wage:
+ return 0.0, 0.0
+ ytd_wage = fica_wage_ytd(payslip, categories)
wage_base = float(payslip.rule_parameter('fed_941_fica_m_wage_base')) # inf
remaining = wage_base - ytd_wage
- wage = categories.GROSS - categories.WAGE_US_941_FICA_EXEMPT
-
if remaining < 0.0:
result = 0.0
elif remaining < wage:
@@ -81,8 +120,6 @@ er_us_941_fica_m = ee_us_941_fica_m
def ee_us_941_fica_m_add(payslip, categories, worked_days, inputs):
"""
Returns FICA Medicare Additional eligible wage and rate.
- Note that this wage is not capped like the above rules.
- WAGE = GROSS - WAGE_FICA_EXEMPT
:return: result, result_rate (wage, percent)
"""
exempt = payslip.contract_id.us_payroll_config_value('fed_941_fica_exempt')
@@ -93,16 +130,14 @@ def ee_us_941_fica_m_add(payslip, categories, worked_days, inputs):
result_rate = -payslip.rule_parameter('fed_941_fica_m_add_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_941_FICA_EXEMPT', str(year) + '-01-01', str(year+1) + '-01-01')
- ytd_wage += payslip.contract_id.external_wages
+ wage = fica_wage(payslip, categories)
+ if not wage:
+ return 0.0, 0.0
+ ytd_wage = fica_wage_ytd(payslip, categories)
wage_start = payslip.rule_parameter('fed_941_fica_m_add_wage_start')
existing_wage = ytd_wage - wage_start
- wage = categories.GROSS - categories.WAGE_US_941_FICA_EXEMPT
-
if existing_wage >= 0.0:
result = wage
elif wage + existing_wage > 0.0:
@@ -113,11 +148,54 @@ def ee_us_941_fica_m_add(payslip, categories, worked_days, inputs):
return result, result_rate
+def fit_wage(payslip, categories):
+ """
+ Returns FIT eligible wage for current Payslip (no wage_base, just by categories)
+ WAGE = GROSS - ALW_FIT_EXEMPT + DED_FIT_EXEMPT
+ :return: wage
+ """
+ wage = categories.GROSS
+
+ wage -= categories.ALW_FIT_EXEMPT + \
+ categories.ALW_FIT_FICA_EXEMPT + \
+ categories.ALW_FIT_FICA_FUTA_EXEMPT + \
+ categories.ALW_FIT_FUTA_EXEMPT
+
+ wage += categories.DED_FIT_EXEMPT + \
+ categories.DED_FIT_FICA_EXEMPT + \
+ categories.DED_FIT_FICA_FUTA_EXEMPT + \
+ categories.DED_FIT_FUTA_EXEMPT
+
+ return wage
+
+
+def fit_wage_ytd(payslip, categories):
+ """
+ Returns Year to Date FIT eligible wages
+ WAGE = GROSS - ALW_FIT_EXEMPT + DED_FIT_EXEMPT
+ :return: 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('ALW_FIT_EXEMPT', str(year) + '-01-01', str(year+1) + '-01-01') + \
+ payslip.sum_category('ALW_FIT_FICA_EXEMPT', str(year) + '-01-01', str(year+1) + '-01-01') + \
+ payslip.sum_category('ALW_FIT_FICA_FUTA_EXEMPT', str(year) + '-01-01', str(year+1) + '-01-01') + \
+ payslip.sum_category('ALW_FIT_FUTA_EXEMPT', str(year) + '-01-01', str(year+1) + '-01-01')
+
+ ytd_wage += payslip.sum_category('DED_FIT_EXEMPT', str(year) + '-01-01', str(year+1) + '-01-01') + \
+ payslip.sum_category('DED_FIT_FICA_EXEMPT', str(year) + '-01-01', str(year+1) + '-01-01') + \
+ payslip.sum_category('DED_FIT_FICA_FUTA_EXEMPT', str(year) + '-01-01', str(year+1) + '-01-01') + \
+ payslip.sum_category('DED_FIT_FUTA_EXEMPT', str(year) + '-01-01', str(year+1) + '-01-01')
+
+ ytd_wage += payslip.contract_id.external_wages
+ return ytd_wage
+
+
# Federal Income Tax
def ee_us_941_fit(payslip, categories, worked_days, inputs):
"""
Returns Wage and rate that is computed given the amount to withhold.
- WAGE = GROSS - WAGE_US_941_FIT_EXEMPT
:return: result, result_rate (wage, percent)
"""
filing_status = payslip.contract_id.us_payroll_config_value('fed_941_fit_w4_filing_status')
@@ -125,7 +203,10 @@ def ee_us_941_fit(payslip, categories, worked_days, inputs):
return 0.0, 0.0
schedule_pay = payslip.contract_id.schedule_pay
- wage = categories.GROSS - categories.WAGE_US_941_FIT_EXEMPT
+ wage = fit_wage(payslip, categories)
+ if not wage:
+ return 0.0, 0.0
+
#_logger.warn('initial gross wage: ' + str(wage))
year = payslip.dict.get_year()
if year >= 2020:
diff --git a/l10n_us_hr_payroll/models/hr_contract.py b/l10n_us_hr_payroll/models/hr_contract.py
index 46bc42b7..ab5772c8 100644
--- a/l10n_us_hr_payroll/models/hr_contract.py
+++ b/l10n_us_hr_payroll/models/hr_contract.py
@@ -6,6 +6,11 @@ from .us_payroll_config import FUTA_TYPE_NORMAL, \
FUTA_TYPE_EXEMPT
+class HrPayrollStructureType(models.Model):
+ _inherit = 'hr.payroll.structure.type'
+ default_schedule_pay = fields.Selection(selection_add=[('semi-monthly', 'Semi-monthly')])
+
+
class HrPayrollStructure(models.Model):
_inherit = 'hr.payroll.structure'
schedule_pay = fields.Selection(selection_add=[('semi-monthly', 'Semi-monthly')])
diff --git a/l10n_us_hr_payroll/models/hr_payslip.py b/l10n_us_hr_payroll/models/hr_payslip.py
index 92a1e0fd..e5a728f2 100644
--- a/l10n_us_hr_payroll/models/hr_payslip.py
+++ b/l10n_us_hr_payroll/models/hr_payslip.py
@@ -9,6 +9,48 @@ 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, \
+ general_state_income_withholding, \
+ is_us_state
+from .state.al_alabama import al_alabama_state_income_withholding
+from .state.ar_arkansas import ar_arkansas_state_income_withholding
+from .state.az_arizona import az_arizona_state_income_withholding
+from .state.ca_california import ca_california_state_income_withholding
+from .state.co_colorado import co_colorado_state_income_withholding
+from .state.ct_connecticut import ct_connecticut_state_income_withholding
+from .state.de_delaware import de_delaware_state_income_withholding
+from .state.ga_georgia import ga_georgia_state_income_withholding
+from .state.hi_hawaii import hi_hawaii_state_income_withholding
+from .state.ia_iowa import ia_iowa_state_income_withholding
+from .state.id_idaho import id_idaho_state_income_withholding
+from .state.il_illinois import il_illinois_state_income_withholding
+from .state.in_indiana import in_indiana_state_income_withholding
+from .state.ks_kansas import ks_kansas_state_income_withholding
+from .state.ky_kentucky import ky_kentucky_state_income_withholding
+from .state.la_louisiana import la_louisiana_state_income_withholding
+from .state.me_maine import me_maine_state_income_withholding
+from .state.mi_michigan import mi_michigan_state_income_withholding
+from .state.mn_minnesota import mn_minnesota_state_income_withholding
+from .state.mo_missouri import mo_missouri_state_income_withholding
+from .state.ms_mississippi import ms_mississippi_state_income_withholding
+from .state.mt_montana import mt_montana_state_income_withholding
+from .state.nc_northcarolina import nc_northcarolina_state_income_withholding
+from .state.nd_north_dakota import nd_north_dakota_state_income_withholding
+from .state.ne_nebraska import ne_nebraska_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.ny_new_york import ny_new_york_state_income_withholding
+from .state.oh_ohio import oh_ohio_state_income_withholding
+from .state.ok_oklahoma import ok_oklahoma_state_income_withholding
+from .state.ri_rhode_island import ri_rhode_island_state_income_withholding
+from .state.sc_south_carolina import sc_south_carolina_state_income_withholding
+from .state.ut_utah import ut_utah_state_income_withholding
+from .state.vt_vermont import vt_vermont_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
+from .state.wi_wisconsin import wi_wisconsin_state_income_withholding
+from .state.wv_west_virginia import wv_west_virginia_state_income_withholding
class HRPayslip(models.Model):
@@ -37,6 +79,48 @@ class HRPayslip(models.Model):
'er_us_941_fica_ss': er_us_941_fica_ss,
'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,
+ 'is_us_state': is_us_state,
+ 'al_alabama_state_income_withholding': al_alabama_state_income_withholding,
+ 'ar_arkansas_state_income_withholding': ar_arkansas_state_income_withholding,
+ 'az_arizona_state_income_withholding': az_arizona_state_income_withholding,
+ 'ca_california_state_income_withholding': ca_california_state_income_withholding,
+ 'co_colorado_state_income_withholding': co_colorado_state_income_withholding,
+ 'ct_connecticut_state_income_withholding': ct_connecticut_state_income_withholding,
+ 'de_delaware_state_income_withholding': de_delaware_state_income_withholding,
+ 'ga_georgia_state_income_withholding': ga_georgia_state_income_withholding,
+ 'hi_hawaii_state_income_withholding': hi_hawaii_state_income_withholding,
+ 'ia_iowa_state_income_withholding': ia_iowa_state_income_withholding,
+ 'id_idaho_state_income_withholding': id_idaho_state_income_withholding,
+ 'il_illinois_state_income_withholding': il_illinois_state_income_withholding,
+ 'in_indiana_state_income_withholding': in_indiana_state_income_withholding,
+ 'ks_kansas_state_income_withholding': ks_kansas_state_income_withholding,
+ 'ky_kentucky_state_income_withholding':ky_kentucky_state_income_withholding,
+ 'la_louisiana_state_income_withholding': la_louisiana_state_income_withholding,
+ 'me_maine_state_income_withholding': me_maine_state_income_withholding,
+ 'mi_michigan_state_income_withholding': mi_michigan_state_income_withholding,
+ 'mn_minnesota_state_income_withholding': mn_minnesota_state_income_withholding,
+ 'mo_missouri_state_income_withholding': mo_missouri_state_income_withholding,
+ 'ms_mississippi_state_income_withholding': ms_mississippi_state_income_withholding,
+ 'mt_montana_state_income_withholding': mt_montana_state_income_withholding,
+ 'nc_northcarolina_state_income_withholding': nc_northcarolina_state_income_withholding,
+ 'nd_north_dakota_state_income_withholding': nd_north_dakota_state_income_withholding,
+ 'ne_nebraska_state_income_withholding': ne_nebraska_state_income_withholding,
+ 'nj_newjersey_state_income_withholding': nj_newjersey_state_income_withholding,
+ 'nm_new_mexico_state_income_withholding': nm_new_mexico_state_income_withholding,
+ 'ny_new_york_state_income_withholding': ny_new_york_state_income_withholding,
+ 'oh_ohio_state_income_withholding': oh_ohio_state_income_withholding,
+ 'ok_oklahoma_state_income_withholding': ok_oklahoma_state_income_withholding,
+ 'ri_rhode_island_state_income_withholding': ri_rhode_island_state_income_withholding,
+ 'sc_south_carolina_state_income_withholding': sc_south_carolina_state_income_withholding,
+ 'ut_utah_state_income_withholding': ut_utah_state_income_withholding,
+ 'vt_vermont_state_income_withholding': vt_vermont_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,
+ 'wi_wisconsin_state_income_withholding': wi_wisconsin_state_income_withholding,
+ 'wv_west_virginia_state_income_withholding': wv_west_virginia_state_income_withholding,
})
return res
diff --git a/l10n_us_hr_payroll/models/res_config_settings.py b/l10n_us_hr_payroll/models/res_config_settings.py
new file mode 100644
index 00000000..05af9430
--- /dev/null
+++ b/l10n_us_hr_payroll/models/res_config_settings.py
@@ -0,0 +1,24 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from odoo import fields, models
+
+
+class ResConfigSettings(models.TransientModel):
+ _inherit = 'res.config.settings'
+
+ payslip_sum_type = fields.Selection([
+ ('date_from', 'Date From'),
+ ('date_to', 'Date To'),
+ ('date', 'Accounting Date'),
+ ], 'Payslip Sum Behavior', help="Behavior for what payslips are considered "
+ "during rule execution. Stock Odoo behavior "
+ "would not consider a payslip starting on 2019-12-30 "
+ "ending on 2020-01-07 when summing a 2020 payslip category.\n\n"
+ "Accounting Date requires Payroll Accounting and will "
+ "fall back to date_to as the 'closest behavior'.",
+ config_parameter='hr_payroll.payslip.sum_behavior')
+
+ def set_values(self):
+ super(ResConfigSettings, self).set_values()
+ self.env['ir.config_parameter'].set_param('hr_payroll.payslip.sum_behavior',
+ self.payslip_sum_type or 'date_from')
diff --git a/l10n_us_hr_payroll/models/state/__init__.py b/l10n_us_hr_payroll/models/state/__init__.py
new file mode 100644
index 00000000..0358305d
--- /dev/null
+++ b/l10n_us_hr_payroll/models/state/__init__.py
@@ -0,0 +1 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
diff --git a/l10n_us_hr_payroll/models/state/al_alabama.py b/l10n_us_hr_payroll/models/state/al_alabama.py
new file mode 100644
index 00000000..11638b7f
--- /dev/null
+++ b/l10n_us_hr_payroll/models/state/al_alabama.py
@@ -0,0 +1,80 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from .general import _state_applies, sit_wage
+
+
+def al_alabama_state_income_withholding(payslip, categories, worked_days, inputs):
+ """
+ Returns SIT eligible wage and rate.
+
+ :return: result, result_rate (wage, percent)
+ """
+ state_code = 'AL'
+ if not _state_applies(payslip, state_code):
+ return 0.0, 0.0
+
+ # Determine Wage
+ wage = sit_wage(payslip, categories)
+ if not wage:
+ return 0.0, 0.0
+
+ exemptions = payslip.contract_id.us_payroll_config_value('al_a4_sit_exemptions')
+ if not exemptions:
+ 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
+
+ pay_periods = payslip.dict.get_pay_periods_in_year()
+ additional = payslip.contract_id.us_payroll_config_value('state_income_tax_additional_withholding')
+ tax_table = payslip.rule_parameter('us_al_sit_tax_rate')
+ dependent_rate = payslip.rule_parameter('us_al_sit_dependent_rate')
+ standard_deduction = payslip.rule_parameter('us_al_sit_standard_deduction_rate').get(exemptions, 0.0)
+ personal_exemption = payslip.rule_parameter('us_al_sit_personal_exemption_rate').get(exemptions, 0.0)
+ dependent = payslip.contract_id.us_payroll_config_value('al_a4_sit_dependents')
+ fed_withholding = categories.EE_US_941_FIT
+
+ annual_wage = wage * pay_periods
+ standard_deduction_amt = 0.0
+ personal_exemption_amt = 0.0
+ dependent_amt = 0.0
+ withholding = 0.0
+
+ if standard_deduction:
+ row = standard_deduction
+ last_amt = 0.0
+ for data in row:
+ if annual_wage < float(data[0]):
+ if len(data) > 3:
+ increment_count = (- (wage - last_amt) // data[3])
+ standard_deduction_amt = data[1] - (increment_count * data[2])
+ else:
+ standard_deduction_amt = data[1]
+ else:
+ last_amt = data[0]
+ after_deduction = annual_wage - standard_deduction_amt
+ after_fed_withholding = (fed_withholding * pay_periods) + after_deduction
+ if not personal_exempt:
+ personal_exemption_amt = personal_exemption
+ after_personal_exemption = after_fed_withholding - personal_exemption_amt
+ for row in dependent_rate:
+ if annual_wage < float(row[1]):
+ dependent_amt = row[0] * dependent
+ break
+
+ taxable_amount = after_personal_exemption - dependent_amt
+ last = 0.0
+ tax_table = tax_table['M'] if exemptions == 'M' else tax_table['0']
+ for row in tax_table:
+ if taxable_amount < float(row[0]):
+ withholding = withholding + ((taxable_amount - last) * (row[1] / 100))
+ break
+ withholding = withholding + ((row[0] - last) * (row[1] / 100))
+ last = row[0]
+
+ if withholding < 0.0:
+ withholding = 0.0
+ withholding /= pay_periods
+ withholding += additional
+ return wage, -((withholding / wage) * 100.0)
diff --git a/l10n_us_hr_payroll/models/state/ar_arkansas.py b/l10n_us_hr_payroll/models/state/ar_arkansas.py
new file mode 100644
index 00000000..c1306c94
--- /dev/null
+++ b/l10n_us_hr_payroll/models/state/ar_arkansas.py
@@ -0,0 +1,47 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from .general import _state_applies, sit_wage
+
+
+def ar_arkansas_state_income_withholding(payslip, categories, worked_days, inputs):
+ """
+ Returns SIT eligible wage and rate.
+
+ :return: result, result_rate (wage, percent)
+ """
+ state_code = 'AR'
+ if not _state_applies(payslip, state_code):
+ return 0.0, 0.0
+
+ if payslip.contract_id.us_payroll_config_value('state_income_tax_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()
+ additional = payslip.contract_id.us_payroll_config_value('state_income_tax_additional_withholding')
+ sit_tax_rate = payslip.rule_parameter('us_ar_sit_tax_rate')
+ standard_deduction = payslip.rule_parameter('us_ar_sit_standard_deduction_rate')
+ allowances = payslip.contract_id.us_payroll_config_value('ar_ar4ec_sit_allowances')
+
+ allowances_amt = allowances * 26.0
+ taxable_income = (wage * pay_periods) - standard_deduction
+ if taxable_income < 87001.0:
+ taxable_income = (taxable_income // 50) * 50.0 + 50.0
+
+ withholding = 0.0
+ for row in sit_tax_rate:
+ cap, rate, adjust_amount = row
+ cap = float(cap)
+ if cap > taxable_income:
+ withholding = (((rate / 100.0) * taxable_income) - adjust_amount) - allowances_amt
+ break
+
+ # In case withholding or taxable_income is negative
+ withholding = max(withholding, 0.0)
+ withholding = round(withholding / pay_periods)
+ withholding += additional
+ return wage, -((withholding / wage) * 100.0)
diff --git a/l10n_us_hr_payroll/models/state/az_arizona.py b/l10n_us_hr_payroll/models/state/az_arizona.py
new file mode 100644
index 00000000..90c44898
--- /dev/null
+++ b/l10n_us_hr_payroll/models/state/az_arizona.py
@@ -0,0 +1,35 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from .general import _state_applies, sit_wage
+
+
+def az_arizona_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 = 'AZ'
+ if not _state_applies(payslip, state_code):
+ return 0.0, 0.0
+
+ # Determine Wage
+ wage = sit_wage(payslip, categories)
+ if not wage:
+ return 0.0, 0.0
+
+ schedule_pay = payslip.contract_id.schedule_pay
+ additional = payslip.contract_id.us_payroll_config_value('state_income_tax_additional_withholding')
+ withholding_percent = payslip.contract_id.us_payroll_config_value('az_a4_sit_withholding_percentage')
+
+ if withholding_percent <= 0.0:
+ return 0.0, 0.0
+
+ wh_percentage = withholding_percent / 100.0
+ withholding = wage * wh_percentage
+
+ if withholding < 0.0:
+ withholding = 0.0
+ withholding += additional
+ return wage, -((withholding / wage) * 100.0)
diff --git a/l10n_us_hr_payroll/models/state/ca_california.py b/l10n_us_hr_payroll/models/state/ca_california.py
new file mode 100644
index 00000000..62382402
--- /dev/null
+++ b/l10n_us_hr_payroll/models/state/ca_california.py
@@ -0,0 +1,98 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from .general import _state_applies, sit_wage
+
+MAX_ALLOWANCES = 10
+
+
+def ca_california_state_income_withholding(payslip, categories, worked_days, inputs):
+ """
+ Returns SIT eligible wage and rate.
+
+ :return: result, result_rate (wage, percent)
+ """
+
+ state_code = 'CA'
+ if not _state_applies(payslip, state_code):
+ return 0.0, 0.0
+
+ # Determine Wage
+ wage = sit_wage(payslip, categories)
+ if not wage:
+ return 0.0, 0.0
+
+ filing_status = payslip.contract_id.us_payroll_config_value('ca_de4_sit_filing_status')
+ if not filing_status:
+ return 0.0, 0.0
+
+ schedule_pay = payslip.contract_id.schedule_pay
+ additional = payslip.contract_id.us_payroll_config_value('state_income_tax_additional_withholding')
+ sit_allowances = payslip.contract_id.us_payroll_config_value('ca_de4_sit_allowances')
+ additional_allowances = payslip.contract_id.us_payroll_config_value('ca_de4_sit_additional_allowances')
+ low_income_exemption = payslip.rule_parameter('us_ca_sit_income_exemption_rate')[schedule_pay]
+ estimated_deduction = payslip.rule_parameter('us_ca_sit_estimated_deduction_rate')[schedule_pay]
+ tax_table = payslip.rule_parameter('us_ca_sit_tax_rate')[filing_status].get(schedule_pay)
+ standard_deduction = payslip.rule_parameter('us_ca_sit_standard_deduction_rate')[schedule_pay]
+ exemption_allowances = payslip.rule_parameter('us_ca_sit_exemption_allowance_rate')[schedule_pay]
+
+ low_income = False
+ if filing_status == 'head_household':
+ _, _, _, income = low_income_exemption
+ if wage <= income:
+ low_income = True
+ elif filing_status == 'married':
+ if sit_allowances >= 2:
+ _, _, income, _ = low_income_exemption
+ if wage <= income:
+ low_income = True
+ else:
+ _, income, _, _ = low_income_exemption
+ if wage <= income:
+ low_income = True
+ else:
+ income, _, _, _ = low_income_exemption
+ if wage <= income:
+ low_income = True
+
+ withholding = 0.0
+ taxable_wage = wage
+ if not low_income:
+ allowance_index = max(additional_allowances - 1, 0)
+ if additional_allowances > MAX_ALLOWANCES:
+ deduction = (estimated_deduction[0] * additional_allowances)
+ taxable_wage -= deduction
+ elif additional_allowances > 0:
+ deduction = estimated_deduction[allowance_index]
+ taxable_wage -= deduction
+
+ if filing_status == 'head_household':
+ _, _, _, deduction = standard_deduction
+ taxable_wage -= deduction
+ elif filing_status == 'married':
+ if sit_allowances >= 2:
+ _, _, deduction, _ = standard_deduction
+ taxable_wage -= deduction
+ else:
+ _, deduction, _, _ = standard_deduction
+ taxable_wage -= deduction
+ else:
+ deduction, _, _, _ = standard_deduction
+ taxable_wage -= deduction
+
+ over = 0.0
+ for row in tax_table:
+ if taxable_wage <= row[0]:
+ withholding = ((taxable_wage - over) * row[1]) + row[2]
+ break
+ over = row[0]
+
+ allowance_index = sit_allowances - 1
+ if sit_allowances > MAX_ALLOWANCES:
+ deduction = exemption_allowances[0] * sit_allowances
+ withholding -= deduction
+ elif sit_allowances > 0:
+ deduction = exemption_allowances[allowance_index]
+ withholding -= deduction
+
+ withholding += additional
+ return wage, -((withholding / wage) * 100.0)
diff --git a/l10n_us_hr_payroll/models/state/co_colorado.py b/l10n_us_hr_payroll/models/state/co_colorado.py
new file mode 100644
index 00000000..f0c7b436
--- /dev/null
+++ b/l10n_us_hr_payroll/models/state/co_colorado.py
@@ -0,0 +1,45 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from .general import _state_applies, sit_wage
+
+
+def co_colorado_state_income_withholding(payslip, categories, worked_days, inputs):
+ """
+ Returns SIT eligible wage and rate.
+
+ :return: result, result_rate (wage, percent)
+ """
+ state_code = 'CO'
+ if not _state_applies(payslip, state_code):
+ return 0.0, 0.0
+
+ # Determine Wage
+ wage = sit_wage(payslip, categories)
+ if not wage:
+ return 0.0, 0.0
+
+ filing_status = payslip.contract_id.us_payroll_config_value('fed_941_fit_w4_filing_status')
+ if not filing_status:
+ return 0.0, 0.0
+
+ state_exempt = payslip.contract_id.us_payroll_config_value('state_income_tax_exempt')
+ if state_exempt:
+ return 0.0, 0.0
+
+ pay_periods = payslip.dict.get_pay_periods_in_year()
+ additional = payslip.contract_id.us_payroll_config_value('state_income_tax_additional_withholding')
+ exemption_rate = payslip.rule_parameter('us_co_sit_exemption_rate')
+ tax_rate = payslip.rule_parameter('us_co_sit_tax_rate')
+
+ taxable_income = wage * pay_periods
+ if filing_status == 'married':
+ taxable_income -= exemption_rate * 2
+ else:
+ taxable_income -= exemption_rate
+
+ withholding = taxable_income * (tax_rate / 100)
+
+ withholding = max(withholding, 0.0)
+ withholding = withholding / pay_periods
+ withholding += additional
+ return wage, -((withholding / wage) * 100.0)
diff --git a/l10n_us_hr_payroll/models/state/ct_connecticut.py b/l10n_us_hr_payroll/models/state/ct_connecticut.py
new file mode 100644
index 00000000..344dc9c8
--- /dev/null
+++ b/l10n_us_hr_payroll/models/state/ct_connecticut.py
@@ -0,0 +1,76 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from .general import _state_applies, sit_wage
+
+
+def ct_connecticut_state_income_withholding(payslip, categories, worked_days, inputs):
+ """
+ Returns SIT eligible wage and rate.
+
+ :return: result, result_rate (wage, percent)
+ """
+ state_code = 'CT'
+ if not _state_applies(payslip, state_code):
+ 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()
+ additional = payslip.contract_id.us_payroll_config_value('state_income_tax_additional_withholding')
+ withholding_code = payslip.contract_id.us_payroll_config_value('ct_w4na_sit_code')
+ exemption_table = payslip.rule_parameter('us_ct_sit_personal_exemption_rate').get(withholding_code, [('inf', 0.0)])
+ initial_tax_tbl = payslip.rule_parameter('us_ct_sit_initial_tax_rate').get(withholding_code, [('inf', 0.0, 0.0)])
+ tax_table = payslip.rule_parameter('us_ct_sit_tax_rate').get(withholding_code, [('inf', 0.0)])
+ recapture_table = payslip.rule_parameter('us_ct_sit_recapture_rate').get(withholding_code, [('inf', 0.0)])
+ decimal_table = payslip.rule_parameter('us_ct_sit_decimal_rate').get(withholding_code, [('inf', 0.0)])
+
+ annual_wages = wage * pay_periods
+ personal_exemption = 0.0
+ for bracket in exemption_table:
+ if annual_wages <= float(bracket[0]):
+ personal_exemption = bracket[1]
+ break
+
+ withholding = 0.0
+ taxable_income = annual_wages - personal_exemption
+ if taxable_income < 0.0:
+ taxable_income = 0.0
+
+ if taxable_income:
+ initial_tax = 0.0
+ last = 0.0
+ for bracket in initial_tax_tbl:
+ if taxable_income <= float(bracket[0]):
+ initial_tax = bracket[1] + ((bracket[2] / 100.0) * (taxable_income - last))
+ break
+ last = bracket[0]
+
+ tax_add_back = 0.0
+ for bracket in tax_table:
+ if annual_wages <= float(bracket[0]):
+ tax_add_back = bracket[1]
+ break
+
+ recapture_amount = 0.0
+ for bracket in recapture_table:
+ if annual_wages <= float(bracket[0]):
+ recapture_amount = bracket[1]
+ break
+
+ withholding = initial_tax + tax_add_back + recapture_amount
+ decimal_amount = 1.0
+ for bracket in decimal_table:
+ if annual_wages <= float(bracket[0]):
+ decimal_amount= bracket[1]
+ break
+
+ withholding = withholding * (1.00 - decimal_amount)
+ if withholding < 0.0:
+ withholding = 0.0
+ withholding /= pay_periods
+
+ withholding += additional
+ return wage, -((withholding / wage) * 100.0)
diff --git a/l10n_us_hr_payroll/models/state/de_delaware.py b/l10n_us_hr_payroll/models/state/de_delaware.py
new file mode 100644
index 00000000..b2588e5d
--- /dev/null
+++ b/l10n_us_hr_payroll/models/state/de_delaware.py
@@ -0,0 +1,49 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from .general import _state_applies, sit_wage
+
+
+def de_delaware_state_income_withholding(payslip, categories, worked_days, inputs):
+ """
+ Returns SIT eligible wage and rate.
+
+ :return: result, result_rate (wage, percent)
+ """
+ state_code = 'DE'
+ if not _state_applies(payslip, state_code):
+ return 0.0, 0.0
+
+ # Determine Wage
+ wage = sit_wage(payslip, categories)
+ if not wage:
+ return 0.0, 0.0
+
+ filing_status = payslip.contract_id.us_payroll_config_value('de_w4_sit_filing_status')
+ if not filing_status:
+ return 0.0, 0.0
+
+ pay_periods = payslip.dict.get_pay_periods_in_year()
+ additional = payslip.contract_id.us_payroll_config_value('state_income_tax_additional_withholding')
+ tax_table = payslip.rule_parameter('us_de_sit_tax_rate')
+ personal_exemption = payslip.rule_parameter('us_de_sit_personal_exemption_rate')
+ allowances = payslip.contract_id.us_payroll_config_value('de_w4_sit_dependent')
+ standard_deduction = payslip.rule_parameter('us_de_sit_standard_deduction_rate')
+
+ taxable_income = wage * pay_periods
+ if filing_status == 'single':
+ taxable_income -= standard_deduction
+ else:
+ taxable_income -= standard_deduction * 2
+
+ withholding = 0.0
+ last = 0.0
+ for row in tax_table:
+ if taxable_income <= float(row[0]):
+ withholding = (row[1] + ((row[2] / 100.0) * (taxable_income - last)) - (allowances * personal_exemption))
+ break
+ last = row[0]
+
+ withholding = max(withholding, 0.0)
+ withholding = withholding / pay_periods
+ withholding += additional
+ return wage, -((withholding / wage) * 100.0)
diff --git a/l10n_us_hr_payroll/models/state/ga_georgia.py b/l10n_us_hr_payroll/models/state/ga_georgia.py
new file mode 100644
index 00000000..77fb0044
--- /dev/null
+++ b/l10n_us_hr_payroll/models/state/ga_georgia.py
@@ -0,0 +1,51 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from .general import _state_applies, sit_wage
+
+
+def ga_georgia_state_income_withholding(payslip, categories, worked_days, inputs):
+ """
+ Returns SIT eligible wage and rate.
+
+ :return: result, result_rate (wage, percent)
+ """
+ state_code = 'GA'
+ if not _state_applies(payslip, state_code):
+ return 0.0, 0.0
+ ga_filing_status = payslip.contract_id.us_payroll_config_value('ga_g4_sit_filing_status')
+ if not ga_filing_status:
+ return 0.0, 0.0
+
+ # Determine Wage
+ wage = sit_wage(payslip, categories)
+ if not wage:
+ return 0.0, 0.0
+
+ schedule_pay = payslip.contract_id.schedule_pay
+ additional = payslip.contract_id.us_payroll_config_value('state_income_tax_additional_withholding')
+ dependent_allowances = payslip.contract_id.us_payroll_config_value('ga_g4_sit_dependent_allowances')
+ additional_allowances = payslip.contract_id.us_payroll_config_value('ga_g4_sit_additional_allowances')
+ dependent_allowance_rate = payslip.rule_parameter('us_ga_sit_dependent_allowance_rate').get(schedule_pay)
+ personal_allowance = payslip.rule_parameter('us_ga_sit_personal_allowance').get(ga_filing_status, {}).get(schedule_pay)
+ deduction = payslip.rule_parameter('us_ga_sit_deduction').get(ga_filing_status, {}).get(schedule_pay)
+ withholding_rate = payslip.rule_parameter('us_ga_sit_rate').get(ga_filing_status, {}).get(schedule_pay)
+ if not all((dependent_allowance_rate, personal_allowance, deduction, withholding_rate)):
+ return 0.0, 0.0
+
+ after_standard_deduction = wage - deduction
+ allowances = dependent_allowances + additional_allowances
+ working_wages = after_standard_deduction - (personal_allowance + (allowances * dependent_allowance_rate))
+
+ withholding = 0.0
+ if working_wages > 0.0:
+ prior_row_base = 0.0
+ for row in withholding_rate:
+ wage_base, base, rate = row
+ wage_base = float(wage_base)
+ if working_wages < wage_base:
+ withholding = base + ((working_wages - prior_row_base) * rate / 100.0)
+ break
+ prior_row_base = wage_base
+
+ withholding += additional
+ return wage, -((withholding / wage) * 100.0)
diff --git a/l10n_us_hr_payroll/models/state/general.py b/l10n_us_hr_payroll/models/state/general.py
new file mode 100644
index 00000000..8979782f
--- /dev/null
+++ b/l10n_us_hr_payroll/models/state/general.py
@@ -0,0 +1,131 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+from odoo.exceptions import UserError
+from ..federal.fed_940 import futa_wage, futa_wage_ytd
+from ..federal.fed_941 import fit_wage, fit_wage_ytd
+
+# import logging
+# _logger = logging.getLogger(__name__)
+
+suta_wage = futa_wage
+suta_wage_ytd = futa_wage_ytd
+sit_wage = fit_wage
+sit_wage_ytd = fit_wage_ytd
+
+
+def _state_applies(payslip, state_code):
+ return state_code == payslip.contract_id.us_payroll_config_value('state_code')
+
+
+# Export for eval context
+is_us_state = _state_applies
+
+
+def _general_rate(payslip, wage, ytd_wage, wage_base=None, wage_start=None, rate=None):
+ """
+ Function parameters:
+ wage_base, wage_start, rate can either be strings (rule_parameters) or floats
+ :return: result, result_rate(wage, percent)
+ """
+
+ # Resolve parameters. On exception, return (probably missing a year, would rather not have exception)
+ if wage_base and isinstance(wage_base, str):
+ try:
+ wage_base = payslip.rule_parameter(wage_base)
+ except (KeyError, UserError):
+ return 0.0, 0.0
+
+ if wage_start and isinstance(wage_start, str):
+ try:
+ wage_start = payslip.rule_parameter(wage_start)
+ except (KeyError, UserError):
+ return 0.0, 0.0
+
+ if rate and isinstance(rate, str):
+ try:
+ rate = payslip.rule_parameter(rate)
+ except (KeyError, UserError):
+ return 0.0, 0.0
+
+ if not rate:
+ return 0.0, 0.0
+ else:
+ # Rate assumed positive percentage!
+ rate = -rate
+
+ if wage_base:
+ remaining = wage_base - ytd_wage
+ if remaining < 0.0:
+ result = 0.0
+ elif remaining < wage:
+ result = remaining
+ else:
+ result = wage
+
+ # _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))
+ return wage, rate
+ if ytd_wage + wage <= wage_start:
+ # _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))
+ 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))
+ 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 + DED_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.contract_id.futa_type in (payslip.contract_id.FUTA_TYPE_EXEMPT, payslip.contract_id.FUTA_TYPE_BASIC):
+ return 0.0, 0.0
+
+ # Determine Wage
+ wage = suta_wage(payslip, categories)
+ if not wage:
+ return 0.0, 0.0
+
+ ytd_wage = suta_wage_ytd(payslip, categories)
+
+ 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 SIT eligible wage and rate.
+ WAGE = GROSS + DED_FIT_EXEMPT
+
+ :return: result, result_rate (wage, percent)
+ """
+ if not _state_applies(payslip, state_code):
+ return 0.0, 0.0
+
+ if payslip.contract_id.us_payroll_config_value('state_income_tax_exempt'):
+ return 0.0, 0.0
+
+ # Determine Wage
+ ytd_wage = sit_wage_ytd(payslip, categories)
+
+ wage = sit_wage(payslip, categories)
+ result, result_rate = _general_rate(payslip, wage, ytd_wage, wage_base=wage_base, wage_start=wage_start, rate=rate)
+ additional = payslip.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/state/hi_hawaii.py b/l10n_us_hr_payroll/models/state/hi_hawaii.py
new file mode 100644
index 00000000..42c51e3e
--- /dev/null
+++ b/l10n_us_hr_payroll/models/state/hi_hawaii.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, sit_wage
+
+
+def hi_hawaii_state_income_withholding(payslip, categories, worked_days, inputs):
+ """
+ Returns SIT eligible wage and rate.
+
+ :return: result, result_rate (wage, percent)
+ """
+ state_code = 'HI'
+ if not _state_applies(payslip, state_code):
+ return 0.0, 0.0
+
+ # Determine Wage
+ wage = sit_wage(payslip, categories)
+ if not wage:
+ return 0.0, 0.0
+
+ filing_status = payslip.contract_id.us_payroll_config_value('hi_hw4_sit_filing_status')
+ if not filing_status:
+ return 0.0, 0.0
+
+ pay_periods = payslip.dict.get_pay_periods_in_year()
+ additional = payslip.contract_id.us_payroll_config_value('state_income_tax_additional_withholding')
+ allowances = payslip.contract_id.us_payroll_config_value('hi_hw4_sit_allowances')
+ tax_table = payslip.rule_parameter('us_hi_sit_tax_rate')[filing_status]
+ personal_exemption = payslip.rule_parameter('us_hi_sit_personal_exemption_rate')
+
+ taxable_income = (wage * pay_periods) - (personal_exemption * allowances)
+ withholding = 0.0
+ last = 0.0
+ for row in tax_table:
+ if taxable_income <= float(row[0]):
+ withholding = row[1] + ((row[2] / 100.0) * (taxable_income - last))
+ break
+ last = row[0]
+
+ withholding = max(withholding, 0.0)
+ withholding = withholding / pay_periods
+ withholding += additional
+ return wage, -((withholding / wage) * 100.0)
diff --git a/l10n_us_hr_payroll/models/state/ia_iowa.py b/l10n_us_hr_payroll/models/state/ia_iowa.py
new file mode 100644
index 00000000..9bb9ac9d
--- /dev/null
+++ b/l10n_us_hr_payroll/models/state/ia_iowa.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 ia_iowa_state_income_withholding(payslip, categories, worked_days, inputs):
+ """
+ Returns SIT eligible wage and rate.
+
+ :return: result, result_rate (wage, percent)
+ """
+ state_code = 'IA'
+ if not _state_applies(payslip, state_code):
+ return 0.0, 0.0
+
+ if payslip.contract_id.us_payroll_config_value('state_income_tax_exempt'):
+ return 0.0, 0.0
+
+ # Determine Wage
+ wage = sit_wage(payslip, categories)
+ if not wage:
+ return 0.0, 0.0
+
+ schedule_pay = payslip.contract_id.schedule_pay
+ fed_withholding = categories.EE_US_941_FIT
+ additional = payslip.contract_id.us_payroll_config_value('state_income_tax_additional_withholding')
+ allowances = payslip.contract_id.us_payroll_config_value('ia_w4_sit_allowances')
+ standard_deduction = payslip.rule_parameter('us_ia_sit_standard_deduction_rate')[schedule_pay]
+ tax_table = payslip.rule_parameter('us_ia_sit_tax_rate')[schedule_pay]
+ deduction_per_allowance = payslip.rule_parameter('us_ia_sit_deduction_allowance_rate')[schedule_pay]
+
+ t1 = wage + fed_withholding
+ standard_deduction_amt = standard_deduction[0] if allowances < 2 else standard_deduction[1]
+ t2 = t1 - standard_deduction_amt
+ t3 = 0.0
+ last = 0.0
+ for row in tax_table:
+ cap, rate, flat_fee = row
+ if float(cap) > float(t2):
+ taxed_amount = t2 - last
+ t3 = flat_fee + (rate * taxed_amount)
+ break
+ last = cap
+ withholding = t3 - (deduction_per_allowance * allowances)
+
+ withholding = max(withholding, 0.0)
+ withholding += additional
+ return wage, -((withholding / wage) * 100.0)
diff --git a/l10n_us_hr_payroll/models/state/id_idaho.py b/l10n_us_hr_payroll/models/state/id_idaho.py
new file mode 100644
index 00000000..5bf503da
--- /dev/null
+++ b/l10n_us_hr_payroll/models/state/id_idaho.py
@@ -0,0 +1,41 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from .general import _state_applies, sit_wage
+
+
+def id_idaho_state_income_withholding(payslip, categories, worked_days, inputs):
+ """
+ Returns SIT eligible wage and rate.
+
+ :return: result, result_rate (wage, percent)
+ """
+ state_code = 'ID'
+ if not _state_applies(payslip, state_code):
+ return 0.0, 0.0
+
+ # Determine Wage
+ wage = sit_wage(payslip, categories)
+ if not wage:
+ return 0.0, 0.0
+
+ filing_status = payslip.contract_id.us_payroll_config_value('id_w4_sit_filing_status')
+ if not filing_status:
+ return 0.0, 0.0
+
+ schedule_pay = payslip.contract_id.schedule_pay
+ allowances = payslip.contract_id.us_payroll_config_value('id_w4_sit_allowances')
+ ictcat_table = payslip.rule_parameter('us_id_sit_ictcat_rate')[schedule_pay]
+ tax_table = payslip.rule_parameter('us_id_sit_tax_rate')[filing_status].get(schedule_pay)
+
+ taxable_income = wage - (ictcat_table * allowances)
+ withholding = 0.0
+ last = 0.0
+ for row in tax_table:
+ if taxable_income <= float(row[0]):
+ withholding = row[1] + ((row[2] / 100.0) * (taxable_income - last))
+ break
+ last = row[0]
+
+ withholding = max(withholding, 0.0)
+ withholding = round(withholding)
+ return wage, -((withholding / wage) * 100.0)
diff --git a/l10n_us_hr_payroll/models/state/il_illinois.py b/l10n_us_hr_payroll/models/state/il_illinois.py
new file mode 100644
index 00000000..6c8919c4
--- /dev/null
+++ b/l10n_us_hr_payroll/models/state/il_illinois.py
@@ -0,0 +1,35 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from .general import _state_applies, sit_wage
+
+
+def il_illinois_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 = 'IL'
+ if not _state_applies(payslip, state_code):
+ 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()
+ additional = payslip.contract_id.us_payroll_config_value('state_income_tax_additional_withholding')
+ basic_allowances_rate = payslip.rule_parameter('us_il_sit_basic_allowances_rate')
+ additional_allowances_rate = payslip.rule_parameter('us_il_sit_additional_allowances_rate')
+ basic_allowances = payslip.contract_id.us_payroll_config_value('il_w4_sit_basic_allowances')
+ additional_allowances = payslip.contract_id.us_payroll_config_value('il_w4_sit_additional_allowances')
+
+ rate = 4.95 / 100.0
+ withholding = rate * (wage - (((basic_allowances * basic_allowances_rate) + (additional_allowances *
+ additional_allowances_rate)) / pay_periods))
+ if withholding < 0.0:
+ withholding = 0.0
+ withholding += additional
+ return wage, -((withholding / wage) * 100.0)
diff --git a/l10n_us_hr_payroll/models/state/in_indiana.py b/l10n_us_hr_payroll/models/state/in_indiana.py
new file mode 100644
index 00000000..0b6bd03e
--- /dev/null
+++ b/l10n_us_hr_payroll/models/state/in_indiana.py
@@ -0,0 +1,34 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from .general import _state_applies, sit_wage
+
+
+def in_indiana_state_income_withholding(payslip, categories, worked_days, inputs):
+ """
+ Returns SIT eligible wage and rate.
+
+ :return: result, result_rate (wage, percent)
+ """
+ state_code = 'IN'
+ if not _state_applies(payslip, state_code):
+ return 0.0, 0.0
+
+ # Determine Wage
+ wage = sit_wage(payslip, categories)
+ if not wage:
+ return 0.0, 0.0
+
+ schedule_pay = payslip.contract_id.schedule_pay
+ additional = payslip.contract_id.us_payroll_config_value('state_income_tax_additional_withholding')
+ personal_exemption = payslip.contract_id.us_payroll_config_value('in_w4_sit_personal_exemption')
+ personal_exemption_rate = payslip.rule_parameter('us_in_sit_personal_exemption_rate')[schedule_pay][personal_exemption - 1]
+ dependent_exemption = payslip.contract_id.us_payroll_config_value('in_w4_sit_dependent_exemption')
+ dependent_exemption_rate = payslip.rule_parameter('us_in_sit_dependent_exemption_rate')[schedule_pay][dependent_exemption - 1]
+ income_tax_rate = payslip.rule_parameter('us_in_suta_income_rate')
+
+ taxable_income = wage - (personal_exemption_rate + dependent_exemption_rate)
+ withholding = taxable_income * (income_tax_rate / 100.0)
+
+ withholding = max(withholding, 0.0)
+ withholding += additional
+ return wage, -((withholding / wage) * 100.0)
diff --git a/l10n_us_hr_payroll/models/state/ks_kansas.py b/l10n_us_hr_payroll/models/state/ks_kansas.py
new file mode 100644
index 00000000..1e7398d0
--- /dev/null
+++ b/l10n_us_hr_payroll/models/state/ks_kansas.py
@@ -0,0 +1,44 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from .general import _state_applies, sit_wage
+
+
+def ks_kansas_state_income_withholding(payslip, categories, worked_days, inputs):
+ """
+ Returns SIT eligible wage and rate.
+
+ :return: result, result_rate (wage, percent)
+ """
+ state_code = 'KS'
+ if not _state_applies(payslip, state_code):
+ return 0.0, 0.0
+
+ # Determine Wage
+ wage = sit_wage(payslip, categories)
+ if not wage:
+ return 0.0, 0.0
+
+ filing_status = payslip.contract_id.us_payroll_config_value('ks_k4_sit_filing_status')
+ if not filing_status:
+ return 0.0, 0.0
+
+ schedule_pay = payslip.contract_id.schedule_pay
+ additional = payslip.contract_id.us_payroll_config_value('state_income_tax_additional_withholding')
+ allowances = payslip.contract_id.us_payroll_config_value('ks_k4_sit_allowances')
+ allowances_amt = payslip.rule_parameter('us_ks_sit_allowances_rate')[schedule_pay]
+ tax_table = payslip.rule_parameter('us_ks_sit_tax_rate')[filing_status].get(schedule_pay)
+
+ taxable_income = wage - (allowances * allowances_amt)
+ withholding = 0.0
+ last = 0.0
+ for row in tax_table:
+ amt, rate, flat_fee = row
+ if wage <= float(amt):
+ withholding = ((taxable_income - last) * (rate / 100)) + flat_fee
+ break
+ last = amt
+
+ withholding = max(withholding, 0.0)
+ withholding += additional
+ withholding = round(withholding)
+ return wage, -((withholding / wage) * 100.0)
diff --git a/l10n_us_hr_payroll/models/state/ky_kentucky.py b/l10n_us_hr_payroll/models/state/ky_kentucky.py
new file mode 100644
index 00000000..ab580880
--- /dev/null
+++ b/l10n_us_hr_payroll/models/state/ky_kentucky.py
@@ -0,0 +1,32 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from .general import _state_applies, sit_wage
+
+
+def ky_kentucky_state_income_withholding(payslip, categories, worked_days, inputs):
+ """
+ Returns SIT eligible wage and rate.
+
+ :return: result, result_rate (wage, percent)
+ """
+ state_code = 'KY'
+ if not _state_applies(payslip, state_code):
+ 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()
+ additional = payslip.contract_id.us_payroll_config_value('state_income_tax_additional_withholding')
+ tax_rate = payslip.rule_parameter('us_ky_sit_tax_rate')
+ standard_deduction = payslip.rule_parameter('us_ky_sit_standard_deduction_rate')
+
+ taxable_income = (wage * pay_periods) - standard_deduction
+ withholding = taxable_income * (tax_rate / 100)
+
+ withholding = max(withholding, 0.0)
+ withholding /= pay_periods
+ withholding += additional
+ return wage, -((withholding / wage) * 100.0)
diff --git a/l10n_us_hr_payroll/models/state/la_louisiana.py b/l10n_us_hr_payroll/models/state/la_louisiana.py
new file mode 100644
index 00000000..c580d5a2
--- /dev/null
+++ b/l10n_us_hr_payroll/models/state/la_louisiana.py
@@ -0,0 +1,62 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from .general import _state_applies, sit_wage
+
+
+def la_louisiana_state_income_withholding(payslip, categories, worked_days, inputs):
+ """
+ Returns SIT eligible wage and rate.
+
+ :return: result, result_rate (wage, percent)
+ """
+ state_code = 'LA'
+ if not _state_applies(payslip, state_code):
+ return 0.0, 0.0
+
+ # Determine Wage
+ wage = sit_wage(payslip, categories)
+ if not wage:
+ return 0.0, 0.0
+
+ filing_status = payslip.contract_id.us_payroll_config_value('la_l4_sit_filing_status')
+ if not filing_status:
+ return 0.0, 0.0
+
+ pay_periods = payslip.dict.get_pay_periods_in_year()
+ personal_exemptions = payslip.contract_id.us_payroll_config_value('la_l4_sit_exemptions')
+ additional = payslip.contract_id.us_payroll_config_value('state_income_tax_additional_withholding')
+ dependent_exemptions = payslip.contract_id.us_payroll_config_value('la_l4_sit_dependents')
+ tax_table = payslip.rule_parameter('us_la_sit_tax_rate')[filing_status]
+ exemption_rate = payslip.rule_parameter('us_la_sit_personal_exemption_rate')
+ dependent_rate = payslip.rule_parameter('us_la_sit_dependent_rate')
+
+ annual_wage = wage * pay_periods
+
+ effect_cap = 0.0
+ multiplier = 0.0
+ if filing_status == 'single':
+ effect_cap = 12500.00
+ multiplier = 1.60
+ elif filing_status == 'married':
+ effect_cap = 25000.00
+ multiplier = 1.65
+
+ after_credits_under = (2.100 / 100) * (((personal_exemptions * exemption_rate) +
+ (dependent_exemptions * dependent_rate)) / pay_periods)
+ after_credits_over = 0.00
+ if after_credits_under > effect_cap:
+ after_credits_under = effect_cap
+ after_credits_over_check = ((personal_exemptions * exemption_rate) + (dependent_exemptions * dependent_rate)) - effect_cap
+ after_credits_over = (multiplier / 100.00) * (after_credits_over_check / pay_periods) if after_credits_over_check > 0 else 0.00
+ withholding = 0.0
+ last = 0.0
+ for amt, rate in tax_table:
+ withholding = withholding + ((rate / 100.0) * (wage - (last / pay_periods)))
+ if annual_wage <= float(amt):
+ break
+ last = amt
+
+ withholding = withholding - (after_credits_under + after_credits_over)
+ withholding = round(withholding, 2)
+ withholding += additional
+ return wage, -((withholding / wage) * 100.0)
diff --git a/l10n_us_hr_payroll/models/state/me_maine.py b/l10n_us_hr_payroll/models/state/me_maine.py
new file mode 100644
index 00000000..0accc6ff
--- /dev/null
+++ b/l10n_us_hr_payroll/models/state/me_maine.py
@@ -0,0 +1,62 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from .general import _state_applies, sit_wage
+
+
+def me_maine_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 = 'ME'
+ if not _state_applies(payslip, state_code):
+ return 0.0, 0.0
+
+ # Determine Wage
+ wage = sit_wage(payslip, categories)
+ if not wage:
+ return 0.0, 0.0
+
+ filing_status = payslip.contract_id.us_payroll_config_value('me_w4me_sit_filing_status')
+ if not filing_status:
+ return 0.0, 0.0
+
+ exempt = payslip.contract_id.us_payroll_config_value('state_income_tax_exempt')
+ if exempt:
+ return 0.0, 0.0
+
+ pay_periods = payslip.dict.get_pay_periods_in_year()
+ additional = payslip.contract_id.us_payroll_config_value('state_income_tax_additional_withholding')
+ allowances = payslip.contract_id.us_payroll_config_value('me_w4me_sit_allowances')
+ tax_rate = payslip.rule_parameter('us_me_sit_tax_rate')[filing_status]
+ personal_exemption = payslip.rule_parameter('us_me_sit_personal_exemption_rate')
+ standard_deduction = payslip.rule_parameter('us_me_sit_standard_deduction_rate')[filing_status]
+
+ taxable_income = wage * pay_periods
+ exemption_amt = allowances * personal_exemption
+ last = 0.0
+ standard_deduction_amt = 0.0
+ for row in standard_deduction:
+ amt, flat_amt = row
+ if taxable_income < 82900:
+ standard_deduction_amt = flat_amt
+ break
+ elif taxable_income < amt:
+ standard_deduction_amt = last * (amt - taxable_income) / flat_amt
+ break
+ last = flat_amt
+ annual_income = taxable_income - (exemption_amt + standard_deduction_amt)
+ withholding = 0.0
+ for row in tax_rate:
+ amt, flat_fee, rate = row
+ if annual_income < float(amt):
+ withholding = ((annual_income - last) * (rate / 100)) + flat_fee
+ break
+ last = amt
+
+ withholding = max(withholding, 0.0)
+ withholding = round(withholding / pay_periods)
+ withholding += additional
+ return wage, -((withholding / wage) * 100.0)
diff --git a/l10n_us_hr_payroll/models/state/mi_michigan.py b/l10n_us_hr_payroll/models/state/mi_michigan.py
new file mode 100644
index 00000000..f9656529
--- /dev/null
+++ b/l10n_us_hr_payroll/models/state/mi_michigan.py
@@ -0,0 +1,35 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from .general import _state_applies, sit_wage
+
+
+def mi_michigan_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 = 'MI'
+ if not _state_applies(payslip, state_code):
+ return 0.0, 0.0
+
+ if payslip.contract_id.us_payroll_config_value('state_income_tax_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()
+ additional = payslip.contract_id.us_payroll_config_value('state_income_tax_additional_withholding')
+ exemption_rate = payslip.rule_parameter('us_mi_sit_exemption_rate')
+ exemption = payslip.contract_id.us_payroll_config_value('mi_w4_sit_exemptions')
+
+ annual_exemption = (exemption * exemption_rate) / pay_periods
+ withholding = ((wage - annual_exemption) * 0.0425)
+ if withholding < 0.0:
+ withholding = 0.0
+ withholding += additional
+ return wage, -((withholding / wage) * 100.0)
diff --git a/l10n_us_hr_payroll/models/state/mn_minnesota.py b/l10n_us_hr_payroll/models/state/mn_minnesota.py
new file mode 100644
index 00000000..c626bc3b
--- /dev/null
+++ b/l10n_us_hr_payroll/models/state/mn_minnesota.py
@@ -0,0 +1,44 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from .general import _state_applies, sit_wage
+
+
+def mn_minnesota_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 = 'MN'
+ if not _state_applies(payslip, state_code):
+ return 0.0, 0.0
+
+ filing_status = payslip.contract_id.us_payroll_config_value('mn_w4mn_sit_filing_status')
+ if not filing_status:
+ 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()
+ additional = payslip.contract_id.us_payroll_config_value('state_income_tax_additional_withholding')
+ sit_tax_rate = payslip.rule_parameter('us_mn_sit_tax_rate')[filing_status]
+ allowances_rate = payslip.rule_parameter('us_mn_sit_allowances_rate')
+ allowances = payslip.contract_id.us_payroll_config_value('mn_w4mn_sit_allowances')
+
+ taxable_income = (wage * pay_periods) - (allowances * allowances_rate)
+ withholding = 0.0
+ for row in sit_tax_rate:
+ cap, subtract_amt, rate, flat_fee = row
+ cap = float(cap)
+ if cap > taxable_income:
+ withholding = ((rate / 100.00) * (taxable_income - subtract_amt)) + flat_fee
+ break
+ withholding = round(withholding / pay_periods)
+ if withholding < 0.0:
+ withholding = 0.0
+ withholding += additional
+ return wage, -((withholding / wage) * 100.0)
diff --git a/l10n_us_hr_payroll/models/state/mo_missouri.py b/l10n_us_hr_payroll/models/state/mo_missouri.py
new file mode 100644
index 00000000..47e56639
--- /dev/null
+++ b/l10n_us_hr_payroll/models/state/mo_missouri.py
@@ -0,0 +1,53 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from .general import _state_applies, sit_wage
+
+
+def mo_missouri_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 = 'MO'
+ if not _state_applies(payslip, state_code):
+ return 0.0, 0.0
+
+ filing_status = payslip.contract_id.us_payroll_config_value('mo_mow4_sit_filing_status')
+ if not filing_status:
+ return 0.0, 0.0
+
+ # Determine Wage
+ wage = sit_wage(payslip, categories)
+ if not wage:
+ return 0.0, 0.0
+
+ reduced_withholding = payslip.contract_id.us_payroll_config_value('mo_mow4_sit_withholding')
+ if reduced_withholding:
+ return wage, -((reduced_withholding / wage) * 100.0)
+
+ pay_periods = payslip.dict.get_pay_periods_in_year()
+ additional = payslip.contract_id.us_payroll_config_value('state_income_tax_additional_withholding')
+ sit_table = payslip.rule_parameter('us_mo_sit_rate')
+ deduction = payslip.rule_parameter('us_mo_sit_deduction')[filing_status]
+
+ gross_taxable_income = wage * pay_periods
+ gross_taxable_income -= deduction
+
+ remaining_taxable_income = gross_taxable_income
+ withholding = 0.0
+ for amt, rate in sit_table:
+ amt = float(amt)
+ rate = rate / 100.0
+ if (remaining_taxable_income - amt) > 0.0 or (remaining_taxable_income - amt) == 0.0:
+ withholding += rate * amt
+ else:
+ withholding += rate * remaining_taxable_income
+ break
+ remaining_taxable_income = remaining_taxable_income - amt
+
+ withholding /= pay_periods
+ withholding += additional
+ withholding = round(withholding)
+ return wage, -((withholding / wage) * 100.0)
diff --git a/l10n_us_hr_payroll/models/state/ms_mississippi.py b/l10n_us_hr_payroll/models/state/ms_mississippi.py
new file mode 100644
index 00000000..10f30ee2
--- /dev/null
+++ b/l10n_us_hr_payroll/models/state/ms_mississippi.py
@@ -0,0 +1,46 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from .general import _state_applies, sit_wage
+
+
+def ms_mississippi_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 = 'MS'
+ if not _state_applies(payslip, state_code):
+ return 0.0, 0.0
+
+ filing_status = payslip.contract_id.us_payroll_config_value('ms_89_350_sit_filing_status')
+ if not filing_status:
+ 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()
+ additional = payslip.contract_id.us_payroll_config_value('state_income_tax_additional_withholding')
+ exemptions = payslip.contract_id.us_payroll_config_value('ms_89_350_sit_exemption_value')
+ standard_deduction = payslip.rule_parameter('us_ms_sit_deduction').get(filing_status)
+ withholding_rate = payslip.rule_parameter('us_ms_sit_rate')
+
+ wage_annual = wage * pay_periods
+ taxable_income = wage_annual - (exemptions + standard_deduction)
+ if taxable_income <= 0.01:
+ return wage, 0.0
+
+ withholding = 0.0
+ for row in withholding_rate:
+ wage_base, base, rate = row
+ if taxable_income >= wage_base:
+ withholding = base + ((taxable_income - wage_base) * rate)
+ break
+ withholding /= pay_periods
+ withholding = round(withholding)
+ withholding += round(additional)
+ return wage, -((withholding / wage) * 100.0)
diff --git a/l10n_us_hr_payroll/models/state/mt_montana.py b/l10n_us_hr_payroll/models/state/mt_montana.py
new file mode 100644
index 00000000..6e33261a
--- /dev/null
+++ b/l10n_us_hr_payroll/models/state/mt_montana.py
@@ -0,0 +1,45 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from .general import _state_applies, sit_wage
+
+
+def mt_montana_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 = 'MT'
+ if not _state_applies(payslip, state_code):
+ return 0.0, 0.0
+
+ if payslip.contract_id.us_payroll_config_value('mt_mw4_sit_exempt'):
+ return 0.0, 0.0
+
+ # Determine Wage
+ wage = sit_wage(payslip, categories)
+ if not wage:
+ return 0.0, 0.0
+
+ schedule_pay = payslip.contract_id.schedule_pay
+ additional = payslip.contract_id.us_payroll_config_value('state_income_tax_additional_withholding')
+ exemptions = payslip.contract_id.us_payroll_config_value('mt_mw4_sit_exemptions')
+ exemption_rate = payslip.rule_parameter('us_mt_sit_exemption_rate').get(schedule_pay)
+ withholding_rate = payslip.rule_parameter('us_mt_sit_rate').get(schedule_pay)
+ if not exemption_rate or not withholding_rate:
+ return 0.0, 0.0
+
+ adjusted_wage = wage - (exemption_rate * (exemptions or 0))
+ withholding = 0.0
+ if adjusted_wage > 0.0:
+ prior_wage_cap = 0.0
+ for row in withholding_rate:
+ wage_cap, base, rate = row
+ wage_cap = float(wage_cap) # e.g. 'inf'
+ if adjusted_wage < wage_cap:
+ withholding = round(base + ((rate / 100.0) * (adjusted_wage - prior_wage_cap)))
+ break
+ prior_wage_cap = wage_cap
+ withholding += additional
+ return wage, -((withholding / wage) * 100.0)
diff --git a/l10n_us_hr_payroll/models/state/nc_northcarolina.py b/l10n_us_hr_payroll/models/state/nc_northcarolina.py
new file mode 100644
index 00000000..056d1fe8
--- /dev/null
+++ b/l10n_us_hr_payroll/models/state/nc_northcarolina.py
@@ -0,0 +1,38 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from .general import _state_applies, sit_wage
+
+
+def nc_northcarolina_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 = 'NC'
+ if not _state_applies(payslip, state_code):
+ return 0.0, 0.0
+
+ filing_status = payslip.contract_id.us_payroll_config_value('nc_nc4_sit_filing_status')
+ if not filing_status:
+ return 0.0, 0.0
+
+ # Determine Wage
+ wage = sit_wage(payslip, categories)
+ if not wage:
+ return 0.0, 0.0
+
+ schedule_pay = payslip.contract_id.schedule_pay
+ additional = payslip.contract_id.us_payroll_config_value('state_income_tax_additional_withholding')
+ allowances = payslip.contract_id.us_payroll_config_value('nc_nc4_sit_allowances')
+ allowances_rate = payslip.rule_parameter('us_nc_sit_allowance_rate').get(schedule_pay)['allowance']
+ deduction = payslip.rule_parameter('us_nc_sit_allowance_rate').get(schedule_pay)['standard_deduction'] if filing_status != 'head_household' else payslip.rule_parameter('us_nc_sit_allowance_rate').get(schedule_pay)['standard_deduction_hh']
+
+ taxable_wage = round((wage - (deduction + (allowances * allowances_rate))) * 0.0535)
+ withholding = 0.0
+ if taxable_wage < 0.0:
+ withholding -= taxable_wage
+ withholding = taxable_wage
+ withholding += additional
+ return wage, -((withholding / wage) * 100.0)
diff --git a/l10n_us_hr_payroll/models/state/nd_north_dakota.py b/l10n_us_hr_payroll/models/state/nd_north_dakota.py
new file mode 100644
index 00000000..1ef4ecbd
--- /dev/null
+++ b/l10n_us_hr_payroll/models/state/nd_north_dakota.py
@@ -0,0 +1,45 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from .general import _state_applies, sit_wage
+
+
+def nd_north_dakota_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 = 'ND'
+ if not _state_applies(payslip, state_code):
+ return 0.0, 0.0
+
+ # Determine Wage
+ wage = sit_wage(payslip, categories)
+ if not wage:
+ return 0.0, 0.0
+
+ filing_status = payslip.contract_id.us_payroll_config_value('nd_w4_sit_filing_status')
+ if not filing_status:
+ return 0.0, 0.0
+
+ schedule_pay = payslip.contract_id.schedule_pay
+ additional = payslip.contract_id.us_payroll_config_value('state_income_tax_additional_withholding')
+ allowance = payslip.contract_id.us_payroll_config_value('nd_w4_sit_allowances')
+ allowance_rate = payslip.rule_parameter('us_nd_sit_allowances_rate')[schedule_pay]
+ tax_rate = payslip.rule_parameter('us_nd_sit_tax_rate')[filing_status].get(schedule_pay)
+
+ taxable_income = wage - (allowance * allowance_rate)
+ withholding = 0.0
+ last = 0.0
+ for row in tax_rate:
+ amt, flat_fee, rate = row
+ if taxable_income < float(amt):
+ withholding = ((taxable_income - last) * (rate / 100)) + flat_fee
+ break
+ last = amt
+
+ withholding = round(withholding)
+ withholding = max(withholding, 0.0)
+ withholding += additional
+ return wage, -((withholding / wage) * 100.0)
diff --git a/l10n_us_hr_payroll/models/state/ne_nebraska.py b/l10n_us_hr_payroll/models/state/ne_nebraska.py
new file mode 100644
index 00000000..9b360778
--- /dev/null
+++ b/l10n_us_hr_payroll/models/state/ne_nebraska.py
@@ -0,0 +1,49 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from .general import _state_applies, sit_wage
+
+
+def ne_nebraska_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 = 'NE'
+ 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
+
+ filing_status = payslip.contract_id.us_payroll_config_value('ne_w4n_sit_filing_status')
+ if not filing_status:
+ return 0.0, 0.0
+
+ schedule_pay = payslip.contract_id.schedule_pay
+ additional = payslip.contract_id.us_payroll_config_value('state_income_tax_additional_withholding')
+ allowances = payslip.contract_id.us_payroll_config_value('ne_w4n_sit_allowances')
+ tax_rate = payslip.rule_parameter('us_ne_sit_tax_rate')[filing_status].get(schedule_pay)
+ sit_allowance = payslip.rule_parameter('us_ne_sit_allowances_rate')[schedule_pay]
+
+ allowance_amt = allowances * sit_allowance
+ taxable_income = wage - allowance_amt
+ withholding = 0.0
+ last = 0.0
+ for row in tax_rate:
+ amt, flat_fee, rate = row
+ if taxable_income < float(amt):
+ withholding = ((taxable_income - last) * (rate / 100)) + flat_fee
+ break
+ last = amt
+
+ withholding = max(withholding, 0.0)
+ withholding += additional
+ return wage, -((withholding / wage) * 100.0)
diff --git a/l10n_us_hr_payroll/models/state/nj_newjersey.py b/l10n_us_hr_payroll/models/state/nj_newjersey.py
new file mode 100644
index 00000000..f0a805b9
--- /dev/null
+++ b/l10n_us_hr_payroll/models/state/nj_newjersey.py
@@ -0,0 +1,52 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from .general import _state_applies, sit_wage
+
+
+def nj_newjersey_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 = 'NJ'
+ if not _state_applies(payslip, state_code):
+ return 0.0, 0.0
+
+ filing_status = payslip.contract_id.us_payroll_config_value('nj_njw4_sit_filing_status')
+ if not filing_status:
+ return 0.0, 0.0
+
+ # Determine Wage
+ wage = sit_wage(payslip, categories)
+ if not wage:
+ return 0.0, 0.0
+
+ allowances = payslip.contract_id.us_payroll_config_value('nj_njw4_sit_allowances')
+ sit_rate_table_key = payslip.contract_id.us_payroll_config_value('nj_njw4_sit_rate_table')
+ if not sit_rate_table_key and filing_status in ('single', 'married_joint'):
+ sit_rate_table_key = 'A'
+ elif not sit_rate_table_key:
+ sit_rate_table_key = 'B'
+ schedule_pay = payslip.contract_id.schedule_pay
+ additional = payslip.contract_id.us_payroll_config_value('state_income_tax_additional_withholding')
+ sit_table = payslip.rule_parameter('us_nj_sit_rate')[sit_rate_table_key].get(schedule_pay)
+ allowance_value = payslip.rule_parameter('us_nj_sit_allowance_rate')[schedule_pay]
+ if not allowances:
+ return 0.0, 0.0
+
+ gross_taxable_income = wage - (allowance_value * allowances)
+ withholding = 0.0
+ prior_wage_base = 0.0
+ for row in sit_table:
+ wage_base, base_amt, rate = row
+ wage_base = float(wage_base)
+ rate = rate / 100.0
+ if gross_taxable_income <= wage_base:
+ withholding = base_amt + ((gross_taxable_income - prior_wage_base) * rate)
+ break
+ prior_wage_base = wage_base
+
+ withholding += additional
+ return wage, -((withholding / wage) * 100.0)
diff --git a/l10n_us_hr_payroll/models/state/nm_new_mexico.py b/l10n_us_hr_payroll/models/state/nm_new_mexico.py
new file mode 100644
index 00000000..48bf1ae1
--- /dev/null
+++ b/l10n_us_hr_payroll/models/state/nm_new_mexico.py
@@ -0,0 +1,40 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from .general import _state_applies, sit_wage
+
+
+def nm_new_mexico_state_income_withholding(payslip, categories, worked_days, inputs):
+ """
+ Returns SIT eligible wage and rate.
+
+ :return: result, result_rate (wage, percent)
+ """
+ state_code = 'NM'
+ if not _state_applies(payslip, state_code):
+ return 0.0, 0.0
+
+ # Determine Wage
+ wage = sit_wage(payslip, categories)
+ if not wage:
+ return 0.0, 0.0
+
+ filing_status = payslip.contract_id.us_payroll_config_value('fed_941_fit_w4_filing_status')
+ if not filing_status:
+ return 0.0, 0.0
+
+ schedule_pay = payslip.contract_id.schedule_pay
+ additional = payslip.contract_id.us_payroll_config_value('state_income_tax_additional_withholding')
+ tax_table = payslip.rule_parameter('us_nm_sit_tax_rate')[filing_status].get(schedule_pay)
+
+ taxable_income = wage
+ withholding = 0.0
+ last = 0.0
+ for row in tax_table:
+ if taxable_income <= float(row[0]):
+ withholding = row[1] + ((row[2] / 100.0) * (taxable_income - last))
+ break
+ last = row[0]
+
+ withholding = max(withholding, 0.0)
+ withholding += additional
+ return wage, -((withholding / wage) * 100.0)
diff --git a/l10n_us_hr_payroll/models/state/ny_new_york.py b/l10n_us_hr_payroll/models/state/ny_new_york.py
new file mode 100644
index 00000000..1a710b32
--- /dev/null
+++ b/l10n_us_hr_payroll/models/state/ny_new_york.py
@@ -0,0 +1,54 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from .general import _state_applies, sit_wage
+
+
+def ny_new_york_state_income_withholding(payslip, categories, worked_days, inputs):
+ """
+ Returns SIT eligible wage and rate.
+
+ :return: result, result_rate (wage, percent)
+ """
+ state_code = 'NY'
+ if not _state_applies(payslip, state_code):
+ return 0.0, 0.0
+
+ # Determine Wage
+ wage = sit_wage(payslip, categories)
+ if not wage:
+ return 0.0, 0.0
+
+ filing_status = payslip.contract_id.us_payroll_config_value('ny_it2104_sit_filing_status')
+ if not filing_status:
+ return 0.0, 0.0
+
+ schedule_pay = payslip.contract_id.schedule_pay
+ additional = payslip.contract_id.us_payroll_config_value('state_income_tax_additional_withholding')
+ tax_table = payslip.rule_parameter('us_ny_sit_tax_rate')[filing_status].get(schedule_pay)
+ allowances = payslip.contract_id.us_payroll_config_value('ny_it2104_sit_allowances')
+ over_10_deduction = payslip.rule_parameter('us_ny_sit_over_10_exemption_rate')[schedule_pay]
+ deduction_exemption = payslip.rule_parameter('us_ny_sit_deduction_exemption_rate')[filing_status].get(schedule_pay)
+
+ if allowances > 10:
+ if filing_status == 'single':
+ wage -= over_10_deduction[0] + over_10_deduction[2] * allowances
+ elif filing_status == 'married':
+ wage -= over_10_deduction[1] + over_10_deduction[2] * allowances
+
+ else:
+ if filing_status == 'single':
+ wage -= deduction_exemption[allowances]
+ elif filing_status == 'married':
+ wage -= deduction_exemption[allowances]
+ last = 0.0
+ withholding = 0.0
+ for row in tax_table:
+ amt, rate, flat_fee = row
+ if wage <= float(amt):
+ withholding = ((wage - last) * rate) + flat_fee
+ break
+ last = amt
+
+ withholding = max(withholding, 0.0)
+ withholding += additional
+ return wage, -((withholding / wage) * 100.0)
diff --git a/l10n_us_hr_payroll/models/state/oh_ohio.py b/l10n_us_hr_payroll/models/state/oh_ohio.py
new file mode 100644
index 00000000..5a7c3869
--- /dev/null
+++ b/l10n_us_hr_payroll/models/state/oh_ohio.py
@@ -0,0 +1,47 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from .general import _state_applies, sit_wage
+
+
+def oh_ohio_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 = 'OH'
+ if not _state_applies(payslip, state_code):
+ return 0.0, 0.0
+
+ if payslip.contract_id.us_payroll_config_value('state_income_tax_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()
+ additional = payslip.contract_id.us_payroll_config_value('state_income_tax_additional_withholding')
+ exemptions = payslip.contract_id.us_payroll_config_value('oh_it4_sit_exemptions')
+ exemption_rate = payslip.rule_parameter('us_oh_sit_exemption_rate')
+ withholding_rate = payslip.rule_parameter('us_oh_sit_rate')
+ multiplier_rate = payslip.rule_parameter('us_oh_sit_multiplier')
+
+ taxable_wage = (wage * pay_periods) - (exemption_rate * (exemptions or 0))
+ withholding = 0.0
+ if taxable_wage > 0.0:
+ prior_wage_cap = 0.0
+ for row in withholding_rate:
+ wage_cap, base, rate = row
+ wage_cap = float(wage_cap) # e.g. 'inf'
+ if taxable_wage < wage_cap:
+ withholding = base + (rate * (taxable_wage - prior_wage_cap))
+ break
+ prior_wage_cap = wage_cap
+ # Normalize to pay periods
+ withholding /= pay_periods
+ withholding *= multiplier_rate
+ withholding += additional
+ return wage, -((withholding / wage) * 100.0)
diff --git a/l10n_us_hr_payroll/models/state/ok_oklahoma.py b/l10n_us_hr_payroll/models/state/ok_oklahoma.py
new file mode 100644
index 00000000..bc0ecc24
--- /dev/null
+++ b/l10n_us_hr_payroll/models/state/ok_oklahoma.py
@@ -0,0 +1,47 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from .general import _state_applies, sit_wage
+
+
+def ok_oklahoma_state_income_withholding(payslip, categories, worked_days, inputs):
+ """
+ Returns SIT eligible wage and rate.
+
+ :return: result, result_rate (wage, percent)
+ """
+ state_code = 'OK'
+ if not _state_applies(payslip, state_code):
+ return 0.0, 0.0
+
+ # Determine Wage
+ wage = sit_wage(payslip, categories)
+ if not wage:
+ return 0.0, 0.0
+
+ if payslip.contract_id.us_payroll_config_value('state_income_tax_exempt'):
+ return 0.0, 0.0
+
+ filing_status = payslip.contract_id.us_payroll_config_value('ok_w4_sit_filing_status')
+ if not filing_status:
+ return 0.0, 0.0
+
+ schedule_pay = payslip.contract_id.schedule_pay
+ additional = payslip.contract_id.us_payroll_config_value('state_income_tax_additional_withholding')
+ allowances = payslip.contract_id.us_payroll_config_value('ok_w4_sit_allowances')
+ allowances_amt = payslip.rule_parameter('us_ok_sit_allowances_rate')[schedule_pay]
+ tax_table = payslip.rule_parameter('us_ok_sit_tax_rate')[filing_status].get(schedule_pay)
+
+ taxable_income = wage - (allowances * allowances_amt)
+ withholding = 0.0
+ last = 0.0
+ for row in tax_table:
+ amt, rate, flat_fee = row
+ if wage <= float(amt):
+ withholding = ((taxable_income - last) * (rate / 100)) + flat_fee
+ break
+ last = amt
+
+ withholding = max(withholding, 0.0)
+ withholding += additional
+ withholding = round(withholding)
+ return wage, -((withholding / wage) * 100.0)
diff --git a/l10n_us_hr_payroll/models/state/ri_rhode_island.py b/l10n_us_hr_payroll/models/state/ri_rhode_island.py
new file mode 100644
index 00000000..454bde47
--- /dev/null
+++ b/l10n_us_hr_payroll/models/state/ri_rhode_island.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 ri_rhode_island_state_income_withholding(payslip, categories, worked_days, inputs):
+ """
+ Returns SIT eligible wage and rate.
+
+ :return: result, result_rate (wage, percent)
+ """
+ state_code = 'RI'
+ if not _state_applies(payslip, state_code):
+ return 0.0, 0.0
+
+ # Determine Wage
+ wage = sit_wage(payslip, categories)
+ if not wage:
+ return 0.0, 0.0
+
+ if payslip.contract_id.us_payroll_config_value('state_income_tax_exempt'):
+ return 0.0, 0.0
+
+ schedule_pay = payslip.contract_id.schedule_pay
+ additional = payslip.contract_id.us_payroll_config_value('state_income_tax_additional_withholding')
+ allowances = payslip.contract_id.us_payroll_config_value('ri_w4_sit_allowances')
+ exemption_rate = payslip.rule_parameter('us_ri_sit_exemption_rate')[schedule_pay]
+ tax_table = payslip.rule_parameter('us_ri_sit_tax_rate')[schedule_pay]
+
+ exemption_amt = 0.0
+ for row in exemption_rate:
+ amt, flat_fee = row
+ if wage > amt:
+ exemption_amt = flat_fee
+
+ taxable_income = wage - (allowances * exemption_amt)
+ withholding = 0.0
+ last = 0.0
+ for row in tax_table:
+ amt, flat_fee, rate = row
+ if wage <= float(amt):
+ withholding = ((taxable_income - last) * (rate / 100)) + flat_fee
+ break
+ last = amt
+
+ withholding = max(withholding, 0.0)
+ withholding += additional
+ return wage, -((withholding / wage) * 100.0)
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..e877f8f9
--- /dev/null
+++ b/l10n_us_hr_payroll/models/state/sc_south_carolina.py
@@ -0,0 +1,50 @@
+# 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()
+ additional = payslip.contract_id.us_payroll_config_value('state_income_tax_additional_withholding')
+ 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
+ withholding += additional
+ return wage, -((withholding / wage) * 100.0)
diff --git a/l10n_us_hr_payroll/models/state/ut_utah.py b/l10n_us_hr_payroll/models/state/ut_utah.py
new file mode 100644
index 00000000..9e6b26f9
--- /dev/null
+++ b/l10n_us_hr_payroll/models/state/ut_utah.py
@@ -0,0 +1,39 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from .general import _state_applies, sit_wage
+
+
+def ut_utah_state_income_withholding(payslip, categories, worked_days, inputs):
+ """
+ Returns SIT eligible wage and rate.
+
+ :return: result, result_rate (wage, percent)
+ """
+ state_code = 'UT'
+ if not _state_applies(payslip, state_code):
+ return 0.0, 0.0
+
+ # Determine Wage
+ wage = sit_wage(payslip, categories)
+ if not wage:
+ return 0.0, 0.0
+
+ filing_status = payslip.contract_id.us_payroll_config_value('ut_w4_sit_filing_status')
+ if not filing_status:
+ return 0.0, 0.0
+
+ schedule_pay = payslip.contract_id.schedule_pay
+ additional = payslip.contract_id.us_payroll_config_value('state_income_tax_additional_withholding')
+ tax_rate = payslip.rule_parameter('us_ut_tax_rate')
+ allowances = payslip.rule_parameter('us_ut_sit_allowances_rate')[filing_status].get(schedule_pay)
+ tax_table = payslip.rule_parameter('us_ut_sit_tax_rate')[filing_status].get(schedule_pay)
+
+ taxable_income = wage * tax_rate
+ withholding = 0.0
+ amt, rate = tax_table
+ withholding = taxable_income - (allowances - ((wage - amt) * (rate / 100)))
+
+ withholding = max(withholding, 0.0)
+ withholding += additional
+ withholding = round(withholding)
+ return wage, -((withholding / wage) * 100.0)
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..a09f80a0
--- /dev/null
+++ b/l10n_us_hr_payroll/models/state/va_virginia.py
@@ -0,0 +1,44 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from .general import _state_applies, sit_wage
+
+
+def va_virginia_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 = 'VA'
+ if not _state_applies(payslip, state_code):
+ return 0.0, 0.0
+
+ if payslip.contract_id.us_payroll_config_value('state_income_tax_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()
+ additional = payslip.contract_id.us_payroll_config_value('state_income_tax_additional_withholding')
+ personal_exemptions = payslip.contract_id.us_payroll_config_value('va_va4_sit_exemptions')
+ other_exemptions = payslip.contract_id.us_payroll_config_value('va_va4_sit_other_exemptions')
+ personal_exemption_rate = payslip.rule_parameter('us_va_sit_exemption_rate')
+ other_exemption_rate = payslip.rule_parameter('us_va_sit_other_exemption_rate')
+ deduction = payslip.rule_parameter('us_va_sit_deduction')
+ withholding_rate = payslip.rule_parameter('us_va_sit_rate')
+
+ 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/state/vt_vermont.py b/l10n_us_hr_payroll/models/state/vt_vermont.py
new file mode 100644
index 00000000..463e007c
--- /dev/null
+++ b/l10n_us_hr_payroll/models/state/vt_vermont.py
@@ -0,0 +1,46 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from .general import _state_applies, sit_wage
+
+
+def vt_vermont_state_income_withholding(payslip, categories, worked_days, inputs):
+ """
+ Returns SIT eligible wage and rate.
+
+ :return: result, result_rate (wage, percent)
+ """
+ state_code = 'VT'
+ if not _state_applies(payslip, state_code):
+ return 0.0, 0.0
+
+ # Determine Wage
+ wage = sit_wage(payslip, categories)
+ if not wage:
+ return 0.0, 0.0
+
+ if payslip.contract_id.us_payroll_config_value('state_income_tax_exempt'):
+ return 0.0, 0.0
+
+ filing_status = payslip.contract_id.us_payroll_config_value('vt_w4vt_sit_filing_status')
+ if not filing_status:
+ return 0.0, 0.0
+
+ schedule_pay = payslip.contract_id.schedule_pay
+ additional = payslip.contract_id.us_payroll_config_value('state_income_tax_additional_withholding')
+ allowances = payslip.contract_id.us_payroll_config_value('vt_w4vt_sit_allowances')
+ allowance_amt = payslip.rule_parameter('us_vt_sit_allowances_rate')[schedule_pay]
+ tax_table = payslip.rule_parameter('us_vt_sit_tax_rate')[filing_status].get(schedule_pay)
+
+ taxable_income = wage - (allowances * allowance_amt)
+ withholding = 0.0
+ last = 0.0
+ for row in tax_table:
+ amt, flat_fee, rate = row
+ if wage <= float(amt):
+ withholding = ((taxable_income - last) * (rate / 100)) + flat_fee
+ break
+ last = amt
+
+ withholding = max(withholding, 0.0)
+ withholding += additional
+ return wage, -((withholding / wage) * 100.0)
diff --git a/l10n_us_hr_payroll/models/state/wa_washington.py b/l10n_us_hr_payroll/models/state/wa_washington.py
new file mode 100644
index 00000000..4294b5f5
--- /dev/null
+++ b/l10n_us_hr_payroll/models/state/wa_washington.py
@@ -0,0 +1,27 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from .general import _state_applies, _general_rate
+
+
+def _wa_washington_fml(payslip, categories, worked_days, inputs, inner_rate=None):
+ if not inner_rate:
+ return 0.0, 0.0
+
+ if not _state_applies(payslip, 'WA'):
+ return 0.0, 0.0
+
+ wage = categories.GROSS
+ year = payslip.dict.get_year()
+ ytd_wage = payslip.sum_category('GROSS', str(year) + '-01-01', str(year + 1) + '-01-01')
+ ytd_wage += payslip.contract_id.external_wages
+ rate = payslip.rule_parameter('us_wa_fml_rate')
+ rate *= payslip.rule_parameter(inner_rate) / 100.0
+ return _general_rate(payslip, wage, ytd_wage, wage_base='us_wa_fml_wage_base', rate=rate)
+
+
+def wa_washington_fml_er(payslip, categories, worked_days, inputs):
+ return _wa_washington_fml(payslip, categories, worked_days, inputs, inner_rate='us_wa_fml_rate_er')
+
+
+def wa_washington_fml_ee(payslip, categories, worked_days, inputs):
+ return _wa_washington_fml(payslip, categories, worked_days, inputs, inner_rate='us_wa_fml_rate_ee')
diff --git a/l10n_us_hr_payroll/models/state/wi_wisconsin.py b/l10n_us_hr_payroll/models/state/wi_wisconsin.py
new file mode 100644
index 00000000..c1d53bbb
--- /dev/null
+++ b/l10n_us_hr_payroll/models/state/wi_wisconsin.py
@@ -0,0 +1,47 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from .general import _state_applies, sit_wage
+
+
+def wi_wisconsin_state_income_withholding(payslip, categories, worked_days, inputs):
+ """
+ Returns SIT eligible wage and rate.
+
+ :return: result, result_rate (wage, percent)
+ """
+ state_code = 'WI'
+ if not _state_applies(payslip, state_code):
+ return 0.0, 0.0
+
+ # Determine Wage
+ wage = sit_wage(payslip, categories)
+ if not wage:
+ return 0.0, 0.0
+
+ if payslip.contract_id.us_payroll_config_value('state_income_tax_exempt'):
+ return 0.0, 0.0
+
+ filing_status = payslip.contract_id.us_payroll_config_value('wi_wt4_sit_filing_status')
+ if not filing_status:
+ return 0.0, 0.0
+
+ pay_periods = payslip.dict.get_pay_periods_in_year()
+ additional = payslip.contract_id.us_payroll_config_value('state_income_tax_additional_withholding')
+ exemptions = payslip.contract_id.us_payroll_config_value('wi_wt4_sit_exemptions')
+ exemption_amt = payslip.rule_parameter('us_wi_sit_exemption_rate')
+ tax_table = payslip.rule_parameter('us_wi_sit_tax_rate')[filing_status]
+
+ taxable_income = wage * pay_periods
+ withholding = 0.0
+ last = 0.0
+ for row in tax_table:
+ amt, rate, flat_fee = row
+ if taxable_income <= float(amt):
+ withholding = (((taxable_income - last) * (rate / 100)) + flat_fee) - (exemptions * exemption_amt)
+ break
+ last = amt
+
+ withholding = max(withholding, 0.0)
+ withholding /= pay_periods
+ withholding += additional
+ return wage, -((withholding / wage) * 100.0)
diff --git a/l10n_us_hr_payroll/models/state/wv_west_virginia.py b/l10n_us_hr_payroll/models/state/wv_west_virginia.py
new file mode 100644
index 00000000..34031eb0
--- /dev/null
+++ b/l10n_us_hr_payroll/models/state/wv_west_virginia.py
@@ -0,0 +1,44 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from .general import _state_applies, sit_wage
+
+
+def wv_west_virginia_state_income_withholding(payslip, categories, worked_days, inputs):
+ """
+ Returns SIT eligible wage and rate.
+
+ :return: result, result_rate (wage, percent)
+ """
+ state_code = 'WV'
+ if not _state_applies(payslip, state_code):
+ return 0.0, 0.0
+
+ # Determine Wage
+ wage = sit_wage(payslip, categories)
+ if not wage:
+ return 0.0, 0.0
+
+ filing_status = payslip.contract_id.us_payroll_config_value('wv_it104_sit_filing_status')
+ if not filing_status:
+ return 0.0, 0.0
+
+ schedule_pay = payslip.contract_id.schedule_pay
+ additional = payslip.contract_id.us_payroll_config_value('state_income_tax_additional_withholding')
+ exemptions = payslip.contract_id.us_payroll_config_value('wv_it104_sit_exemptions')
+ exemption_amt = payslip.rule_parameter('us_wv_sit_exemption_rate')[schedule_pay]
+ tax_table = payslip.rule_parameter('us_wv_sit_tax_rate')[filing_status].get(schedule_pay)
+
+ taxable_income = wage - (exemptions * exemption_amt)
+ withholding = 0.0
+ last = 0.0
+ for row in tax_table:
+ amt, flat_fee, rate = row
+ if taxable_income <= float(amt):
+ withholding = ((taxable_income - last) * (rate / 100)) + flat_fee
+ break
+ last = amt
+
+ withholding = max(withholding, 0.0)
+ withholding += additional
+ withholding = round(withholding)
+ return wage, -((withholding / wage) * 100.0)
diff --git a/l10n_us_hr_payroll/models/update.py b/l10n_us_hr_payroll/models/update.py
new file mode 100644
index 00000000..2184abef
--- /dev/null
+++ b/l10n_us_hr_payroll/models/update.py
@@ -0,0 +1,26 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+import datetime
+from odoo import fields, models
+
+
+class PublisherWarrantyContract(models.AbstractModel):
+ _inherit = 'publisher_warranty.contract'
+
+ def _get_hibou_modules(self):
+ modules = super(PublisherWarrantyContract, self)._get_hibou_modules()
+ try:
+ today_date = fields.Date.today()
+ last_thirty_date = today_date - datetime.timedelta(days=30)
+ today = fields.Date.to_string(today_date + datetime.timedelta(days=1)) # Dates vs Datetimes, pad out a day
+ last_thirty = fields.Date.to_string(last_thirty_date)
+ self.env.cr.execute(
+ 'SELECT COUNT(DISTINCT(employee_id)) FROM hr_payslip WHERE create_date BETWEEN %s AND %s',
+ (last_thirty, today))
+ employee_count = self.env.cr.fetchone()[0] or 0
+ modules.update({
+ 'l10n_us_hr_payroll': employee_count,
+ })
+ except:
+ pass
+ return modules
diff --git a/l10n_us_hr_payroll/models/us_payroll_config.py b/l10n_us_hr_payroll/models/us_payroll_config.py
index ed0e6d73..6687cbeb 100644
--- a/l10n_us_hr_payroll/models/us_payroll_config.py
+++ b/l10n_us_hr_payroll/models/us_payroll_config.py
@@ -14,6 +14,13 @@ class HRContractUSPayrollConfig(models.Model):
name = fields.Char(string="Description")
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')
+ workers_comp_ee_code = fields.Char(string='Workers\' Comp Code (Employee Withholding)',
+ help='Code for a Rule Parameter, used by some states or your own rules.')
+ workers_comp_er_code = fields.Char(string='Workers\' Comp Code (Employer Withholding)',
+ help='Code for a Rule Parameter, used by some states or your own rules.')
fed_940_type = fields.Selection([
(FUTA_TYPE_EXEMPT, 'Exempt (0%)'),
@@ -43,3 +50,229 @@ class HRContractUSPayrollConfig(models.Model):
help='Form W4 (2020+) 4(b)')
fed_941_fit_w4_additional_withholding = fields.Float(string='Federal W4 Additional Withholding [4(c)]',
help='Form W4 (2020+) 4(c)')
+
+ al_a4_sit_exemptions = fields.Selection([
+ ('', '0'),
+ ('S', 'S'),
+ ('MS', 'MS'),
+ ('M', 'M'),
+ ('H', 'H'),
+ ], string='Alabama A4 Withholding Exemptions', help='A4 1. 2. 3.')
+ al_a4_sit_dependents = fields.Integer(string='Alabama A4 Dependents', help='A4 4.')
+
+ ar_ar4ec_sit_allowances = fields.Integer(string='Arkansas AR4EC allowances', help='AR4EC 3.')
+
+ az_a4_sit_withholding_percentage = fields.Float(
+ string='Arizona A-4 Withholding Percentage',
+ help='A-4 1. (0.8 or 1.3 or 1.8 or 2.7 or 3.6 or 4.2 or 5.1 or 0 for exempt.')
+
+ ca_de4_sit_allowances = fields.Integer(string='California W-4 Allowances',
+ help='CA W-4 3.')
+ ca_de4_sit_additional_allowances = fields.Integer(string='California W-4 Additional Allowances',
+ help='CA W-4 4(c).')
+ ca_de4_sit_filing_status = fields.Selection([
+ ('', 'Exempt'),
+ ('single', 'Single or Married filing separately'),
+ ('married', 'Married filing jointly'),
+ ('head_household', 'Head of Household')
+ ], string='California W-4 Filing Status', help='CA W-4 1(c).')
+
+ ct_w4na_sit_code = fields.Selection([
+ ('a', 'A'),
+ ('b', 'B'),
+ ('c', 'C'),
+ ('d', 'D'),
+ ('f', 'F'),
+ ], string='Connecticut CT-W4 Withholding Code', help='CT-W4 1.')
+
+ de_w4_sit_filing_status = fields.Selection([
+ ('single', 'Single or Married filing separately'),
+ ('married', 'Married filing jointly'),
+ ], string='Delaware W-4 Marital Status', help='DE W-4 3.')
+ de_w4_sit_dependent = fields.Integer(string='Delaware W-4 Dependents', help='DE W-4 4.')
+
+ ga_g4_sit_filing_status = fields.Selection([
+ ('', 'Exempt'),
+ ('single', 'Single'),
+ ('married filing joint, both spouses working', 'Married Filing Joint, both spouses working'),
+ ('married filing joint, one spouse working', 'Married Filing Joint, one spouse working'),
+ ('married filing separate', 'Married Filing Separate'),
+ ('head of household', 'Head of Household'),
+ ], string='Georgia G-4 Filing Status', help='G-4 3.')
+ ga_g4_sit_dependent_allowances = fields.Integer(string='Georgia G-4 Dependent Allowances',
+ help='G-4 4.')
+ ga_g4_sit_additional_allowances = fields.Integer(string='Georgia G-4 Additional Allowances',
+ help='G-4 5.')
+
+ hi_hw4_sit_filing_status = fields.Selection([
+ ('', 'Exempt'),
+ ('single', 'Single'),
+ ('married', 'Married'),
+ ('head_of_household', 'Head of Household'),
+ ], string='Hawaii HW-4 Marital Status', help='HI HW-4 3.')
+ hi_hw4_sit_allowances = fields.Integer(string='Hawaii HW-4 Allowances', help='HI HW-4 4.')
+
+ ia_w4_sit_allowances = fields.Integer(string='Iowa W-4 allowances', help='IA W-4 6.')
+
+ id_w4_sit_filing_status = fields.Selection([
+ ('single', 'Single'),
+ ('married', 'Married'),
+ ('head of household', 'Head of Household'),
+ ], string='Idaho ID W-4 Withholding Status', help='ID W-4 A.B.C.')
+ id_w4_sit_allowances = fields.Integer(string='Idaho ID W-4 Allowances', help='ID W-4 1.')
+
+ il_w4_sit_basic_allowances = fields.Integer(string='Illinois IL-W-4 Number of Basic Allowances', help='IL-W-4 Step 1.')
+ il_w4_sit_additional_allowances = fields.Integer(string='Illinois IL-W-4 Number of Additional Allowances', help='IL-W-4 Step 2.')
+
+ in_w4_sit_personal_exemption = fields.Integer(string='Indiana In-W-4 Number of Personal Exemption', help='IN-W-4 5.')
+ in_w4_sit_dependent_exemption = fields.Integer(string='Indiana In-W-4 Number of Dependent Exemption', help='IN-W-4 6.')
+
+ ks_k4_sit_filing_status = fields.Selection([
+ ('single', 'Single'),
+ ('married', 'Joint'),
+ ], string='Kansas K-4 Filing Status', help='KS K-4 3.')
+ ks_k4_sit_allowances = fields.Integer(string='Kansas KS K-4 Number of Allowances', help='KS K-4 Step 4.')
+
+ la_l4_sit_filing_status = fields.Selection([
+ ('single', 'Single'),
+ ('married', 'Married'),
+ ], string='Louisiana LA L-4 Filing Status', help='LA L-4 3.')
+ la_l4_sit_exemptions = fields.Integer(string='Louisiana LA L-4 Number of Exemptions', help='LA L-4 6.')
+ la_l4_sit_dependents = fields.Integer(string='Louisiana LA L-4 Number of Dependents', help='LA L-4 7.')
+
+ me_w4me_sit_filing_status = fields.Selection([
+ ('', 'Exempt'),
+ ('single', 'Single or Head of Household'),
+ ('married', 'Married'),
+ ], string='Maine W-4ME Filing Status', help='ME W-4ME 3.')
+ me_w4me_sit_allowances = fields.Integer(string='Maine Allowances', help='W-4ME 4.')
+
+ mi_w4_sit_exemptions = fields.Integer(string='Michigan MI W-4 Exemptions', help='MI-W4 6.')
+
+ mn_w4mn_sit_filing_status = fields.Selection([
+ ('', 'Exempt'),
+ ('single', 'Single'),
+ ('married', 'Married'),
+ ], string='Minnesota W-4MN Marital Status', help='W-4MN')
+ mn_w4mn_sit_allowances = fields.Integer(string='Minnesota Allowances', help='W-4MN 1.')
+
+ mo_mow4_sit_filing_status = fields.Selection([
+ ('', 'Exempt'),
+ ('single', 'Single or Married Spouse Works or Married Filing Separate'),
+ ('married', 'Married (Spouse does not work)'),
+ ('head_of_household', 'Head of Household'),
+ ], string='Missouri W-4 Filing Status', help='MO W-4 1.')
+ mo_mow4_sit_withholding = fields.Integer(string='Missouri MO W-4 Reduced Withholding', help='MO W-4 3.')
+
+ ms_89_350_sit_filing_status = fields.Selection([
+ ('', 'Exempt'),
+ ('single', 'Single'),
+ ('married', 'Married (spouse NOT employed)'),
+ ('married_dual', 'Married (spouse IS employed)'),
+ ('head_of_household', 'Head of Household'),
+ ], string='Mississippi 89-350 Filing Status', help='89-350 1. 2. 3. 8.')
+ ms_89_350_sit_exemption_value = fields.Float(string='Mississippi 89-350 Exemption Total',
+ help='89-350 Box 6 (including filing status amounts)')
+
+ mt_mw4_sit_exemptions = fields.Integer(string='Montana MW-4 Exemptions',
+ help='MW-4 Box G')
+ # Don't use the main state_income_tax_exempt because of special meaning and reporting
+ # Use additional withholding but name it on the form 'MW-4 Box H'
+ mt_mw4_sit_exempt = fields.Selection([
+ ('', 'Not Exempt'),
+ ('tribe', 'Registered Tribe'),
+ ('reserve', 'Reserve or National Guard'),
+ ('north_dakota', 'North Dakota'),
+ ('montana_for_marriage', 'Montana for Marriage'),
+ ], string='Montana MW-4 Exempt from Withholding', help='MW-4 Section 2')
+
+ nc_nc4_sit_filing_status = fields.Selection([
+ ('', 'Exempt'),
+ ('single', 'Single'),
+ ('married', 'Married'),
+ ('head_household', 'Head of Household')
+ ], string='North Carolina NC-4 Filing Status', help='NC-4')
+ nc_nc4_sit_allowances = fields.Integer(string='North Carolina NC-4 Allowances', help='NC-4 1.')
+
+ nd_w4_sit_filing_status = fields.Selection([
+ ('', 'Exempt'),
+ ('single', 'Single'),
+ ('married', 'Married'),
+ ('head_household', 'Head of Household')
+ ], string='North Dakota ND W-4 Filing Status', help='ND W-4')
+ nd_w4_sit_allowances = fields.Integer(string='North Dakota ND W-4')
+
+ ne_w4n_sit_filing_status = fields.Selection([
+ ('single', 'Single'),
+ ('married', 'Married'),
+ ], string='Nebraska NE W-4N Filing Status', help='NE W-4N')
+ ne_w4n_sit_allowances = fields.Integer(string='Nebraska NE W-4N Allowances', help='NE W-4N 1.')
+
+ nj_njw4_sit_filing_status = fields.Selection([
+ ('', 'Exempt'),
+ ('single', 'Single'),
+ ('married_separate', 'Married/Civil Union partner Separate'),
+ ('married_joint', 'Married/Civil Union Couple Joint'),
+ ('widower', 'Widower/Surviving Civil Union Partner'),
+ ('head_household', 'Head of Household')
+ ], string='New Jersey NJ-W4 Filing Status', help='NJ-W4 2.')
+ nj_njw4_sit_allowances = fields.Integer(string='New Jersey NJ-W4 Allowances', help='NJ-W4 4.')
+ nj_njw4_sit_rate_table = fields.Selection([
+ ('A', 'A'),
+ ('B', 'B'),
+ ('C', 'C'),
+ ('D', 'D'),
+ ('E', 'E')
+ ], string='New Jersey Wage Chart Letter', help='NJ-W4. 3.')
+
+ ny_it2104_sit_filing_status = fields.Selection([
+ ('', 'Exempt'),
+ ('single', 'Single'),
+ ('married', 'Married'),
+ ], string='New York NY IT-2104 Filing Status', help='NY IT-2104')
+ ny_it2104_sit_allowances = fields.Integer(string="New York IT-2104 Allowances", help="NY IT-2104 1. 2.")
+
+ # Ohio will use generic SIT exempt and additional fields
+ oh_it4_sit_exemptions = fields.Integer(string='Ohio IT-4 Exemptions',
+ help='Line 4')
+
+ ok_w4_sit_filing_status = fields.Selection([
+ ('single', 'Single'),
+ ('married', 'Married'),
+ ('head_household', 'Married, but withhold at higher Single rate')
+ ], string='Oklahoma OK-W-4 Filing Status', help='OK-W-4')
+ ok_w4_sit_allowances = fields.Integer(string='Oklahoma OK-W-4 Allowances', help='OK-W-4 5.')
+
+ ri_w4_sit_allowances = fields.Integer(string='Rhode Island RI W-4 Allowances', help='RI W-4 1.')
+
+ sc_w4_sit_allowances = fields.Integer(string='South Carolina SC W-4 Allowances', help='SC W-4 5.')
+
+ ut_w4_sit_filing_status = fields.Selection([
+ ('single', 'Single'),
+ ('married', 'Married'),
+ ('head_household', 'Head of Household')
+ ], string='Utah UT W-4 Filing Status', help='UT W-4 C.')
+
+ vt_w4vt_sit_filing_status = fields.Selection([
+ ('single', 'Single'),
+ ('married', 'Married'),
+ ], string='Vermont VT W-4VT Filing Status', help='VT W-4VT')
+ vt_w4vt_sit_allowances = fields.Integer(string='Vermont VT W-4VT Allowances', help='VT W-4VT 5.')
+
+ 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)')
+
+ wi_wt4_sit_filing_status = fields.Selection([
+ ('single', 'Single'),
+ ('married', 'Married'),
+ ], string='Wisconsin WT-4 Filing Status', help='WI WT-4')
+ wi_wt4_sit_exemptions = fields.Integer(string='Wisconsin Exemptions', help='WI WT-4 1.(d)')
+
+ wv_it104_sit_filing_status = fields.Selection([
+ ('single', 'Single'),
+ ('married', 'Married'),
+ ('head_household', 'Head of Household')
+ ], string='West Virginia WV/IT-104 Filing Status', help='WV WV/IT-104')
+ wv_it104_sit_exemptions = fields.Integer(string='West Virginia Exemptions', help='WV WV/IT-104 4.')
diff --git a/l10n_us_hr_payroll/tests/__init__.py b/l10n_us_hr_payroll/tests/__init__.py
index 23702419..a7c95ebf 100755
--- a/l10n_us_hr_payroll/tests/__init__.py
+++ b/l10n_us_hr_payroll/tests/__init__.py
@@ -1,5 +1,130 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
from . import common
+
+from . import test_special
+
from . import test_us_payslip_2019
from . import test_us_payslip_2020
+
+from . import test_us_ak_alaska_payslip_2019
+from . import test_us_ak_alaska_payslip_2020
+
+from . import test_us_al_alabama_payslip_2019
+from . import test_us_al_alabama_payslip_2020
+
+from . import test_us_ar_arkansas_payslip_2019
+from . import test_us_ar_arkansas_payslip_2020
+
+from . import test_us_az_arizona_payslip_2019
+from . import test_us_az_arizona_payslip_2020
+
+from . import test_us_ca_california_payslip_2019
+from . import test_us_ca_california_payslip_2020
+
+from . import test_us_co_colorado_payslip_2020
+
+from . import test_us_ct_connecticut_payslip_2019
+from . import test_us_ct_connecticut_payslip_2020
+
+from . import test_us_de_delaware_payslip_2020
+
+from . import test_us_fl_florida_payslip_2019
+from . import test_us_fl_florida_payslip_2020
+
+from . import test_us_ga_georgia_payslip_2019
+from . import test_us_ga_georgia_payslip_2020
+
+from . import test_us_hi_hawaii_payslip_2019
+from . import test_us_hi_hawaii_payslip_2020
+
+from . import test_us_ia_iowa_payslip_2019
+from . import test_us_ia_iowa_payslip_2020
+
+from . import test_us_id_idaho_payslip_2019
+from . import test_us_id_idaho_payslip_2020
+
+from . import test_us_il_illinois_payslip_2019
+from . import test_us_il_illinois_payslip_2020
+
+from . import test_us_in_indiana_payslip_2020
+
+from . import test_us_ky_kentucky_payslip_2020
+
+from . import test_us_ks_kansas_payslip_2020
+
+from . import test_us_la_louisiana_payslip_2019
+from . import test_us_la_louisiana_payslip_2020
+
+from . import test_us_me_maine_payslip_2020
+
+from . import test_us_mi_michigan_payslip_2019
+from . import test_us_mi_michigan_payslip_2020
+
+from . import test_us_mn_minnesota_payslip_2019
+from . import test_us_mn_minnesota_payslip_2020
+
+from . import test_us_mo_missouri_payslip_2019
+from . import test_us_mo_missouri_payslip_2020
+
+from . import test_us_ms_mississippi_payslip_2019
+from . import test_us_ms_mississippi_payslip_2020
+
+from . import test_us_mt_montana_payslip_2019
+from . import test_us_mt_montana_payslip_2020
+
+from . import test_us_nc_northcarolina_payslip_2019
+from . import test_us_nc_northcarolina_payslip_2020
+
+from . import test_us_nd_north_dakota_payslip_2020
+
+from . import test_us_ne_nebraska_payslip_2020
+
+from . import test_us_nh_new_hampshire_payslip_2020
+
+from . import test_us_nj_newjersey_payslip_2019
+from . import test_us_nj_newjersey_payslip_2020
+
+from . import test_us_nm_new_mexico_payslip_2020
+
+from . import test_us_nv_nevada_payslip_2020
+
+from . import test_us_ny_new_york_payslip_2019
+from . import test_us_ny_new_york_payslip_2020
+
+from . import test_us_oh_ohio_payslip_2019
+from . import test_us_oh_ohio_payslip_2020
+
+from . import test_us_ok_oklahoma_payslip_2020
+
+from . import test_us_pa_pennsylvania_payslip_2019
+from . import test_us_pa_pennsylvania_payslip_2020
+
+from . import test_us_ri_rhode_island_payslip_2020
+
+from . import test_us_sc_south_carolina_payslip_2019
+from . import test_us_sc_south_carolina_payslip_2020
+
+from . import test_us_sd_south_dakota_payslip_2020
+
+from . import test_us_tn_tennessee_payslip_2020
+
+from . import test_us_tx_texas_payslip_2019
+from . import test_us_tx_texas_payslip_2020
+
+from . import test_us_us_utah_payslip_2020
+
+from . import test_us_vt_vermont_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
+
+from . import test_us_wv_west_virginia_payslip_2020
+
+from . import test_us_wi_wisconsin_payslip_2020
+
+from . import test_us_wy_wyoming_payslip_2019
+from . import test_us_wy_wyoming_payslip_2020
diff --git a/l10n_us_hr_payroll/tests/common.py b/l10n_us_hr_payroll/tests/common.py
index fc84b0fe..07615652 100755
--- a/l10n_us_hr_payroll/tests/common.py
+++ b/l10n_us_hr_payroll/tests/common.py
@@ -3,9 +3,11 @@
from logging import getLogger
from sys import float_info as sys_float_info
from collections import defaultdict
+from datetime import timedelta
from odoo.tests import common
from odoo.tools.float_utils import float_round as odoo_float_round
+from odoo.addons.l10n_us_hr_payroll.models.hr_contract import USHRContract
def process_payslip(payslip):
@@ -20,6 +22,12 @@ class TestUsPayslip(common.TransactionCase):
debug = False
_logger = getLogger(__name__)
+ def setUp(self):
+ super(TestUsPayslip, self).setUp()
+ self.env['ir.config_parameter'].set_param('hr_payroll.payslip.sum_behavior', 'date_to')
+ self.structure_type_id = self.ref('l10n_us_hr_payroll.structure_type_employee')
+ self.resource_calendar_id = self.ref('resource.resource_calendar_std')
+
float_info = sys_float_info
def float_round(self, value, digits):
@@ -61,6 +69,10 @@ class TestUsPayslip(common.TransactionCase):
'employee_id': employee.id,
}
+ # Backwards compatability with 'futa_type'
+ if 'futa_type' in kwargs:
+ kwargs['fed_940_type'] = kwargs['futa_type']
+
for key, val in kwargs.items():
# Assume any Odoo object is in a Many2one
if hasattr(val, 'id'):
@@ -83,13 +95,13 @@ class TestUsPayslip(common.TransactionCase):
if not contract_values.get('state'):
contract_values['state'] = 'open' # Running
if not contract_values.get('structure_type_id'):
- contract_values['structure_type_id'] = self.ref('l10n_us_hr_payroll.structure_type_employee')
+ contract_values['structure_type_id'] = self.structure_type_id
if not contract_values.get('date_start'):
contract_values['date_start'] = '2016-01-01'
if not contract_values.get('date_end'):
contract_values['date_end'] = '2030-12-31'
if not contract_values.get('resource_calendar_id'):
- contract_values['resource_calendar_id'] = self.ref('resource.resource_calendar_std')
+ contract_values['resource_calendar_id'] = self.resource_calendar_id
# Compatibility with earlier Odoo versions
if not contract_values.get('journal_id') and hasattr(contract_model, 'journal_id'):
@@ -140,11 +152,87 @@ class TestUsPayslip(common.TransactionCase):
def assertPayrollEqual(self, first, second):
self.assertAlmostEqual(first, second, self.payroll_digits)
- def test_semi_monthly(self):
- salary = 80000.0
- employee = self._createEmployee()
- # so the schedule_pay is now on the Structure...
- contract = self._createContract(employee, wage=salary, schedule_pay='semi-monthly')
- payslip = self._createPayslip(employee, '2019-01-01', '2019-01-14')
+ def assertPayrollAlmostEqual(self, first, second):
+ self.assertAlmostEqual(first, second, self.payroll_digits-1)
+ def get_us_state(self, code, cache={}):
+ country_key = 'US_COUNTRY'
+ if code in cache:
+ return cache[code]
+ if country_key not in cache:
+ cache[country_key] = self.env.ref('base.us')
+ us_country = cache[country_key]
+ us_state = self.env['res.country.state'].search([
+ ('country_id', '=', us_country.id),
+ ('code', '=', code),
+ ], limit=1)
+ cache[code] = us_state
+ return us_state
+
+ def _test_suta(self, category, state_code, rate, date, wage_base=None, relaxed=False, **extra_contract):
+ if relaxed:
+ _assert = self.assertPayrollAlmostEqual
+ else:
+ _assert = self.assertPayrollEqual
+ if wage_base:
+ # Slightly larger than 1/2 the wage_base
+ wage = round(wage_base / 2.0) + 100.0
+ self.assertTrue((2 * wage) > wage_base, 'Granularity of wage_base too low.')
+ else:
+ wage = 1000.0
+
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=wage,
+ state_id=self.get_us_state(state_code),
+ **extra_contract)
+
+ rate = -rate / 100.0 # Assumed passed as percent positive
+
+ # Tests
+ payslip = self._createPayslip(employee, date, date + timedelta(days=30))
+
+ # Test exemptions
+ contract.us_payroll_config_id.fed_940_type = USHRContract.FUTA_TYPE_EXEMPT
payslip.compute_sheet()
+ cats = self._getCategories(payslip)
+ _assert(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)
+ _assert(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)
+ _assert(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:
+ remaining_unemp_wages = wage_base - wage
+ self.assertTrue((remaining_unemp_wages * rate) <= 0.01) # less than 0.01 because rate is negative
+ _assert(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)
+ # 1 wage - external_wages
+ # 1 wage - current Payslip
+ contract.external_wages = wage
+ payslip.compute_sheet()
+ cats = self._getCategories(payslip)
+ _assert(cats.get(category, 0.0), 0.0)
+ else:
+ _assert(cats.get(category, 0.0), wage * rate)
+
+ def _test_er_suta(self, state_code, rate, date, wage_base=None, relaxed=False, **extra_contract):
+ self._test_suta('ER_US_SUTA', state_code, rate, date, wage_base=wage_base, relaxed=relaxed, **extra_contract)
+
+ def _test_ee_suta(self, state_code, rate, date, wage_base=None, relaxed=False, **extra_contract):
+ self._test_suta('EE_US_SUTA', state_code, rate, date, wage_base=wage_base, relaxed=relaxed, **extra_contract)
diff --git a/l10n_us_hr_payroll/tests/test_special.py b/l10n_us_hr_payroll/tests/test_special.py
new file mode 100644
index 00000000..688761ec
--- /dev/null
+++ b/l10n_us_hr_payroll/tests/test_special.py
@@ -0,0 +1,119 @@
+from .common import TestUsPayslip, process_payslip
+
+
+class TestSpecial(TestUsPayslip):
+ def test_semi_monthly(self):
+ salary = 80000.0
+ employee = self._createEmployee()
+ # so the schedule_pay is now on the Structure...
+ contract = self._createContract(employee, wage=salary, schedule_pay='semi-monthly')
+ payslip = self._createPayslip(employee, '2019-01-01', '2019-01-14')
+ payslip.compute_sheet()
+
+ def test_payslip_sum_behavior(self):
+ us_structure = self.env.ref('l10n_us_hr_payroll.hr_payroll_structure')
+ rule_category_comp = self.env.ref('hr_payroll.COMP')
+ test_rule_category = self.env['hr.salary.rule.category'].create({
+ 'name': 'Test Sum Behavior',
+ 'code': 'test_sum_behavior',
+ 'parent_id': rule_category_comp.id,
+ })
+ test_rule = self.env['hr.salary.rule'].create({
+ 'sequence': 450,
+ 'struct_id': us_structure.id,
+ 'category_id': test_rule_category.id,
+ 'name': 'Test Sum Behavior',
+ 'code': 'test_sum_behavior',
+ 'condition_select': 'python',
+ 'condition_python': 'result = 1',
+ 'amount_select': 'code',
+ 'amount_python_compute': '''
+ytd_category = payslip.sum_category('test_sum_behavior', '2020-01-01', '2021-01-01')
+ytd_rule = payslip.sum('test_sum_behavior', '2020-01-01', '2021-01-01')
+result = 0.0
+if ytd_category != ytd_rule:
+ # error
+ result = -1.0
+elif ytd_rule == 0.0:
+ # first payslip in period
+ result = 1.0
+'''
+ })
+ salary = 80000.0
+ employee = self._createEmployee()
+ contract = self._createContract(employee, wage=salary, schedule_pay='bi-weekly')
+ payslip = self._createPayslip(employee, '2019-12-30', '2020-01-12')
+ payslip.compute_sheet()
+ cats = self._getCategories(payslip)
+ self.assertEqual(cats['test_sum_behavior'], 1.0)
+ process_payslip(payslip)
+
+ # Basic date_from behavior.
+ self.env['ir.config_parameter'].set_param('hr_payroll.payslip.sum_behavior', 'date_from')
+ # The the date_from on the last payslip will not be found
+ payslip = self._createPayslip(employee, '2020-01-13', '2020-01-27')
+ payslip.compute_sheet()
+ cats = self._getCategories(payslip)
+ self.assertEqual(cats['test_sum_behavior'], 1.0)
+
+ # date_to behavior.
+ self.env['ir.config_parameter'].set_param('hr_payroll.payslip.sum_behavior', 'date_to')
+ # The date_to on the last payslip is found
+ payslip = self._createPayslip(employee, '2020-01-13', '2020-01-27')
+ payslip.compute_sheet()
+ cats = self._getCategories(payslip)
+ self.assertEqual(cats['test_sum_behavior'], 0.0)
+
+ def test_recursive_salary_rule_category(self):
+ # self.debug = True
+ us_structure = self.env.ref('l10n_us_hr_payroll.hr_payroll_structure')
+ # In this scenario, you are in rule code that will check for the category
+ # and a subcategory will also
+ alw_category = self.env.ref('hr_payroll.ALW')
+ ded_category = self.env.ref('hr_payroll.DED')
+ test_category = self.env['hr.salary.rule.category'].create({
+ 'name': 'Special ALW',
+ 'code': 'ALW_SPECIAL_RECURSIVE',
+ 'parent_id': alw_category.id,
+ })
+ test_special_alw = self.env['hr.salary.rule'].create({
+ 'name': 'Flat amount 200',
+ 'code': 'ALW_SPECIAL_RECURSIVE',
+ 'category_id': test_category.id,
+ 'condition_select': 'none',
+ 'amount_select': 'fix',
+ 'amount_fix': 200.0,
+ 'struct_id': us_structure.id,
+ })
+ test_recursion = self.env['hr.salary.rule'].create({
+ 'name': 'Actual Test Behavior',
+ 'code': 'RECURSION_TEST',
+ 'category_id': ded_category.id,
+ 'condition_select': 'none',
+ 'amount_select': 'code',
+ 'amount_python_compute': """
+# this rule will always be the total of the ALW category and YTD ALW category
+result = categories.ALW
+year = payslip.dict.get_year()
+result += payslip.sum_category('ALW', str(year) + '-01-01', str(year+1) + '-01-01')
+ """,
+ 'sequence': 101,
+ 'struct_id': us_structure.id,
+ })
+
+ salary = 80000.0
+ employee = self._createEmployee()
+ contract = self._createContract(employee, wage=salary, schedule_pay='bi-weekly')
+ payslip = self._createPayslip(employee, '2020-01-01', '2020-01-14')
+ payslip.compute_sheet()
+ cats = self._getCategories(payslip)
+ rules = self._getRules(payslip)
+ self.assertEqual(rules['RECURSION_TEST'], 200.0)
+ process_payslip(payslip)
+
+ payslip = self._createPayslip(employee, '2020-01-15', '2020-01-27')
+ payslip.compute_sheet()
+ cats = self._getCategories(payslip)
+ rules = self._getRules(payslip)
+ # two hundred is in the YTD ALW
+ self.assertEqual(rules['RECURSION_TEST'], 200.0 + 200.0)
diff --git a/l10n_us_hr_payroll/tests/test_us_ak_alaska_payslip_2019.py b/l10n_us_hr_payroll/tests/test_us_ak_alaska_payslip_2019.py
new file mode 100644
index 00000000..3eb62184
--- /dev/null
+++ b/l10n_us_hr_payroll/tests/test_us_ak_alaska_payslip_2019.py
@@ -0,0 +1,61 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from .common import TestUsPayslip, process_payslip
+
+
+class TestUsAKPayslip(TestUsPayslip):
+ # TAXES AND RATES
+ AK_UNEMP_MAX_WAGE = 39900.00
+ AK_UNEMP = -(1.780 / 100.0)
+ AK_UNEMP_EE = -(0.5 / 100.0)
+
+ def test_taxes_monthly_over_max(self):
+ salary = 50000.00
+ schedule_pay = 'monthly'
+
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('AK'),
+ state_income_tax_additional_withholding=0.0,
+ schedule_pay=schedule_pay)
+
+ self._log('2019 Alaska tax first payslip monthly:')
+ payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
+ payslip.compute_sheet()
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['ER_US_SUTA'], self.AK_UNEMP_MAX_WAGE * self.AK_UNEMP)
+ self.assertPayrollEqual(cats['EE_US_SUTA'], self.AK_UNEMP_MAX_WAGE * self.AK_UNEMP_EE)
+
+ process_payslip(payslip)
+
+ remaining_ak_unemp_wages = 0.00 # We already reached the maximum wage for unemployment insurance.
+
+ self._log('2019 Alaska tax second payslip monthly:')
+ payslip = self._createPayslip(employee, '2019-02-01', '2019-02-28')
+ payslip.compute_sheet()
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['ER_US_SUTA'], remaining_ak_unemp_wages * self.AK_UNEMP) # 0
+
+ def test_taxes_weekly_under_max(self):
+ salary = 5000.00
+ schedule_pay = 'weekly'
+
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('AK'),
+ state_income_tax_additional_withholding=0.0,
+ schedule_pay=schedule_pay)
+
+ self._log('2019 Alaska tax first payslip weekly:')
+ payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
+ payslip.compute_sheet()
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['ER_US_SUTA'], salary * self.AK_UNEMP)
+ self.assertPayrollEqual(cats['EE_US_SUTA'], salary * self.AK_UNEMP_EE)
+
+ process_payslip(payslip)
diff --git a/l10n_us_hr_payroll/tests/test_us_ak_alaska_payslip_2020.py b/l10n_us_hr_payroll/tests/test_us_ak_alaska_payslip_2020.py
new file mode 100644
index 00000000..868a8dff
--- /dev/null
+++ b/l10n_us_hr_payroll/tests/test_us_ak_alaska_payslip_2020.py
@@ -0,0 +1,15 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from datetime import date
+from .common import TestUsPayslip
+
+
+class TestUsAKPayslip(TestUsPayslip):
+ # TAXES AND RATES
+ AK_UNEMP_MAX_WAGE = 41500.00
+ AK_UNEMP = 1.590
+ AK_UNEMP_EE = 0.5
+
+ def test_2020_taxes(self):
+ self._test_er_suta('AK', self.AK_UNEMP, date(2020, 1, 1), wage_base=self.AK_UNEMP_MAX_WAGE)
+ self._test_ee_suta('AK', self.AK_UNEMP_EE, date(2020, 1, 1), wage_base=self.AK_UNEMP_MAX_WAGE)
diff --git a/l10n_us_hr_payroll/tests/test_us_al_alabama_payslip_2019.py b/l10n_us_hr_payroll/tests/test_us_al_alabama_payslip_2019.py
new file mode 100644
index 00000000..33ddb2f9
--- /dev/null
+++ b/l10n_us_hr_payroll/tests/test_us_al_alabama_payslip_2019.py
@@ -0,0 +1,264 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from .common import TestUsPayslip, process_payslip
+
+
+class TestUsALPayslip(TestUsPayslip):
+ # TAXES AND RATES
+ AL_UNEMP_MAX_WAGE = 8000.00
+ AL_UNEMP = -2.70 / 100.0
+
+ def test_taxes_weekly(self):
+ salary = 10000.00
+ schedule_pay = 'weekly'
+ dependents = 1
+ filing_status = 'S'
+ # see https://revenue.alabama.gov/wp-content/uploads/2019/01/whbooklet_0119.pdf for reference
+ # Hand Calculated Amount to Test
+ # Step 1 -> 10000.00 for wages per period , 52.0 for weekly -> 10000 * 52 -> 520000.0
+ # Step 2A -> standard deduction for highest wage bracket -> 2000. Subtract from yearly income
+ # 520000 - 2000 = 518000.0
+ # Step 2B -> Subtract Federal Income Tax in yearly form -> Our Fed withholding is -2999.66 * 52 = -155982.32
+ # -> 518000.0 - 155982.32 = 362017.68
+ # Step 2C -> Subtract the personal exemption -> 1500 for single filing_status
+ # -> 362017.68 - 1500 = 360517.68
+ # Step 2D -> Since income is so high, only 300$ per dependent -> 300$. Subtract
+ # -> 360517.68 - 300 = 360217.68
+ #
+ # Step 5 (after adding previous lines) -> Compute marginal taxes.
+ # (500 * (2.00 / 100)) + (2500 * (4.00 / 100)) + ((360217.68 - 500 - 2500) * (5.00 / 100)) -> 17970.884000000002
+ # Convert back to pay period
+ # wh = round(17970.884000000002, 2) -> 17970.88 / 52.0 -> 345.59
+ wh = -345.59
+
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('AL'),
+ al_a4_sit_exemptions=filing_status,
+ state_income_tax_additional_withholding=0.0,
+ state_income_tax_exempt=False,
+ al_a4_sit_dependents=dependents,
+ schedule_pay=schedule_pay)
+
+ self._log('2019 Alabama tax first payslip weekly:')
+ payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
+ payslip.compute_sheet()
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['EE_US_941_FIT'], -2999.66) # Hand Calculated.
+ self.assertPayrollEqual(cats['ER_US_SUTA'], self.AL_UNEMP_MAX_WAGE * self.AL_UNEMP)
+ self.assertPayrollEqual(cats['EE_US_SIT'], wh)
+
+ process_payslip(payslip)
+
+ remaining_AL_UNEMP_wages = 0.00 # We already reached the maximum wage for unemployment insurance.
+
+ self._log('2019 Alabama tax second payslip weekly:')
+ payslip = self._createPayslip(employee, '2019-02-01', '2019-02-28')
+ payslip.compute_sheet()
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['ER_US_SUTA'], remaining_AL_UNEMP_wages * self.AL_UNEMP) # 0
+
+ def test_taxes_married_jointly(self):
+ salary = 10000.00
+ schedule_pay = 'weekly'
+ dependents = 1
+ filing_status = 'M'
+
+ # see https://revenue.alabama.gov/wp-content/uploads/2019/01/whbooklet_0119.pdf for reference
+ # Hand Calculated Amount to Test
+ # Step 1 -> 10000.00 for wages per period , 52.0 for weekly -> 10000 * 52 -> 520000.0
+ # Step 2A -> standard deduction for highest wage bracket -> 4000. Subtract from yearly income
+ # 520000 - 4000 = 516000.0
+ # Step 2B -> Subtract Federal Income Tax in yearly form -> Our Fed withholding is -2999.66 * 52 = -155982.32
+ # -> 516000.0 - 155982.32 = 360017.68
+ # Step 2C -> Subtract the personal exemption -> 3000 for married filing jointly.
+ # -> 360017.68 - 3000 = 357017.68
+ # Step 2D -> Since income is so high, only 300$ per dependent -> 300$. Subtract
+ # -> 357017.68 - 300 = 356717.68
+ #
+ # Step 5 (after adding previous lines) -> Compute marginal taxes.
+ # (1000 * (2.00 / 100)) + (5000 * (4.00 / 100)) + ((356717.68 - 1000 - 50000) * (5.00 / 100))
+ # -> 17755.884000000002
+ # Convert back to pay period
+ # wh = round(17755.884000000002, 2) -> 15505.88 / 52.0 -> 341.45923076923077
+ wh = -341.46
+
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('AL'),
+ al_a4_sit_exemptions=filing_status,
+ state_income_tax_additional_withholding=0.0,
+ state_income_tax_exempt=False,
+ al_a4_sit_dependents=dependents,
+ schedule_pay=schedule_pay)
+
+ self._log('2019 Alabama tax first payslip weekly:')
+ payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
+ payslip.compute_sheet()
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['EE_US_941_FIT'], -2999.66) # Hand Calculated.
+ self.assertPayrollEqual(cats['ER_US_SUTA'], self.AL_UNEMP_MAX_WAGE * self.AL_UNEMP)
+ self.assertPayrollEqual(cats['EE_US_SIT'], wh)
+
+ process_payslip(payslip)
+
+
+ def test_taxes_semimonthly_filing_seperate(self):
+ salary = 20000.00
+ schedule_pay = 'monthly'
+ filing_status = 'MS'
+ dependents = 2
+
+ # see https://revenue.alabama.gov/wp-content/uploads/2019/01/whbooklet_0119.pdf for reference
+ # Hand Calculated Amount to Test
+ # Step 1 -> 10000.00 for wages per period , 12.0 for monthly -> 20000 * 12 -> 240000.00
+ # Step 2A -> standard deduction for highest wage bracket -> 2000. Subtract from yearly income
+ # 240000.00 - 2000 = 238000.00
+ # Step 2B -> Subtract Federal Income Tax in yearly form -> Our Fed withholding is -4821.99 * 12 = -57863.88
+ # -> 238000.00 - 57863.88 = 180136.12
+ # Step 2C -> Subtract the personal exemption -> 1500 for married filing separately
+ # -> 180136.12 - 1500 = 178636.12
+ # Step 2D -> Since income is so high, only 300$ per dependent -> 600. Subtract
+ # -> 178636.12 - 600 = 178036.12
+ #
+ # Step 5 (after adding previous lines) -> Compute marginal taxes.
+ # (500 * (2.00 / 100)) + (2500 * (4.00 / 100)) + ((178036.12 - 500 - 2500) * (5.00 / 100)) -> 8861.806
+ # Convert back to pay period
+ # wh = 8861.806 / 12.0 rounded -> 738.48
+ wh = -738.48
+
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('AL'),
+ al_a4_sit_exemptions=filing_status,
+ state_income_tax_additional_withholding=0.0,
+ state_income_tax_exempt=False,
+ al_a4_sit_dependents=dependents,
+ schedule_pay=schedule_pay)
+
+ self._log('2019 Alabama tax first payslip monthly:')
+ payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
+ payslip.compute_sheet()
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['EE_US_941_FIT'], -4822.00) # Hand Calculated.
+ self.assertPayrollEqual(cats['ER_US_SUTA'], self.AL_UNEMP_MAX_WAGE * self.AL_UNEMP)
+ self.assertPayrollEqual(cats['EE_US_SIT'], wh)
+
+ process_payslip(payslip)
+
+ def test_tax_exempt(self):
+ salary = 5500.00
+ wh = 0
+ schedule_pay = 'weekly'
+ dependents = 2
+
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('AL'),
+ al_a4_sit_exemptions='',
+ state_income_tax_additional_withholding=0.0,
+ state_income_tax_exempt=True,
+ al_a4_sit_dependents=dependents,
+ schedule_pay=schedule_pay)
+
+ self._log('2019 Alabama tax first payslip exempt:')
+ payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
+ payslip.compute_sheet()
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['ER_US_SUTA'], salary * self.AL_UNEMP)
+ self.assertPayrollEqual(cats.get('EE_US_SIT', 0.0), wh)
+
+ def test_additional_withholding(self):
+ salary = 5500.0
+ schedule_pay = 'weekly'
+ additional_wh = 40.0
+ dependents = 2
+ # filing status default is single
+
+ # see https://revenue.alabama.gov/wp-content/uploads/2019/01/whbooklet_0119.pdf for reference
+ # Hand Calculated Amount to Test
+ # Step 1 -> 5500.00 for wages per period , 52.0 for monthly -> 5500 * 52.0 -> 286000.0
+ # Step 2A -> standard deduction for highest wage bracket -> 2000. Subtract from yearly income
+ # 286000.0 - 2000 = 284000.0
+ # Step 2B -> Subtract Federal Income Tax in yearly form -> Our Fed withholding is -1422.4 * 52.0 = -73964.8
+ # -> 284000.0 - 73964.8 = 210035.2
+ # Step 2C -> Subtract the personal exemption -> 1500 for single
+ # -> 210035.2 - 1500 = 208535.2
+ # Step 2D -> Since income is so high, only 300$ per dependent -> 600. Subtract
+ # -> 208535.2 - 600 = 207935.2
+ #
+ # Step 5 (after adding previous lines) -> Compute marginal taxes.
+ # (500 * (2.00 / 100)) + (2500 * (4.00 / 100)) + ((207935.2 - 500 - 2500) * (5.00 / 100)) -> 10356.76
+ # Convert back to pay period
+ # wh = 10356.76 / 52.0 rounded -> 199.17
+ wh = -199.17
+
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('AL'),
+ al_a4_sit_exemptions='S',
+ state_income_tax_additional_withholding=40.0,
+ state_income_tax_exempt=False,
+ al_a4_sit_dependents=dependents,
+ schedule_pay=schedule_pay)
+
+ self._log('2019 Alabama tax first payslip additional withholding:')
+ payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
+ payslip.compute_sheet()
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['EE_US_941_FIT'], -1422.4) # Hand Calculated.
+ self.assertPayrollEqual(cats['ER_US_SUTA'], salary * self.AL_UNEMP)
+ self.assertPayrollEqual(cats['EE_US_SIT'], wh - additional_wh)
+
+ def test_personal_exemption(self):
+ salary = 5500.0
+ schedule_pay = 'weekly'
+ # filing status default is single
+
+ # see https://revenue.alabama.gov/wp-content/uploads/2019/01/whbooklet_0119.pdf for reference
+ # Hand Calculated Amount to Test
+ # Step 1 -> 5500.00 for wages per period , 52.0 for monthly -> 5500 * 52.0 -> 286000.0
+ # Step 2A -> standard deduction for highest wage bracket -> 2000. Subtract from yearly income
+ # 286000.0 - 2000 = 284000.0
+ # Step 2B -> Subtract Federal Income Tax in yearly form -> Our Fed withholding is -1422.4 * 52.0 = -73964.8
+ # -> 284000.0 - 73964.8 = 210035.2
+ # Step 2C -> Subtract the personal exemption -> 0 for personal exemptioon
+ # -> 210035.2 - 0 = 210035.2
+ # Step 2D -> Subtract per dependent. No dependents so 0
+ # -> 210035.2 - 0 = 210035.2
+ #
+ # Step 5 (after adding previous lines) -> Compute marginal taxes.
+ # (500 * (2.00 / 100)) + (2500 * (4.00 / 100)) + ((210035.2 - 500 - 2500) * (5.00 / 100)) -> 10461.76
+ # Convert back to pay period
+ # wh = 10461.76 / 52.0 rounded -> 201.19
+ wh = -199.74
+
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('AL'),
+ al_a4_sit_exemptions='S',
+ state_income_tax_additional_withholding=0.0,
+ state_income_tax_exempt=False,
+ al_a4_sit_dependents=0.0,
+ schedule_pay=schedule_pay)
+
+ self._log('2019 Alabama tax first payslip additional withholding:')
+ payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
+ payslip.compute_sheet()
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['EE_US_941_FIT'], -1422.4) # Hand Calculated.
+ self.assertPayrollEqual(cats['ER_US_SUTA'], salary * self.AL_UNEMP)
+ self.assertPayrollEqual(cats['EE_US_SIT'], wh)
diff --git a/l10n_us_hr_payroll/tests/test_us_al_alabama_payslip_2020.py b/l10n_us_hr_payroll/tests/test_us_al_alabama_payslip_2020.py
new file mode 100644
index 00000000..23865fc7
--- /dev/null
+++ b/l10n_us_hr_payroll/tests/test_us_al_alabama_payslip_2020.py
@@ -0,0 +1,36 @@
+# 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 TestUsALPayslip(TestUsPayslip):
+ # Taxes and Rates
+ AL_UNEMP_MAX_WAGE = 8000.00
+ AL_UNEMP = 2.70
+
+ def _test_sit(self, wage, exempt, exemptions, additional_withholding, dependent, schedule_pay, date_start, expected_withholding):
+
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=wage,
+ state_id=self.get_us_state('AL'),
+ al_a4_sit_exemptions=exempt,
+ state_income_tax_exempt=exemptions,
+ state_income_tax_additional_withholding=additional_withholding,
+ al_a4_sit_dependents=dependent,
+ 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('AL', self.AL_UNEMP, date(2020, 1, 1), wage_base=self.AL_UNEMP_MAX_WAGE)
+ self._test_sit(10000.0, 'S', False, 0.0, 1.0, 'weekly', date(2020, 1, 1), 349.08)
+ self._test_sit(850.0, 'M', False, 0.0, 2.0, 'weekly', date(2020, 1, 1), 29.98)
+ self._test_sit(5000.0, 'H', False, 0.0, 2.0, 'bi-weekly', date(2020, 1, 1), 191.15)
+ self._test_sit(20000.0, 'MS', False, 2.0, 0, 'monthly', date(2020, 1, 1), 757.6)
+ self._test_sit(5500.0, '', True, 2.0, 150, 'weekly', date(2020, 1, 1), 0.00)
diff --git a/l10n_us_hr_payroll/tests/test_us_ar_arkansas_payslip_2019.py b/l10n_us_hr_payroll/tests/test_us_ar_arkansas_payslip_2019.py
new file mode 100644
index 00000000..73b0f59c
--- /dev/null
+++ b/l10n_us_hr_payroll/tests/test_us_ar_arkansas_payslip_2019.py
@@ -0,0 +1,72 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from .common import TestUsPayslip, process_payslip
+
+
+class TestUsARPayslip(TestUsPayslip):
+ # https://www.dfa.arkansas.gov/images/uploads/incomeTaxOffice/whformula.pdf Calculation based on this file.
+ AR_UNEMP_MAX_WAGE = 10000.00
+ AR_UNEMP = -3.2 / 100.0
+ AR_INC_TAX = -0.0535
+
+ def test_taxes_monthly(self):
+ salary = 2127.0
+ schedule_pay = 'monthly'
+
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('AR'),
+ state_income_tax_additional_withholding=0.0,
+ ar_ar4ec_sit_allowances=2.0,
+ state_income_tax_exempt=False,
+ schedule_pay='monthly')
+
+ self._log('2019 Arkansas tax first payslip weekly:')
+ payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
+ payslip.compute_sheet()
+ cats = self._getCategories(payslip)
+
+ # Not exempt from rule 1 or rule 2 - unemployment wages., and actual unemployment.
+ self.assertPayrollEqual(cats['ER_US_SUTA'], salary * self.AR_UNEMP)
+
+ process_payslip(payslip)
+
+ # Make a new payslip, this one will have maximums
+ remaining_AR_UNEMP_wages = self.AR_UNEMP_MAX_WAGE - salary if (self.AR_UNEMP_MAX_WAGE - 2*salary < salary) else salary
+ # We reached the cap of 10000.0 in the first payslip.
+ self._log('2019 Arkansas tax second payslip weekly:')
+ payslip = self._createPayslip(employee, '2019-02-01', '2019-02-28')
+ payslip.compute_sheet()
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['ER_US_SUTA'], remaining_AR_UNEMP_wages * self.AR_UNEMP)
+
+ def test_additional_withholding(self):
+ salary = 5000.0
+ schedule_pay = 'monthly'
+ pay_periods = 12
+ allowances = 2
+ # TODO: comment on how it was calculated
+ test_ar_amt = 2598.60
+
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('AR'),
+ state_income_tax_additional_withholding=100.0,
+ ar_ar4ec_sit_allowances=2.0,
+ state_income_tax_exempt=False,
+ schedule_pay='monthly')
+
+
+ self._log('2019 Arkansas tax first payslip weekly:')
+ payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
+ payslip.compute_sheet()
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['ER_US_SUTA'], salary * self.AR_UNEMP)
+ # TODO: change to hand the test_ar_amt already be divided by pay periods
+ self.assertPayrollAlmostEqual(cats['EE_US_SIT'], -round(test_ar_amt / pay_periods) - 100)
+
+ process_payslip(payslip)
diff --git a/l10n_us_hr_payroll/tests/test_us_ar_arkansas_payslip_2020.py b/l10n_us_hr_payroll/tests/test_us_ar_arkansas_payslip_2020.py
new file mode 100644
index 00000000..6afe3d4d
--- /dev/null
+++ b/l10n_us_hr_payroll/tests/test_us_ar_arkansas_payslip_2020.py
@@ -0,0 +1,35 @@
+# 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 TestUsARPayslip(TestUsPayslip):
+ # Taxes and Rates
+ AR_UNEMP_MAX_WAGE = 8000.0
+ AR_UNEMP = 2.9
+
+ def _test_sit(self, wage, exemptions, allowances, additional_withholding, schedule_pay, date_start, expected_withholding):
+
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=wage,
+ state_id=self.get_us_state('AR'),
+ state_income_tax_exempt=exemptions,
+ state_income_tax_additional_withholding=additional_withholding,
+ ar_ar4ec_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('AR', self.AR_UNEMP, date(2020, 1, 1), wage_base=self.AR_UNEMP_MAX_WAGE)
+ self._test_sit(5000.0, True, 0.0, 0, 'monthly', date(2020, 1, 1), 0.0)
+ self._test_sit(5000.0, False, 0.0, 0, 'monthly', date(2020, 1, 1), 221.0)
+ self._test_sit(700.0, False, 0.0, 150, 'weekly', date(2020, 1, 1), 175.0)
+ self._test_sit(7000.0, False, 2.0, 0, 'semi-monthly', date(2020, 1, 1), 420.0)
+ self._test_sit(3000.0, False, 1.0, 0, 'bi-weekly', date(2020, 1, 1), 142.0)
diff --git a/l10n_us_hr_payroll/tests/test_us_az_arizona_payslip_2019.py b/l10n_us_hr_payroll/tests/test_us_az_arizona_payslip_2019.py
new file mode 100644
index 00000000..b97063b6
--- /dev/null
+++ b/l10n_us_hr_payroll/tests/test_us_az_arizona_payslip_2019.py
@@ -0,0 +1,72 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from .common import TestUsPayslip, process_payslip
+
+
+class TestUsAZPayslip(TestUsPayslip):
+
+ # TAXES AND RATES
+ AZ_UNEMP_MAX_WAGE = 7000.00
+ AZ_UNEMP = -(2.00 / 100.0)
+
+ def test_taxes_with_additional_wh(self):
+ salary = 15000.00
+ schedule_pay = 'weekly'
+ withholding_percentage = 5.1
+ percent_wh = (5.10 / 100) # 5.1%
+ additional_wh = 12.50
+
+ wh_to_test = -((percent_wh * salary) + additional_wh)
+
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('AZ'),
+ state_income_tax_additional_withholding=12.50,
+ az_a4_sit_withholding_percentage=withholding_percentage,
+ schedule_pay=schedule_pay)
+
+ self._log('2019 Arizona tax first payslip weekly:')
+ payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
+ payslip.compute_sheet()
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['ER_US_SUTA'], self.AZ_UNEMP_MAX_WAGE * self.AZ_UNEMP)
+ self.assertPayrollEqual(cats['EE_US_SIT'], wh_to_test)
+
+ process_payslip(payslip)
+
+ remaining_AZ_UNEMP_wages = 0.0 # We already reached max unemployment wages.
+
+ self._log('2019 Arizona tax second payslip weekly:')
+ payslip = self._createPayslip(employee, '2019-02-01', '2019-02-28')
+ payslip.compute_sheet()
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['ER_US_SUTA'], remaining_AZ_UNEMP_wages * self.AZ_UNEMP)
+
+ def test_taxes_monthly(self):
+ salary = 1000.00
+ schedule_pay = 'monthly'
+ withholding_percentage = 2.7
+ percent_wh = (2.70 / 100) # 2.7%
+ additional_wh = 0.0
+ wh_to_test = -((percent_wh * salary) + additional_wh)
+
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('AZ'),
+ state_income_tax_additional_withholding=0.0,
+ az_a4_sit_withholding_percentage=withholding_percentage,
+ schedule_pay=schedule_pay)
+
+ self._log('2019 Arizona tax first payslip monthly:')
+ payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
+ payslip.compute_sheet()
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['ER_US_SUTA'], salary * self.AZ_UNEMP)
+ self.assertPayrollEqual(cats['EE_US_SIT'], wh_to_test)
+
+ process_payslip(payslip)
diff --git a/l10n_us_hr_payroll/tests/test_us_az_arizona_payslip_2020.py b/l10n_us_hr_payroll/tests/test_us_az_arizona_payslip_2020.py
new file mode 100644
index 00000000..248648bc
--- /dev/null
+++ b/l10n_us_hr_payroll/tests/test_us_az_arizona_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 TestUsAZPayslip(TestUsPayslip):
+ # Taxes and Rates
+ AZ_UNEMP_MAX_WAGE = 7000.0
+ AZ_UNEMP = 2.0
+
+ def _test_sit(self, wage, additional_withholding, withholding_percent, schedule_pay, date_start, expected_withholding):
+
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=wage,
+ state_id=self.get_us_state('AZ'),
+ state_income_tax_additional_withholding=additional_withholding,
+ az_a4_sit_withholding_percentage=withholding_percent,
+ 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('AZ', self.AZ_UNEMP, date(2020, 1, 1), wage_base=self.AZ_UNEMP_MAX_WAGE)
+ self._test_sit(1000.0, 0.0, 2.70, 'monthly', date(2020, 1, 1), 27.0)
+ self._test_sit(1000.0, 10.0, 2.70, 'monthly', date(2020, 1, 1), 37.0)
+ self._test_sit(15000.0, 0.0, 3.60, 'weekly', date(2020, 1, 1), 540.0)
+ self._test_sit(8000.0, 0.0, 4.20, 'semi-monthly', date(2020, 1, 1), 336.0)
+ self._test_sit(8000.0, 0.0, 0.00, 'semi-monthly', date(2020, 1, 1), 0.0)
diff --git a/l10n_us_hr_payroll/tests/test_us_ca_california_payslip_2019.py b/l10n_us_hr_payroll/tests/test_us_ca_california_payslip_2019.py
new file mode 100644
index 00000000..b9331fe3
--- /dev/null
+++ b/l10n_us_hr_payroll/tests/test_us_ca_california_payslip_2019.py
@@ -0,0 +1,245 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from .common import TestUsPayslip, process_payslip
+
+
+class TestUsCAPayslip(TestUsPayslip):
+ ###
+ # Taxes and Rates
+ ###
+ CA_MAX_WAGE = 7000
+ CA_UIT = -3.5 / 100.0
+ CA_ETT = -0.1 / 100.0
+ CA_SDI = -1.0 / 100.0
+
+ # Examples from https://www.edd.ca.gov/pdf_pub_ctr/20methb.pdf
+ def test_example_a(self):
+ salary = 210
+ schedule_pay = 'weekly'
+ allowances = 1
+ additional_allowances = 0
+
+ wh = 0.00
+
+ employee = self._createEmployee()
+
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('CA'),
+ ca_de4_sit_filing_status='single',
+ state_income_tax_additional_withholding=0.0,
+ ca_de4_sit_allowances=allowances,
+ ca_de4_sit_additional_allowances=additional_allowances,
+ schedule_pay=schedule_pay)
+
+ self._log('2019 California 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 * (self.CA_UIT + self.CA_ETT))
+ self.assertPayrollEqual(cats['EE_US_SUTA'], salary * self.CA_SDI)
+ self.assertPayrollEqual(cats['EE_US_SIT'], wh)
+
+ process_payslip(payslip)
+
+ def test_example_b(self):
+ salary = 1250
+ schedule_pay = 'bi-weekly'
+ allowances = 2
+ additional_allowances = 1
+
+ # Example B
+ subject_to_withholding = salary - 38
+ taxable_income = subject_to_withholding - 339
+ computed_tax = (taxable_income - 632) * 0.022 + 6.95 # 6.95 Marginal Amount
+ wh = computed_tax - 9.65 # two exemption allowances
+ wh = -wh
+
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('CA'),
+ ca_de4_sit_filing_status='married',
+ state_income_tax_additional_withholding=0.0,
+ ca_de4_sit_allowances=allowances,
+ ca_de4_sit_additional_allowances=additional_allowances,
+ schedule_pay=schedule_pay)
+
+ self._log('2019 California 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 * (self.CA_UIT + self.CA_ETT))
+ self.assertPayrollEqual(cats['EE_US_SUTA'], salary * self.CA_SDI)
+ self.assertPayrollEqual(cats['EE_US_SIT'], wh)
+
+ process_payslip(payslip)
+
+
+ def test_example_c(self):
+ salary = 4100
+ schedule_pay = 'monthly'
+ allowances = 5
+ additional_allowances = 0.0
+
+ wh = -9.3
+
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('CA'),
+ ca_de4_sit_filing_status='married',
+ state_income_tax_additional_withholding=0.0,
+ ca_de4_sit_allowances=allowances,
+ ca_de4_sit_additional_allowances=additional_allowances,
+ schedule_pay=schedule_pay)
+
+ self._log('2019 California 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 * (self.CA_UIT + self.CA_ETT))
+ self.assertPayrollEqual(cats['EE_US_SUTA'], salary * self.CA_SDI)
+ self.assertPayrollEqual(cats['EE_US_SIT'], wh)
+
+ process_payslip(payslip)
+
+ # Make a new payslip, this one will have maximums
+
+ remaining_ca_uit_wages = self.CA_MAX_WAGE - salary if (self.CA_MAX_WAGE - 2 * salary < salary) \
+ else salary
+
+ self._log('2019 California 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'], round((remaining_ca_uit_wages * (self.CA_UIT + self.CA_ETT)), 2))
+
+ def test_example_d(self):
+ salary = 800
+ schedule_pay = 'weekly'
+ allowances = 3
+ additional_allowances = 0
+
+ wh = -3.18
+
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('CA'),
+ ca_de4_sit_filing_status='head_household',
+ state_income_tax_additional_withholding=0.0,
+ ca_de4_sit_allowances=allowances,
+ ca_de4_sit_additional_allowances=additional_allowances,
+ schedule_pay=schedule_pay)
+
+ self._log('2019 California 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 * (self.CA_UIT + self.CA_ETT))
+ self.assertPayrollEqual(cats['EE_US_SUTA'], salary * self.CA_SDI)
+ self.assertPayrollEqual(cats['EE_US_SIT'], wh)
+
+ process_payslip(payslip)
+
+ # Make a new payslip, this one will have maximums
+
+ remaining_ca_uit_wages = self.CA_MAX_WAGE - salary if (self.CA_MAX_WAGE - 2 * salary < salary) \
+ else salary
+
+ self._log('2019 California 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'], round((remaining_ca_uit_wages * (self.CA_UIT + self.CA_ETT)), 2))
+
+ def test_example_e(self):
+ salary = 1800
+ schedule_pay = 'semi-monthly'
+ allowances = 4
+ additional_allowances = 0
+
+ wh = -3.08
+
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('CA'),
+ ca_de4_sit_filing_status='married',
+ state_income_tax_additional_withholding=0.0,
+ ca_de4_sit_allowances=allowances,
+ ca_de4_sit_additional_allowances=additional_allowances,
+ schedule_pay=schedule_pay)
+
+ self._log('2019 California 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 * (self.CA_UIT + self.CA_ETT))
+ self.assertPayrollEqual(cats['EE_US_SUTA'], salary * self.CA_SDI)
+ self.assertPayrollEqual(cats['EE_US_SIT'], wh)
+
+ process_payslip(payslip)
+
+ # Make a new payslip, this one will have maximums
+
+ remaining_ca_uit_wages = self.CA_MAX_WAGE - salary if (self.CA_MAX_WAGE - 2 * salary < salary) \
+ else salary
+
+ self._log('2019 California 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'], round((remaining_ca_uit_wages * (self.CA_UIT + self.CA_ETT)), 2))
+
+ def test_example_f(self):
+ salary = 45000
+ schedule_pay = 'annually'
+ allowances = 4
+ additional_allowances = 0
+
+ wh = -113.85
+
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('CA'),
+ ca_de4_sit_filing_status='married',
+ state_income_tax_additional_withholding=0.0,
+ ca_de4_sit_allowances=allowances,
+ ca_de4_sit_additional_allowances=additional_allowances,
+ schedule_pay=schedule_pay)
+
+ self._log('2019 California 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.CA_MAX_WAGE * (self.CA_UIT + self.CA_ETT))
+ self.assertPayrollEqual(cats['EE_US_SUTA'], self.CA_MAX_WAGE * self.CA_SDI)
+ self.assertPayrollEqual(cats['EE_US_SIT'], wh)
+
+ process_payslip(payslip)
\ No newline at end of file
diff --git a/l10n_us_hr_payroll/tests/test_us_ca_california_payslip_2020.py b/l10n_us_hr_payroll/tests/test_us_ca_california_payslip_2020.py
new file mode 100755
index 00000000..264b115e
--- /dev/null
+++ b/l10n_us_hr_payroll/tests/test_us_ca_california_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, timedelta
+from .common import TestUsPayslip
+
+
+class TestUsCAPayslip(TestUsPayslip):
+ ###
+ # 2020 Taxes and Rates
+ ###
+ CA_UNEMP_MAX_WAGE = 7000.0 # Note that this is used for SDI and FLI as well
+ CA_UIT = 3.4
+ CA_ETT = 0.1
+ CA_SDI = 1.0
+
+ def _test_sit(self, wage, filing_status, allowances, additional_allowances, additional_withholding, schedule_pay, date_start, expected_withholding):
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=wage,
+ state_id=self.get_us_state('CA'),
+ ca_de4_sit_filing_status=filing_status,
+ ca_de4_sit_allowances=allowances,
+ ca_de4_sit_additional_allowances=additional_allowances,
+ state_income_tax_additional_withholding=additional_withholding,
+ schedule_pay=schedule_pay)
+ payslip = self._createPayslip(employee, date_start, date_start + timedelta(days=7))
+ payslip.compute_sheet()
+ cats = self._getCategories(payslip)
+ self.assertPayrollAlmostEqual(cats.get('EE_US_SIT', 0.0), -expected_withholding if filing_status else 0.0)
+
+ def test_2020_taxes_example1(self):
+ combined_er_rate = self.CA_UIT + self.CA_ETT
+ self._test_er_suta('CA', combined_er_rate, date(2020, 1, 1), wage_base=self.CA_UNEMP_MAX_WAGE)
+ self._test_ee_suta('CA', self.CA_SDI, date(2020, 1, 1), wage_base=self.CA_UNEMP_MAX_WAGE, relaxed=True)
+ # these expected values come from https://www.edd.ca.gov/pdf_pub_ctr/20methb.pdf
+ self._test_sit(210.0, 'single', 1, 0, 0, 'weekly', date(2020, 1, 1), 0.00)
+ self._test_sit(1250.0, 'married', 2, 1, 0, 'bi-weekly', date(2020, 1, 1), 1.23)
+ self._test_sit(4100.0, 'married', 5, 0, 0, 'monthly', date(2020, 1, 1), 1.5)
+ self._test_sit(800.0, 'head_household', 3, 0, 0, 'weekly', date(2020, 1, 1), 2.28)
+ self._test_sit(1800.0, 'married', 4, 0, 0, 'semi-monthly', date(2020, 1, 1), 0.84)
+ self._test_sit(45000.0, 'married', 4, 0, 0, 'annually', date(2020, 1, 1), 59.78)
+ self._test_sit(45000.0, 'married', 4, 0, 20.0, 'annually', date(2020, 1, 1), 79.78)
+ self._test_sit(6000.0, '', 4, 0, 20.0, 'annually', date(2020, 1, 1), 0.00)
diff --git a/l10n_us_hr_payroll/tests/test_us_co_colorado_payslip_2020.py b/l10n_us_hr_payroll/tests/test_us_co_colorado_payslip_2020.py
new file mode 100755
index 00000000..0fa45d9c
--- /dev/null
+++ b/l10n_us_hr_payroll/tests/test_us_co_colorado_payslip_2020.py
@@ -0,0 +1,37 @@
+# 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 TestUsCOPayslip(TestUsPayslip):
+ ###
+ # 2020 Taxes and Rates
+ ###
+ CO_UNEMP_MAX_WAGE = 13600.0
+ CO_UNEMP = 1.7
+
+ def _test_sit(self, wage, filing_status, additional_withholding, schedule_pay, date_start, expected_withholding, state_income_tax_exempt=False):
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=wage,
+ state_id=self.get_us_state('CO'),
+ fed_941_fit_w4_filing_status=filing_status,
+ state_income_tax_additional_withholding=additional_withholding,
+ state_income_tax_exempt=state_income_tax_exempt,
+ 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('CO', self.CO_UNEMP, date(2020, 1, 1), wage_base=self.CO_UNEMP_MAX_WAGE)
+ self._test_sit(5000.0, 'married', 0.0, 'semi-monthly', date(2020, 1, 1), 216.07)
+ self._test_sit(800.0, 'single', 0.0, 'weekly', date(2020, 1, 1), 33.48)
+ self._test_sit(20000.0, 'married', 0.0, 'quarterly', date(2020, 1, 1), 833.4)
+ self._test_sit(20000.0, 'married', 10.0, 'quarterly', date(2020, 1, 1), 843.4)
+ self._test_sit(20000.0, 'married', 0.0, 'quarterly', date(2020, 1, 1), 0.0, True)
+ self._test_sit(800.0, '', 0.0, 'weekly', date(2020, 1, 1), 0.00)
diff --git a/l10n_us_hr_payroll/tests/test_us_ct_connecticut_payslip_2019.py b/l10n_us_hr_payroll/tests/test_us_ct_connecticut_payslip_2019.py
new file mode 100644
index 00000000..ab423131
--- /dev/null
+++ b/l10n_us_hr_payroll/tests/test_us_ct_connecticut_payslip_2019.py
@@ -0,0 +1,121 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from .common import TestUsPayslip, process_payslip
+
+
+class TestUsCTPayslip(TestUsPayslip):
+ # TAXES AND RATES
+ CT_UNEMP_MAX_WAGE = 15000.00
+ CT_UNEMP = -(3.40 / 100.0)
+
+ def test_taxes_weekly_with_additional_wh(self):
+
+ # Tax tables can be found here:
+ # https://portal.ct.gov/-/media/DRS/Publications/pubsip/2019/IP-2019(1).pdf?la=en
+ # Step 1 - Wages per period -> 10000.00
+ salary = 10000.00
+ # Step 2 and 3 - Annual wages -> 10000.00 * 52.0 -> 520000.0
+ schedule_pay = 'weekly'
+ # Step 4 Employee Withholding Code -> A
+ wh_code = 'a'
+ # Step 5 - Use annual wages and withholding code with table for exemption amount.
+ # exemption_amt = 0 since highest bracket.
+ # Step 6 - Subtract 5 from 3 for taxable income.
+ # taxable income = 520000.00 since we do not have an exemption.
+ # Step 7 - Determine initial amount from table
+ # initial = 31550 + ((6.99 / 100) * (520000.00 - 500000.00))
+ # 32948.0
+ # Step 8 - Determine the tax rate phase out add back from table.
+ # phase_out = 200
+ # Step 9 - Determine the recapture amount from table.
+ # Close to top, but not top. -> 2900
+ # Step 10 - Add Step 7, 8, 9
+ # 32948.0 + 200 + 2900.00 - > 36048.0
+ # Step 11 - Determine decimal amount from personal tax credits.
+ # We get no tax credit.
+ # Step 12 - Multiple Step 10 by 1.00 - Step 11
+ # 36048.0 * 1.00 = 36048.0
+ # Step 13 - Divide by the number of pay periods.
+ # 36048.0 / 52.0 = 693.23
+ # Step 14 & 15 & 16- Add / Subtract the additional or under withholding amount. Then Add this to the amount
+ # for withholding per period.
+ additional_wh = 12.50
+ # 693.23 + 12.50 ->
+ wh = -705.73
+
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('CT'),
+ ct_w4na_sit_code=wh_code,
+ state_income_tax_additional_withholding=additional_wh,
+ schedule_pay=schedule_pay)
+
+ self._log('2019 Connecticut tax first payslip weekly:')
+ payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
+ payslip.compute_sheet()
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['ER_US_SUTA'], salary * self.CT_UNEMP)
+ self.assertPayrollEqual(cats['EE_US_SIT'], wh)
+
+ process_payslip(payslip)
+
+ remaining_CT_UNEMP_wages = 5000.00 # We already reached the maximum wage for unemployment insurance.
+ self._log('2019 Connecticut tax second payslip weekly:')
+ payslip = self._createPayslip(employee, '2019-02-01', '2019-02-28')
+ payslip.compute_sheet()
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['ER_US_SUTA'], remaining_CT_UNEMP_wages * self.CT_UNEMP)
+
+ def test_taxes_weekly_with_different_code(self):
+
+ # Tax tables can be found here:
+ # https://portal.ct.gov/-/media/DRS/Publications/pubsip/2019/IP-2019(1).pdf?la=en
+ # Step 1 - Wages per period -> 15000.00
+ salary = 15000.00
+ # Step 2 and 3 - Annual wages -> 15000.00 * 12.0 -> 180000.0
+ schedule_pay = 'monthly'
+ # Step 4 Employee Withholding Code -> B
+ wh_code = 'b'
+ # Step 5 - Use annual wages and withholding code with table for exemption amount.
+ # exemption_amt = 0 since highest bracket.
+ # Step 6 - Subtract 5 from 3 for taxable income.
+ # taxable income = 180000.0 since we do not have an exemption.
+ # Step 7 - Determine initial amount from table
+ # initial = 8080 + ((6.00 / 100) * (180000.0 - 160000))
+ # 9280.0
+ # Step 8 - Determine the tax rate phase out add back from table.
+ # phase_out = 320
+ # Step 9 - Determine the recapture amount from table.
+ # Bottom -> 0
+ # Step 10 - Add Step 7, 8, 9
+ # 9280.0 + 320 + 0 - > 9600.0
+ # Step 11 - Determine decimal amount from personal tax credits.
+ # We get no tax credit.
+ # Step 12 - Multiple Step 10 by 1.00 - Step 11
+ # 9600.0 * 1.00 = 9600.0
+ # Step 13 - Divide by the number of pay periods.
+ # 9600.0 / 12.0 = 800.0
+ # Step 14 & 15 & 16- Add / Subtract the additional or under withholding amount. Then Add this to the amount
+ # for withholding per period.
+ additional_wh = 15.00
+ # 800.0 + 15.00 ->
+ wh = -815.0
+
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('CT'),
+ ct_w4na_sit_code=wh_code,
+ state_income_tax_additional_withholding=additional_wh,
+ schedule_pay=schedule_pay)
+
+ self._log('2019 Connecticut tax first payslip monthly:')
+ payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
+ payslip.compute_sheet()
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['ER_US_SUTA'], self.CT_UNEMP_MAX_WAGE * self.CT_UNEMP)
+ self.assertPayrollEqual(cats['EE_US_SIT'], wh)
diff --git a/l10n_us_hr_payroll/tests/test_us_ct_connecticut_payslip_2020.py b/l10n_us_hr_payroll/tests/test_us_ct_connecticut_payslip_2020.py
new file mode 100644
index 00000000..8ce41d06
--- /dev/null
+++ b/l10n_us_hr_payroll/tests/test_us_ct_connecticut_payslip_2020.py
@@ -0,0 +1,35 @@
+# 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 TestUsCTPayslip(TestUsPayslip):
+ # Taxes and Rates
+ CT_UNEMP_MAX_WAGE = 15000.0
+ CT_UNEMP = 3.2
+
+ def _test_sit(self, wage, withholding_code, additional_withholding, schedule_pay, date_start, expected_withholding):
+
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=wage,
+ state_id=self.get_us_state('CT'),
+ ct_w4na_sit_code=withholding_code,
+ state_income_tax_additional_withholding=additional_withholding,
+ 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('CT', self.CT_UNEMP, date(2020, 1, 1), wage_base=self.CT_UNEMP_MAX_WAGE)
+ self._test_sit(10000.0, 'a', 0.0, 'weekly', date(2020, 1, 1), 693.23)
+ self._test_sit(12000.0, 'b', 15.0, 'bi-weekly', date(2020, 1, 1), 688.85)
+ self._test_sit(5000.0, 'f', 15.0, 'monthly', date(2020, 1, 1), 230.25)
+ self._test_sit(15000.0, 'c', 0.0, 'monthly', date(2020, 1, 1), 783.33)
+ self._test_sit(18000.0, 'b', 0.0, 'weekly', date(2020, 1, 1), 1254.35)
+ self._test_sit(500.0, 'd', 0.0, 'weekly', date(2020, 1, 1), 21.15)
diff --git a/l10n_us_hr_payroll/tests/test_us_de_delaware_payslip_2020.py b/l10n_us_hr_payroll/tests/test_us_de_delaware_payslip_2020.py
new file mode 100755
index 00000000..ed285368
--- /dev/null
+++ b/l10n_us_hr_payroll/tests/test_us_de_delaware_payslip_2020.py
@@ -0,0 +1,36 @@
+# 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 TestUsDEPayslip(TestUsPayslip):
+ ###
+ # 2020 Taxes and Rates
+ ###
+ DE_UNEMP_MAX_WAGE = 16500.0
+ DE_UNEMP = 1.50
+ # Calculation based on section 17. https://revenue.delaware.gov/employers-guide-withholding-regulations-employers-duties/
+
+ def _test_sit(self, wage, filing_status, additional_withholding, dependents, schedule_pay, date_start, expected_withholding):
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=wage,
+ state_id=self.get_us_state('DE'),
+ de_w4_sit_filing_status=filing_status,
+ state_income_tax_additional_withholding=additional_withholding,
+ de_w4_sit_dependent=dependents,
+ 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('DE', self.DE_UNEMP, date(2020, 1, 1), wage_base=self.DE_UNEMP_MAX_WAGE)
+ self._test_sit(480.77, 'single', 0.0, 1.0, 'weekly', date(2020, 1, 1), 13.88)
+ self._test_sit(5000.0, 'single', 0.0, 2.0, 'monthly', date(2020, 1, 1), 211.93)
+ self._test_sit(5000.0, 'single', 10.0, 1.0, 'monthly', date(2020, 1, 1), 231.1)
+ self._test_sit(20000.0, 'married', 0.0, 3.0, 'quarterly', date(2020, 1, 1), 876.0)
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
new file mode 100755
index 00000000..419be377
--- /dev/null
+++ b/l10n_us_hr_payroll/tests/test_us_fl_florida_payslip_2019.py
@@ -0,0 +1,84 @@
+# 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
+
+
+class TestUsFlPayslip(TestUsPayslip):
+ ###
+ # 2019 Taxes and Rates
+ ###
+ FL_UNEMP_MAX_WAGE = 7000.0
+ FL_UNEMP = -2.7 / 100.0
+
+ def test_2019_taxes(self):
+ salary = 5000.0
+
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('FL'))
+
+ self._log('2019 Florida 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 * self.FL_UNEMP)
+
+ process_payslip(payslip)
+
+ # Make a new payslip, this one will have maximums
+
+ remaining_fl_unemp_wages = self.FL_UNEMP_MAX_WAGE - salary if (self.FL_UNEMP_MAX_WAGE - 2*salary < salary) \
+ else salary
+
+ self._log('2019 Florida 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_fl_unemp_wages * self.FL_UNEMP)
+
+ def test_2019_taxes_with_external(self):
+ salary = 5000.0
+ external_wages = 6000.0
+
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=salary,
+ external_wages=external_wages,
+ state_id=self.get_us_state('FL'))
+
+ self._log('2019 Forida_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.FL_UNEMP_MAX_WAGE - external_wages) * self.FL_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,
+ external_wages=external_wages,
+ futa_type=USHRContract.FUTA_TYPE_BASIC,
+ state_id=self.get_us_state('FL'))
+
+ self._log('2019 Forida_external tax first payslip:')
+ payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats.get('ER_US_SUTA', 0.0), 0.0)
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
new file mode 100755
index 00000000..5952eb1f
--- /dev/null
+++ b/l10n_us_hr_payroll/tests/test_us_fl_florida_payslip_2020.py
@@ -0,0 +1,16 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from datetime import date
+from .common import TestUsPayslip
+
+
+class TestUsFlPayslip(TestUsPayslip):
+ ###
+ # 2020 Taxes and Rates
+ ###
+ FL_UNEMP_MAX_WAGE = 7000.0
+ FL_UNEMP = 2.7
+
+ def test_2020_taxes(self):
+ # Only has state unemployment
+ self._test_er_suta('FL', self.FL_UNEMP, date(2020, 1, 1), wage_base=self.FL_UNEMP_MAX_WAGE)
diff --git a/l10n_us_hr_payroll/tests/test_us_ga_georgia_payslip_2019.py b/l10n_us_hr_payroll/tests/test_us_ga_georgia_payslip_2019.py
new file mode 100755
index 00000000..98206965
--- /dev/null
+++ b/l10n_us_hr_payroll/tests/test_us_ga_georgia_payslip_2019.py
@@ -0,0 +1,135 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from .common import TestUsPayslip, process_payslip
+
+
+class TestUsGAPayslip(TestUsPayslip):
+
+ # TAXES AND RATES
+ GA_UNEMP_MAX_WAGE = 9500.00
+ GA_UNEMP = -(2.70 / 100.0)
+
+ def test_taxes_weekly_single_with_additional_wh(self):
+ salary = 15000.00
+ schedule_pay = 'weekly'
+ allowances = 1
+ filing_status = 'single'
+ additional_wh = 12.50
+ # Hand Calculated Amount to Test
+ # Step 1 - Subtract standard deduction from wages. Std Deduct for single weekly is 88.50
+ # step1 = 15000.00 - 88.50 = 14911.5
+ # Step 2 - Subtract personal allowance from step1. Allowance for single weekly is 51.92
+ # step2 = step1 - 51.92 = 14859.58
+ # Step 3 - Subtract amount for dependents. Weekly dependent allowance is 57.50
+ # step3 = 14859.58 - 57.50 = 14802.08
+ # Step 4 -Determine wh amount from tables
+ # step4 = 4.42 + ((5.75 / 100.00) * (14802.08 - 135.00))
+ # Add additional_wh
+ # wh = 847.7771 + 12.50 = 860.2771
+ wh = -860.28
+
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('GA'),
+ ga_g4_sit_dependent_allowances=allowances,
+ ga_g4_sit_additional_allowances=0,
+ ga_g4_sit_filing_status=filing_status,
+ state_income_tax_additional_withholding=additional_wh,
+ schedule_pay=schedule_pay)
+
+ self.assertEqual(contract.schedule_pay, 'weekly')
+
+ self._log('2019 Georgia tax first payslip weekly:')
+ payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
+ payslip.compute_sheet()
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['ER_US_SUTA'], self.GA_UNEMP_MAX_WAGE * self.GA_UNEMP)
+ self.assertPayrollEqual(cats['EE_US_SIT'], wh)
+
+ process_payslip(payslip)
+
+ remaining_GA_UNEMP_wages = 0.0 # We already reached max unemployment wages.
+
+ self._log('2019 Georgia tax second payslip weekly:')
+ payslip = self._createPayslip(employee, '2019-02-01', '2019-02-28')
+ payslip.compute_sheet()
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['ER_US_SUTA'], remaining_GA_UNEMP_wages * self.GA_UNEMP)
+
+
+ def test_taxes_monthly_head_of_household(self):
+ salary = 25000.00
+ schedule_pay = 'monthly'
+ allowances = 2
+ filing_status = 'head of household'
+ additional_wh = 15.00
+ # Hand Calculated Amount to Test
+ # Step 1 - Subtract standard deduction from wages. Std Deduct for head of household monthly is 383.50
+ # step1 = 25000.00 - 383.50 = 24616.5
+ # Step 2 - Subtract personal allowance from step1. Allowance for head of household monthly is 225.00
+ # step2 = 24616.5 - 225.00 = 24391.5
+ # Step 3 - Subtract amount for dependents. Weekly dependent allowance is 250.00
+ # step3 = 24391.5 - (2 * 250.00) = 23891.5
+ # Step 4 - Determine wh amount from tables
+ # step4 = 28.33 + ((5.75 / 100.00) * (23891.5 - 833.00)) = 1354.19375
+ # Add additional_wh
+ # wh = 1354.19375 + 15.00 = 1369.19375
+ wh = -1369.19
+
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('GA'),
+ ga_g4_sit_dependent_allowances=allowances,
+ ga_g4_sit_additional_allowances=0,
+ ga_g4_sit_filing_status=filing_status,
+ state_income_tax_additional_withholding=additional_wh,
+ schedule_pay=schedule_pay)
+
+ self.assertEqual(contract.schedule_pay, 'monthly')
+
+ self._log('2019 Georgia tax first payslip monthly:')
+ payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
+ payslip.compute_sheet()
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['ER_US_SUTA'], self.GA_UNEMP_MAX_WAGE * self.GA_UNEMP)
+ self.assertPayrollAlmostEqual(cats['EE_US_SIT'], wh)
+
+ process_payslip(payslip)
+
+ remaining_GA_UNEMP_wages = 0.0 # We already reached max unemployment wages.
+
+ self._log('2019 Georgia tax second payslip weekly:')
+ payslip = self._createPayslip(employee, '2019-02-01', '2019-02-28')
+ payslip.compute_sheet()
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['ER_US_SUTA'], remaining_GA_UNEMP_wages * self.GA_UNEMP)
+
+ def test_taxes_exempt(self):
+ salary = 25000.00
+ schedule_pay = 'monthly'
+ allowances = 2
+ filing_status = ''
+ additional_wh = 15.00
+
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('GA'),
+ ga_g4_sit_dependent_allowances=allowances,
+ ga_g4_sit_additional_allowances=0,
+ ga_g4_sit_filing_status=filing_status,
+ state_income_tax_additional_withholding=additional_wh,
+ schedule_pay=schedule_pay)
+
+ self._log('2019 Georgia tax first payslip exempt:')
+ payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
+ payslip.compute_sheet()
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats.get('EE_US_SIT', 0), 0)
diff --git a/l10n_us_hr_payroll/tests/test_us_ga_georgia_payslip_2020.py b/l10n_us_hr_payroll/tests/test_us_ga_georgia_payslip_2020.py
new file mode 100755
index 00000000..21a0a810
--- /dev/null
+++ b/l10n_us_hr_payroll/tests/test_us_ga_georgia_payslip_2020.py
@@ -0,0 +1,39 @@
+# 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 TestUsGAPayslip(TestUsPayslip):
+
+ # TAXES AND RATES
+ GA_UNEMP_MAX_WAGE = 9500.00
+ GA_UNEMP = 2.70
+
+ # Example calculated based on https://dor.georgia.gov/employers-tax-guide 2020_employer tax gauide
+
+ def _test_sit(self, wage, filing_status, additional_withholding, dependent_allowances, additional_allowances,
+ schedule_pay, date_start, expected_withholding):
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=wage,
+ state_id=self.get_us_state('GA'),
+ ga_g4_sit_filing_status=filing_status,
+ state_income_tax_additional_withholding=additional_withholding,
+ ga_g4_sit_dependent_allowances=dependent_allowances,
+ ga_g4_sit_additional_allowances=additional_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.assertPayrollAlmostEqual(cats.get('EE_US_SIT', 0.0), -expected_withholding)
+
+ def test_2020_taxes_example(self):
+ self._test_er_suta('GA', self.GA_UNEMP, date(2020, 1, 1), wage_base=self.GA_UNEMP_MAX_WAGE)
+ self._test_sit(15000.0, 'single', 12.50, 1, 0, 'weekly', date(2020, 1, 1), 860.28)
+ self._test_sit(25000.0, 'head of household', 15.00, 2, 0, 'monthly', date(2020, 1, 1), 1369.19)
+ self._test_sit(425.0, 'married filing separate', 0.0, 1, 0, 'weekly', date(2020, 1, 1), 11.45)
+ self._test_sit(3000.0, 'single', 0.00, 1, 1, 'quarterly', date(2020, 1, 1), 0.0)
+ self._test_sit(2500.0, '', 0.00, 1, 1, 'quarterly', date(2020, 1, 1), 0.0)
diff --git a/l10n_us_hr_payroll/tests/test_us_hi_hawaii_payslip_2019.py b/l10n_us_hr_payroll/tests/test_us_hi_hawaii_payslip_2019.py
new file mode 100644
index 00000000..13f1f2b5
--- /dev/null
+++ b/l10n_us_hr_payroll/tests/test_us_hi_hawaii_payslip_2019.py
@@ -0,0 +1,93 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from .common import TestUsPayslip, process_payslip
+
+
+class TestUsHIPayslip(TestUsPayslip):
+
+ # TAXES AND RATES
+ HI_UNEMP_MAX_WAGE = 46800.00
+ HI_UNEMP = -(2.40 / 100.0)
+
+ def test_taxes_single_weekly(self):
+ salary = 375.00
+ schedule_pay = 'weekly'
+ filing_status = 'single'
+ allowances = 3
+ wh_to_check = -15.3
+ # Taxable income = (wage * payperiod ) - (allownaces * personal_exemption)
+ # taxable_income = (375 * 52) - (3 * 1144) = 16068
+ # Last = row[0] = 692
+ # withholding = row[1] + ((row[2] / 100.0) * (taxable_income - last))
+ # withholding = 682 + ((6.80 / 100.0 ) * (16068 - 14400)) = 795.42
+ # wh_to_check = 795.42/52 = 15.3
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('HI'),
+ hi_hw4_sit_filing_status=filing_status,
+ state_income_tax_additional_withholding=0.0,
+ hi_hw4_sit_allowances=allowances,
+ schedule_pay=schedule_pay)
+
+ self._log('2019 Hawaii tax first payslip single:')
+ payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
+ payslip.compute_sheet()
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['ER_US_SUTA'], salary * self.HI_UNEMP)
+ self.assertPayrollEqual(cats['EE_US_SIT'], wh_to_check)
+
+ process_payslip(payslip)
+
+ remaining_id_unemp_wages = self.HI_UNEMP_MAX_WAGE - salary if (self.HI_UNEMP_MAX_WAGE - 2*salary < salary) \
+ else salary
+
+ self._log('2019 Hawaii tax second payslip single:')
+ payslip = self._createPayslip(employee, '2019-02-01', '2019-02-28')
+ payslip.compute_sheet()
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['ER_US_SUTA'], remaining_id_unemp_wages * self.HI_UNEMP)
+
+ def test_taxes_married_monthly(self):
+ salary = 5000.00
+ schedule_pay = 'monthly'
+ filing_status = 'married'
+ allowances = 2
+ wh_to_check = -287.1
+ # Taxable income = (wage * payperiod ) - (allownaces * personal_exemption)
+ # taxable_income = (5000 * 12) - (2 * 1144) = 57712
+ # Last = row[0] = 48000
+ # withholding = row[1] + ((row[2] / 100.0) * (taxable_income - last))
+ # withholding = 2707 + ((7.70 / 100.0 ) * (57712 - 48000)) = 3445.112
+ # wh_to_check = 3445.112/52 = 287.092
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('HI'),
+ hi_hw4_sit_filing_status=filing_status,
+ state_income_tax_additional_withholding=0.0,
+ hi_hw4_sit_allowances=allowances,
+ schedule_pay=schedule_pay)
+
+ self._log('2019 Hawaii tax first payslip monthly:')
+ payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
+ payslip.compute_sheet()
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['ER_US_SUTA'], salary * self.HI_UNEMP)
+ self.assertPayrollEqual(cats['EE_US_SIT'], wh_to_check)
+
+ process_payslip(payslip)
+
+ remaining_id_unemp_wages = self.HI_UNEMP_MAX_WAGE - salary if (self.HI_UNEMP_MAX_WAGE - 2*salary < salary) \
+ else salary
+
+ self._log('2019 Hawaii tax second payslip monthly:')
+ payslip = self._createPayslip(employee, '2019-02-01', '2019-02-28')
+ payslip.compute_sheet()
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['ER_US_SUTA'], remaining_id_unemp_wages * self.HI_UNEMP)
+
diff --git a/l10n_us_hr_payroll/tests/test_us_hi_hawaii_payslip_2020.py b/l10n_us_hr_payroll/tests/test_us_hi_hawaii_payslip_2020.py
new file mode 100755
index 00000000..9a746057
--- /dev/null
+++ b/l10n_us_hr_payroll/tests/test_us_hi_hawaii_payslip_2020.py
@@ -0,0 +1,37 @@
+# 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 TestUsHIPayslip(TestUsPayslip):
+ ###
+ # 2020 Taxes and Rates
+ ###
+ HI_UNEMP_MAX_WAGE = 48100.00
+ HI_UNEMP = 2.4
+
+ def _test_sit(self, wage, filing_status, additional_withholding, allowances, schedule_pay, date_start, expected_withholding):
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=wage,
+ state_id=self.get_us_state('HI'),
+ hi_hw4_sit_filing_status=filing_status,
+ state_income_tax_additional_withholding=additional_withholding,
+ hi_hw4_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('HI', self.HI_UNEMP, date(2020, 1, 1), wage_base=self.HI_UNEMP_MAX_WAGE)
+ self._test_sit(375.0, 'single', 0.0, 3.0, 'weekly', date(2020, 1, 1), 15.3)
+ self._test_sit(5000.0, 'married', 0.0, 2.0, 'monthly', date(2020, 1, 1), 287.1)
+ self._test_sit(5000.0, 'married', 10.0, 2.0, 'monthly', date(2020, 1, 1), 297.1)
+ self._test_sit(50000.0, 'head_of_household', 0.0, 3.0, 'weekly', date(2020, 1, 1), 3933.65)
+ self._test_sit(750.0, 'single', 10.0, 3.0, 'bi-weekly', date(2020, 1, 1), 40.59)
+ self._test_sit(3000.0, '', 0.0, 3.0, 'weekly', date(2020, 1, 1), 0.00)
diff --git a/l10n_us_hr_payroll/tests/test_us_ia_iowa_payslip_2019.py b/l10n_us_hr_payroll/tests/test_us_ia_iowa_payslip_2019.py
new file mode 100644
index 00000000..cb3bccfd
--- /dev/null
+++ b/l10n_us_hr_payroll/tests/test_us_ia_iowa_payslip_2019.py
@@ -0,0 +1,152 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from .common import TestUsPayslip, process_payslip
+
+
+class TestUsIAPayslip(TestUsPayslip):
+ IA_UNEMP_MAX_WAGE = 30600
+ IA_UNEMP = -1.0 / 100.0
+ IA_INC_TAX = -0.0535
+
+ def test_taxes_weekly(self):
+ wages = 30000.00
+ schedule_pay = 'weekly'
+ allowances = 1
+ additional_wh = 0.00
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=wages,
+ state_id=self.get_us_state('IA'),
+ state_income_tax_additional_withholding=additional_wh,
+ ia_w4_sit_allowances=allowances,
+ schedule_pay=schedule_pay)
+
+ self._log('2019 Iowa tax first payslip weekly:')
+ payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
+ payslip.compute_sheet()
+ cats = self._getCategories(payslip)
+
+ # T1 is the gross taxable wages for the pay period minus the Federal withholding amount. We add the federal
+ # withholding amount because it is calculated in the base US payroll module as a negative
+ # t1 = 30000 - (10399.66) = 19600.34
+ t1_to_test = wages + cats['EE_US_941_FIT']
+ self.assertPayrollAlmostEqual(t1_to_test, 19600.34)
+
+ # T2 is T1 minus our standard deduction which is a table of flat rates dependent on the number of allowances.
+ # In our case, we have a weekly period which on the table has a std deduct. of $32.50 for 0 or 1 allowances,
+ # and 80.00 of 2 or more allowances.
+ standard_deduction = 32.50 # The allowance tells us what standard_deduction amount to use.
+ # t2 = 19600.34 - 32.50 = 19567.84
+ t2_to_test = t1_to_test - standard_deduction
+ self.assertPayrollAlmostEqual(t2_to_test, 19567.84)
+ # T3 is T2 multiplied by the income rates in the large table plus a flat fee for that bracket.
+ # 1153.38 is the bracket floor. 8.53 is the rate, and 67.63 is the flat fee.
+ # t3 = 1638.38
+ t3_to_test = ((t2_to_test - 1153.38) * (8.53 / 100)) + 67.63
+ self.assertPayrollAlmostEqual(t3_to_test, 1638.38)
+ # T4 is T3 minus a flat amount determined by pay period * the number of deductions. For 2019, our weekly
+ # deduction amount per allowance is 0.77
+ # t4 = 1638.38 - 0.77 = 155.03
+ t4_to_test = t3_to_test - (0.77 * allowances)
+ self.assertPayrollAlmostEqual(t4_to_test, 1637.61)
+ # t5 is our T4 plus the additional withholding per period
+ # t5 = 1637.61 + 0.0
+ # Convert to negative as well.
+ t5_to_test = -t4_to_test - additional_wh
+ self.assertPayrollAlmostEqual(t5_to_test, -1637.61)
+
+ self.assertPayrollEqual(cats['ER_US_SUTA'], wages * self.IA_UNEMP)
+ self.assertPayrollEqual(cats['EE_US_SIT'], t5_to_test)
+
+
+ # Make a new payslip, this one will have maximums
+
+ remaining_IA_UNEMP_wages = self.IA_UNEMP_MAX_WAGE - wages if (self.IA_UNEMP_MAX_WAGE - 2*wages < wages) \
+ else wages
+
+ self._log('2019 Iowa tax second payslip weekly:')
+ payslip = self._createPayslip(employee, '2019-02-01', '2019-02-28')
+ payslip.compute_sheet()
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['ER_US_SUTA'], wages * self.IA_UNEMP)
+
+ def test_taxes_biweekly(self):
+ wages = 3000.00
+ schedule_pay = 'bi-weekly'
+ allowances = 1
+ additional_wh = 0.00
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=wages,
+ state_id=self.get_us_state('IA'),
+ state_income_tax_additional_withholding=additional_wh,
+ ia_w4_sit_allowances=allowances,
+ schedule_pay=schedule_pay)
+
+ self._log('2019 Iowa tax first payslip bi-weekly:')
+ payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
+ payslip.compute_sheet()
+ cats = self._getCategories(payslip)
+
+ # T1 is the gross taxable wages for the pay period minus the Federal withholding amount. We add the federal
+ # withholding amount because it is calculated in the base US payroll module as a negative
+ t1_to_test = wages + cats['EE_US_941_FIT']
+ # T2 is T1 minus our standard deduction which is a table of flat rates dependent on the number of allowances.
+ # In our case, we have a biweekly period which on the table has a std deduct. of $65.00 for 0 or 1 allowances,
+ # and $160.00 of 2 or more allowances.
+ standard_deduction = 65.00 # The allowance tells us what standard_deduction amount to use.
+ t2_to_test = t1_to_test - standard_deduction
+ # T3 is T2 multiplied by the income rates in the large table plus a flat fee for that bracket.
+ t3_to_test = ((t2_to_test - 2306.77) * (8.53 / 100)) + 135.28
+ # T4 is T3 minus a flat amount determined by pay period * the number of deductions. For 2019, our weekly
+ # deduction amount per allowance is 0.77
+ t4_to_test = t3_to_test - (1.54 * allowances)
+ # t5 is our T4 plus the additional withholding per period
+ t5_to_test = -t4_to_test - additional_wh
+
+ self.assertPayrollEqual(cats['ER_US_SUTA'], wages * self.IA_UNEMP)
+ self.assertPayrollEqual(cats['EE_US_SIT'], t5_to_test - additional_wh)
+
+ process_payslip(payslip)
+
+ def test_taxes_with_external_weekly(self):
+ wages = 2500.00
+ schedule_pay = 'weekly'
+ allowances = 1
+ additional_wh = 0.00
+
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=wages,
+ state_id=self.get_us_state('IA'),
+ state_income_tax_additional_withholding=additional_wh,
+ ia_w4_sit_allowances=allowances,
+ schedule_pay=schedule_pay)
+
+ self._log('2019 Iowa external tax first payslip external weekly:')
+ payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
+ payslip.compute_sheet()
+ cats = self._getCategories(payslip)
+
+
+ # T1 is the gross taxable wages for the pay period minus the Federal withholding amount. We add the federal
+ # withholding amount because it is calculated in the base US payroll module as a negative
+ t1_to_test = wages + cats['EE_US_941_FIT']
+ # T2 is T1 minus our standard deduction which is a table of flat rates dependent on the number of allowances.
+ # In our case, we have a weekly period which on the table has a std deduct. of $32.50 for 0 or 1 allowances,
+ # and 80.00 of 2 or more allowances.
+ standard_deduction = 32.50 # The allowance tells us what standard_deduction amount to use.
+ t2_to_test = t1_to_test - standard_deduction
+ # T3 is T2 multiplied by the income rates in the large table plus a flat fee for that bracket.
+ t3_to_test = ((t2_to_test - 1153.38) * (8.53 / 100)) + 67.63
+ # T4 is T3 minus a flat amount determined by pay period * the number of deductions. For 2019, our weekly
+ # deduction amount per allowance is 0.77
+ t4_to_test = t3_to_test - (0.77 * allowances)
+ # t5 is our T4 plus the additional withholding per period
+ t5_to_test = -t4_to_test - additional_wh
+
+ self.assertPayrollEqual(cats['ER_US_SUTA'], wages * self.IA_UNEMP)
+ self.assertPayrollAlmostEqual(cats['EE_US_SIT'], t5_to_test)
+
+ process_payslip(payslip)
\ No newline at end of file
diff --git a/l10n_us_hr_payroll/tests/test_us_ia_iowa_payslip_2020.py b/l10n_us_hr_payroll/tests/test_us_ia_iowa_payslip_2020.py
new file mode 100755
index 00000000..eaca0e71
--- /dev/null
+++ b/l10n_us_hr_payroll/tests/test_us_ia_iowa_payslip_2020.py
@@ -0,0 +1,36 @@
+# 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 TestUsIAPayslip(TestUsPayslip):
+ ###
+ # 2020 Taxes and Rates
+ ###
+ IA_UNEMP_MAX_WAGE = 31600.00
+ IA_UNEMP = 1.0
+
+ def _test_sit(self, wage, exempt, additional_withholding, allowances, schedule_pay, date_start, expected_withholding):
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=wage,
+ state_id=self.get_us_state('IA'),
+ state_income_tax_exempt=exempt,
+ state_income_tax_additional_withholding=additional_withholding,
+ ia_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.assertPayrollAlmostEqual(cats.get('EE_US_SIT', 0.0), -expected_withholding)
+
+ def test_2020_taxes_example(self):
+ self._test_er_suta('IA', self.IA_UNEMP, date(2020, 1, 1), wage_base=self.IA_UNEMP_MAX_WAGE)
+ self._test_sit(2100.0, False, 0.0, 3.0, 'bi-weekly', date(2020, 1, 1), 83.5)
+ self._test_sit(3000.0, True, 10.0, 1.0, 'bi-weekly', date(2020, 1, 1), 0.00)
+ self._test_sit(300.0, False, 0.0, 1.0, 'weekly', date(2020, 1, 1), 6.77)
+ self._test_sit(5000.0, False, 0.0, 1.0, 'monthly', date(2020, 1, 1), 230.76)
+ self._test_sit(7500.0, False, 10.0, 2.0, 'semi-monthly', date(2020, 1, 1), 432.84)
diff --git a/l10n_us_hr_payroll/tests/test_us_id_idaho_payslip_2019.py b/l10n_us_hr_payroll/tests/test_us_id_idaho_payslip_2019.py
new file mode 100644
index 00000000..8e3576d6
--- /dev/null
+++ b/l10n_us_hr_payroll/tests/test_us_id_idaho_payslip_2019.py
@@ -0,0 +1,85 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from .common import TestUsPayslip, process_payslip
+
+
+class TestUsIDPayslip(TestUsPayslip):
+
+ # TAXES AND RATES
+ ID_UNEMP_MAX_WAGE = 40000.00
+ ID_UNEMP = -(1.00 / 100.0)
+
+ def test_taxes_single_biweekly(self):
+ salary = 1212.00
+ schedule_pay = 'bi-weekly'
+ filing_status = 'single'
+ allowances = 4
+ # SEE https://tax.idaho.gov/i-1026.cfm?seg=compute for example calculations
+ wh_to_check = -10.00
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('ID'),
+ id_w4_sit_filing_status=filing_status,
+ id_w4_sit_allowances=allowances,
+ schedule_pay=schedule_pay)
+
+ self._log('2019 Idaho tax first payslip single:')
+ payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
+ payslip.compute_sheet()
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['ER_US_SUTA'], salary * self.ID_UNEMP)
+ self.assertPayrollEqual(cats['EE_US_SIT'], wh_to_check)
+
+ process_payslip(payslip)
+
+ remaining_id_unemp_wages = self.ID_UNEMP_MAX_WAGE - salary if (self.ID_UNEMP_MAX_WAGE - 2*salary < salary) \
+ else salary
+
+ self._log('2019 Idaho tax second payslip single:')
+ payslip = self._createPayslip(employee, '2019-02-01', '2019-02-28')
+ payslip.compute_sheet()
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['ER_US_SUTA'], remaining_id_unemp_wages * self.ID_UNEMP)
+
+ def test_taxes_married_monthly(self):
+ salary = 5000.00
+ schedule_pay = 'monthly'
+ filing_status = 'married'
+ allowances = 2
+
+ # ICTCAT says monthly allowances are 246.67
+ # we have 2 so 246.67 * 2 = 493.34
+ # 5000.00 - 493.34 = 4506.66
+ # Wh is 89$ plus 6.925% over 3959,00
+ # 126.92545499999999 - > 127.0
+ wh_to_check = -127.0
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('ID'),
+ id_w4_sit_filing_status=filing_status,
+ id_w4_sit_allowances=allowances,
+ schedule_pay=schedule_pay)
+
+ self._log('2019 Idaho tax first payslip monthly:')
+ payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
+ payslip.compute_sheet()
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['ER_US_SUTA'], salary * self.ID_UNEMP)
+ self.assertPayrollEqual(cats['EE_US_SIT'], wh_to_check)
+
+ process_payslip(payslip)
+
+ remaining_id_unemp_wages = self.ID_UNEMP_MAX_WAGE - salary if (self.ID_UNEMP_MAX_WAGE - 2*salary < salary) \
+ else salary
+
+ self._log('2019 Idaho tax second payslip monthly:')
+ payslip = self._createPayslip(employee, '2019-02-01', '2019-02-28')
+ payslip.compute_sheet()
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['ER_US_SUTA'], remaining_id_unemp_wages * self.ID_UNEMP)
diff --git a/l10n_us_hr_payroll/tests/test_us_id_idaho_payslip_2020.py b/l10n_us_hr_payroll/tests/test_us_id_idaho_payslip_2020.py
new file mode 100755
index 00000000..eb0da9a7
--- /dev/null
+++ b/l10n_us_hr_payroll/tests/test_us_id_idaho_payslip_2020.py
@@ -0,0 +1,35 @@
+# 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 TestUsIDPayslip(TestUsPayslip):
+ ###
+ # 2020 Taxes and Rates
+ ###
+ ID_UNEMP_MAX_WAGE = 41600.00
+ ID_UNEMP = 1.0
+
+ def _test_sit(self, wage, filing_status, allowances, schedule_pay, date_start, expected_withholding):
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=wage,
+ state_id=self.get_us_state('ID'),
+ id_w4_sit_filing_status=filing_status,
+ id_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('ID', self.ID_UNEMP, date(2020, 1, 1), wage_base=self.ID_UNEMP_MAX_WAGE)
+ self._test_sit(1212.0, 'single', 4.0, 'bi-weekly', date(2020, 1, 1), 10.0)
+ self._test_sit(10000.0, 'married', 1.0, 'annually', date(2020, 1, 1), 0.0)
+ self._test_sit(52000.0, 'married', 4.0, 'monthly', date(2020, 1, 1), 3345.0)
+ self._test_sit(5000.0, 'head of household', 0.0, 'semi-monthly', date(2020, 1, 1), 300.0)
+ self._test_sit(5900.0, 'single', 5.0, 'weekly', date(2020, 1, 1), 367.0)
diff --git a/l10n_us_hr_payroll/tests/test_us_il_illinois_payslip_2019.py b/l10n_us_hr_payroll/tests/test_us_il_illinois_payslip_2019.py
new file mode 100644
index 00000000..ba633607
--- /dev/null
+++ b/l10n_us_hr_payroll/tests/test_us_il_illinois_payslip_2019.py
@@ -0,0 +1,71 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from .common import TestUsPayslip, process_payslip
+
+
+class TestUsILPayslip(TestUsPayslip):
+ # TAXES AND RATES
+ IL_UNEMP_MAX_WAGE = 12960.00
+ IL_UNEMP = -(3.175 / 100.0)
+
+ def test_taxes_monthly(self):
+ salary = 15000.00
+ schedule_pay = 'monthly'
+ basic_allowances = 1
+ additional_allowances = 1
+ flat_rate = (4.95 / 100)
+ wh_to_test = -(flat_rate * (salary - ((basic_allowances * 2275 + additional_allowances * 1000) / 12.0)))
+
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('IL'),
+ state_income_tax_additional_withholding=0.0,
+ il_w4_sit_basic_allowances=1.0,
+ il_w4_sit_additional_allowances=1.0,
+ schedule_pay='monthly')
+
+ self._log('2019 Illinois tax first payslip monthly:')
+ payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
+ payslip.compute_sheet()
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['ER_US_SUTA'], self.IL_UNEMP_MAX_WAGE * self.IL_UNEMP)
+ self.assertPayrollEqual(cats['EE_US_SIT'], wh_to_test)
+
+ process_payslip(payslip)
+
+ remaining_IL_UNEMP_wages = 0.0 # We already reached max unemployment wages.
+
+ self._log('2019 Illinois tax second payslip monthly:')
+ payslip = self._createPayslip(employee, '2019-02-01', '2019-02-28')
+ payslip.compute_sheet()
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['ER_US_SUTA'], remaining_IL_UNEMP_wages * self.IL_UNEMP)
+
+ def test_taxes_with_additional_wh(self):
+ salary = 15000.00
+ schedule_pay = 'monthly'
+ basic_allowances = 1
+ additional_allowances = 1
+ additional_wh = 15.0
+ flat_rate = (4.95 / 100)
+ wh_to_test = -(flat_rate * (salary - ((basic_allowances * 2275 + additional_allowances * 1000) / 12.0)) + additional_wh)
+
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('IL'),
+ state_income_tax_additional_withholding=15.0,
+ il_w4_sit_basic_allowances=1.0,
+ il_w4_sit_additional_allowances=1.0,
+ schedule_pay='monthly')
+
+ self._log('2019 Illinois tax first payslip monthly:')
+ payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
+ payslip.compute_sheet()
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['ER_US_SUTA'], self.IL_UNEMP_MAX_WAGE * self.IL_UNEMP)
+ self.assertPayrollEqual(cats['EE_US_SIT'], wh_to_test)
diff --git a/l10n_us_hr_payroll/tests/test_us_il_illinois_payslip_2020.py b/l10n_us_hr_payroll/tests/test_us_il_illinois_payslip_2020.py
new file mode 100644
index 00000000..ead932e4
--- /dev/null
+++ b/l10n_us_hr_payroll/tests/test_us_il_illinois_payslip_2020.py
@@ -0,0 +1,36 @@
+# 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 TestUsILPayslip(TestUsPayslip):
+ # Taxes and Rates
+ IL_UNEMP_MAX_WAGE = 12740.0
+ IL_UNEMP = 3.125
+
+ def _test_sit(self, wage, additional_withholding, basic_allowances, additional_allowances, schedule_pay, date_start, expected_withholding):
+
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=wage,
+ state_id=self.get_us_state('IL'),
+ state_income_tax_additional_withholding=additional_withholding,
+ il_w4_sit_basic_allowances=basic_allowances,
+ il_w4_sit_additional_allowances=additional_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('IL', self.IL_UNEMP, date(2020, 1, 1), wage_base=self.IL_UNEMP_MAX_WAGE)
+ self._test_sit(800.0, 0.0, 2, 2, 'weekly', date(2020, 1, 1), 33.27)
+ self._test_sit(800.0, 10.0, 2, 2, 'weekly', date(2020, 1, 1), 43.27)
+ self._test_sit(2500.0, 0.0, 1, 1, 'monthly', date(2020, 1, 1), 110.04)
+ self._test_sit(2500.0, 0.0, 0, 0, 'monthly', date(2020, 1, 1), 123.75)
+ self._test_sit(3000.0, 15.0, 0, 0, 'quarterly', date(2020, 1, 1), 163.50)
+
diff --git a/l10n_us_hr_payroll/tests/test_us_in_indiana_payslip_2020.py b/l10n_us_hr_payroll/tests/test_us_in_indiana_payslip_2020.py
new file mode 100755
index 00000000..53b7ddf3
--- /dev/null
+++ b/l10n_us_hr_payroll/tests/test_us_in_indiana_payslip_2020.py
@@ -0,0 +1,36 @@
+# 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 TestUsINPayslip(TestUsPayslip):
+ ###
+ # 2020 Taxes and Rates
+ ###
+ IN_UNEMP_MAX_WAGE = 9500.0
+ IN_UNEMP = 2.5
+ # Calculation based on https://www.in.gov/dor/files/dn01.pdf
+
+ def _test_sit(self, wage, additional_withholding, personal_exemption, dependent_exemption, schedule_pay, date_start, expected_withholding):
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=wage,
+ state_id=self.get_us_state('IN'),
+ state_income_tax_additional_withholding=additional_withholding,
+ in_w4_sit_personal_exemption=personal_exemption,
+ in_w4_sit_dependent_exemption=dependent_exemption,
+ 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('IN', self.IN_UNEMP, date(2020, 1, 1), wage_base=self.IN_UNEMP_MAX_WAGE)
+ self._test_sit(800.0, 0.0, 5.0, 3.0, 'weekly', date(2020, 1, 1), 19.94)
+ self._test_sit(800.0, 10.0, 5.0, 3.0, 'weekly', date(2020, 1, 1), 29.94)
+ self._test_sit(9000.0, 0.0, 4.0, 3.0, 'monthly', date(2020, 1, 1), 267.82)
+ self._test_sit(10000.0, 0.0, 2.0, 2.0, 'bi-weekly', date(2020, 1, 1), 316.79)
diff --git a/l10n_us_hr_payroll/tests/test_us_ks_kansas_payslip_2020.py b/l10n_us_hr_payroll/tests/test_us_ks_kansas_payslip_2020.py
new file mode 100755
index 00000000..3ed586f8
--- /dev/null
+++ b/l10n_us_hr_payroll/tests/test_us_ks_kansas_payslip_2020.py
@@ -0,0 +1,36 @@
+# 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 TestUsKSPayslip(TestUsPayslip):
+ ###
+ # 2020 Taxes and Rates
+ ###
+ KS_UNEMP_MAX_WAGE = 14000.0
+ KS_UNEMP = 2.7
+ # Calculation based on example https://revenue.ky.gov/Forms/42A003(T)%20(12-2019)%202020%20Tax%20Tables.pdf
+
+ def _test_sit(self, wage, filing_status, allowances, additional_withholding, schedule_pay, date_start, expected_withholding):
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=wage,
+ state_id=self.get_us_state('KS'),
+ ks_k4_sit_filing_status=filing_status,
+ ks_k4_sit_allowances=allowances,
+ state_income_tax_additional_withholding=additional_withholding,
+ 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.assertPayrollAlmostEqual(cats.get('EE_US_SIT', 0.0), -expected_withholding)
+
+ def test_2020_taxes_example(self):
+ self._test_er_suta('KS', self.KS_UNEMP, date(2020, 1, 1), wage_base=self.KS_UNEMP_MAX_WAGE)
+ self._test_sit(6250, 'married', 2, 0, 'semi-monthly', date(2020, 1, 1), 290.00)
+ self._test_sit(5000, 'single', 1, 0, 'monthly', date(2020, 1, 1), 222.00)
+ self._test_sit(1500, 'married', 0, 0, 'bi-weekly', date(2020, 1, 1), 39.00)
+ self._test_sit(750, 'single', 2, 10, 'weekly', date(2020, 1, 1), 36.00)
diff --git a/l10n_us_hr_payroll/tests/test_us_ky_kentucky_payslip_2020.py b/l10n_us_hr_payroll/tests/test_us_ky_kentucky_payslip_2020.py
new file mode 100755
index 00000000..aa067848
--- /dev/null
+++ b/l10n_us_hr_payroll/tests/test_us_ky_kentucky_payslip_2020.py
@@ -0,0 +1,35 @@
+# 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 TestUsKYPayslip(TestUsPayslip):
+ ###
+ # 2020 Taxes and Rates
+ ###
+ KY_UNEMP_MAX_WAGE = 10800.0
+ KY_UNEMP = 2.7
+ # Calculation based on example https://revenue.ky.gov/Forms/42A003(T)%20(12-2019)%202020%20Tax%20Tables.pdf
+
+ def _test_sit(self, wage, additional_withholding, schedule_pay, date_start, expected_withholding):
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=wage,
+ state_id=self.get_us_state('KY'),
+ state_income_tax_additional_withholding=additional_withholding,
+ 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('KY', self.KY_UNEMP, date(2020, 1, 1), wage_base=self.KY_UNEMP_MAX_WAGE)
+ self._test_sit(3020, 0.0, 'monthly', date(2020, 1, 1), 139.96)
+ self._test_sit(1500, 0.0, 'bi-weekly', date(2020, 1, 1), 69.90)
+ self._test_sit(1500, 10.0, 'bi-weekly', date(2020, 1, 1), 79.90)
+ self._test_sit(750, 00.0, 'weekly', date(2020, 1, 1), 34.95)
+ self._test_sit(7000, 0.0, 'semi-monthly', date(2020, 1, 1), 344.48)
diff --git a/l10n_us_hr_payroll/tests/test_us_la_louisiana_payslip_2019.py b/l10n_us_hr_payroll/tests/test_us_la_louisiana_payslip_2019.py
new file mode 100644
index 00000000..1d01c618
--- /dev/null
+++ b/l10n_us_hr_payroll/tests/test_us_la_louisiana_payslip_2019.py
@@ -0,0 +1,91 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from .common import TestUsPayslip, process_payslip
+
+
+class TestUsLAPayslip(TestUsPayslip):
+
+ # TAXES AND RATES
+ LA_UNEMP_MAX_WAGE = 7700.00
+ LA_UNEMP = -(1.14 / 100.0)
+
+ def test_taxes_single_weekly(self):
+ salary = 700.00
+ schedule_pay = 'weekly'
+ filing_status = 'single'
+ exemptions = 1
+ dependents = 2
+ additional_withholding = 0
+ # SEE http://revenue.louisiana.gov/TaxForms/1306(1_12)TF.pdf for example calculations
+ # wh_to test is 19.42
+ # Our algorithm correctly rounds whereas theirs does it prematurely.
+ wh_to_check = -19.43
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('LA'),
+ la_l4_sit_filing_status=filing_status,
+ state_income_tax_additional_withholding=additional_withholding,
+ la_l4_sit_exemptions=exemptions,
+ la_l4_sit_dependents=dependents,
+ schedule_pay=schedule_pay)
+
+ self._log('2019 Louisiana tax first payslip weekly:')
+ payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
+ payslip.compute_sheet()
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['ER_US_SUTA'], salary * self.LA_UNEMP)
+ self.assertPayrollEqual(cats['EE_US_SIT'], wh_to_check)
+
+ process_payslip(payslip)
+
+ remaining_la_unemp_wages = self.LA_UNEMP_MAX_WAGE - salary if (self.LA_UNEMP_MAX_WAGE - 2*salary < salary) \
+ else salary
+
+ self._log('2019 Louisiana tax second payslip weekly:')
+ payslip = self._createPayslip(employee, '2019-02-01', '2019-02-28')
+ payslip.compute_sheet()
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['ER_US_SUTA'], remaining_la_unemp_wages * self.LA_UNEMP)
+
+ def test_taxes_married_biweekly(self):
+ salary = 4600.00
+ schedule_pay = 'bi-weekly'
+ filing_status = 'married'
+ exemptions = 2
+ dependents = 3
+ additional_withholding = 0
+ # SEE http://revenue.louisiana.gov/TaxForms/1306(1_12)TF.pdf for example calculations
+ # wh_to test is 157.12
+ wh_to_check = -157.12
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('LA'),
+ la_l4_sit_filing_status=filing_status,
+ state_income_tax_additional_withholding=additional_withholding,
+ la_l4_sit_exemptions=exemptions,
+ la_l4_sit_dependents=dependents,
+ schedule_pay=schedule_pay)
+
+ self._log('2019 Louisiana tax first payslip bi-weekly:')
+ payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
+ payslip.compute_sheet()
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['ER_US_SUTA'], salary * self.LA_UNEMP)
+ self.assertPayrollEqual(cats['EE_US_SIT'], wh_to_check)
+
+ process_payslip(payslip)
+
+ remaining_la_unemp_wages = self.LA_UNEMP_MAX_WAGE - salary if (self.LA_UNEMP_MAX_WAGE - 2*salary < salary) \
+ else salary
+
+ self._log('2019 Louisiana tax second payslip bi-weekly:')
+ payslip = self._createPayslip(employee, '2019-02-01', '2019-02-28')
+ payslip.compute_sheet()
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['ER_US_SUTA'], remaining_la_unemp_wages * self.LA_UNEMP)
diff --git a/l10n_us_hr_payroll/tests/test_us_la_louisiana_payslip_2020.py b/l10n_us_hr_payroll/tests/test_us_la_louisiana_payslip_2020.py
new file mode 100755
index 00000000..d23c3ab3
--- /dev/null
+++ b/l10n_us_hr_payroll/tests/test_us_la_louisiana_payslip_2020.py
@@ -0,0 +1,36 @@
+# 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 TestUsLAPayslip(TestUsPayslip):
+ ###
+ # 2020 Taxes and Rates
+ ###
+ LA_UNEMP_MAX_WAGE = 7700.0
+ LA_UNEMP = 1.14
+ # Calculation based on http://revenue.louisiana.gov/TaxForms/1306(1_12)TF.pdf
+
+ def _test_sit(self, wage, filing_status, additional_withholding, exemptions, dependents, schedule_pay, date_start, expected_withholding):
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=wage,
+ state_id=self.get_us_state('LA'),
+ la_l4_sit_filing_status=filing_status,
+ state_income_tax_additional_withholding=additional_withholding,
+ la_l4_sit_exemptions=exemptions,
+ la_l4_sit_dependents=dependents,
+ 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.assertPayrollAlmostEqual(cats.get('EE_US_SIT', 0.0), -expected_withholding)
+
+ def test_2020_taxes_example(self):
+ self._test_er_suta('LA', self.LA_UNEMP, date(2020, 1, 1), wage_base=self.LA_UNEMP_MAX_WAGE)
+ self._test_sit(700.0, 'single', 0.0, 1.0, 2.0, 'weekly', date(2020, 1, 1), 19.43)
+ self._test_sit(4600.0, 'married', 0.0, 2.0, 3.0, 'bi-weekly', date(2020, 1, 1), 157.12)
+ self._test_sit(6000.0, 'single', 10.0, 2.0, 3.0, 'monthly', date(2020, 1, 1), 219.08)
diff --git a/l10n_us_hr_payroll/tests/test_us_me_maine_payslip_2020.py b/l10n_us_hr_payroll/tests/test_us_me_maine_payslip_2020.py
new file mode 100644
index 00000000..165455ce
--- /dev/null
+++ b/l10n_us_hr_payroll/tests/test_us_me_maine_payslip_2020.py
@@ -0,0 +1,39 @@
+# 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 TestUsMEPayslip(TestUsPayslip):
+ ###
+ # 2020 Taxes and Rates
+ ###
+ ME_UNEMP_MAX_WAGE = 12000.0
+ ME_UNEMP = 1.92
+ # Calculation based on this file page.6 and 7 https://www.maine.gov/revenue/forms/with/2020/20_WH_Tab&Instructions.pdf
+
+ def _test_sit(self, wage, filing_status, additional_withholding, exempt, allowances, schedule_pay, date_start, expected_withholding):
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=wage,
+ state_id=self.get_us_state('ME'),
+ me_w4me_sit_filing_status=filing_status,
+ state_income_tax_additional_withholding=additional_withholding,
+ state_income_tax_exempt=exempt,
+ me_w4me_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.assertPayrollAlmostEqual(cats.get('EE_US_SIT', 0.0), -expected_withholding)
+
+ def test_2020_taxes_example(self):
+ self._test_er_suta('ME', self.ME_UNEMP, date(2020, 1, 1), wage_base=self.ME_UNEMP_MAX_WAGE)
+ self._test_sit(300.0, 'single', 0.0, False, 2, 'weekly', date(2020, 1, 1), 0.0)
+ self._test_sit(800.0, 'single', 0.0, False, 2, 'bi-weekly', date(2020, 1, 1), 6.00)
+ self._test_sit(4500.0, 'married', 0.0, True, 0, 'weekly', date(2020, 1, 1), 0.00)
+ self._test_sit(4500.0, 'married', 0.0, False, 2, 'monthly', date(2020, 1, 1), 113.00)
+ self._test_sit(4500.0, 'married', 10.0, False, 2, 'weekly', date(2020, 1, 1), 287.00)
+ self._test_sit(7000.0, '', 10.0, False, 2, 'weekly', date(2020, 1, 1), 0.00)
diff --git a/l10n_us_hr_payroll/tests/test_us_mi_michigan_payslip_2019.py b/l10n_us_hr_payroll/tests/test_us_mi_michigan_payslip_2019.py
new file mode 100755
index 00000000..b12baed2
--- /dev/null
+++ b/l10n_us_hr_payroll/tests/test_us_mi_michigan_payslip_2019.py
@@ -0,0 +1,194 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from .common import TestUsPayslip, process_payslip
+
+
+class TestUsMIPayslip(TestUsPayslip):
+ # Taxes and Rates
+ MI_UNEMP_MAX_WAGE = 9500.0
+ MI_UNEMP = - 2.7 / 100.0
+ MI_INC_TAX = - 4.25 / 100.0
+ ANNUAL_EXEMPTION_AMOUNT = 4400.00
+ PAY_PERIOD_DIVISOR = {
+ 'weekly': 52.0,
+ 'bi-weekly': 26.0,
+ 'semi-monthly': 24.0,
+ 'monthly': 12.0
+ }
+
+ def test_2019_taxes_weekly(self):
+ salary = 5000.0
+ schedule_pay = 'weekly'
+ exemptions = 1
+
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('MI'),
+ state_income_tax_additional_withholding=0.0,
+ mi_w4_sit_exemptions=1.0,
+ schedule_pay='weekly')
+
+ allowance_amount = self.ANNUAL_EXEMPTION_AMOUNT / self.PAY_PERIOD_DIVISOR[schedule_pay]
+ wh = -((salary - (allowance_amount * exemptions)) * -self.MI_INC_TAX)
+
+ self._log('2019 Michigan tax first payslip weekly:')
+ payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
+ payslip.compute_sheet()
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['ER_US_SUTA'], salary * self.MI_UNEMP)
+ self.assertPayrollAlmostEqual(cats['EE_US_SIT'], wh)
+ #
+ process_payslip(payslip)
+
+ # Make a new payslip, this one will have maximums
+ remaining_MI_UNEMP_wages = self.MI_UNEMP_MAX_WAGE - salary if (self.MI_UNEMP_MAX_WAGE - 2*salary < salary) \
+ else salary
+
+ self._log('2019 Michigan tax second payslip weekly:')
+ payslip = self._createPayslip(employee, '2019-02-01', '2019-02-28')
+ payslip.compute_sheet()
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['ER_US_SUTA'], remaining_MI_UNEMP_wages * self.MI_UNEMP)
+
+ def test_2019_taxes_biweekly(self):
+ salary = 5000.0
+ schedule_pay = 'bi-weekly'
+ allowance_amount = self.ANNUAL_EXEMPTION_AMOUNT / self.PAY_PERIOD_DIVISOR[schedule_pay]
+ exemption = 2
+
+ wh = -((salary - (allowance_amount * exemption)) * -self.MI_INC_TAX)
+
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('MI'),
+ state_income_tax_additional_withholding=0.0,
+ mi_w4_sit_exemptions=2.0,
+ schedule_pay='bi-weekly')
+
+ self._log('2019 Michigan tax first payslip bi-weekly:')
+ payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
+ payslip.compute_sheet()
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['ER_US_SUTA'], salary * self.MI_UNEMP)
+ self.assertPayrollEqual(cats['EE_US_SIT'], wh)
+
+ process_payslip(payslip)
+
+ # Make a new payslip, this one will have maximums
+ remaining_MI_UNEMP_wages = self.MI_UNEMP_MAX_WAGE - salary if (self.MI_UNEMP_MAX_WAGE - 2*salary < salary) \
+ else salary
+
+ self._log('2019 Michigan tax second payslip bi-weekly:')
+ payslip = self._createPayslip(employee, '2019-02-01', '2019-02-28')
+ payslip.compute_sheet()
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['ER_US_SUTA'], remaining_MI_UNEMP_wages * self.MI_UNEMP)
+
+ def test_2019_taxes_semimonthly(self):
+ salary = 5000.0
+ schedule_pay = 'semi-monthly'
+ allowance_amount = self.ANNUAL_EXEMPTION_AMOUNT / self.PAY_PERIOD_DIVISOR[schedule_pay]
+ exemption = 1
+
+ wh = -((salary - (allowance_amount * exemption)) * -self.MI_INC_TAX)
+
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('MI'),
+ state_income_tax_additional_withholding=0.0,
+ mi_w4_sit_exemptions=1.0,
+ schedule_pay='semi-monthly')
+
+ self._log('2019 Michigan tax first payslip semi-monthly:')
+ payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
+ payslip.compute_sheet()
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['ER_US_SUTA'], salary * self.MI_UNEMP)
+ self.assertPayrollEqual(cats['EE_US_SIT'], wh)
+
+ process_payslip(payslip)
+
+ # Make a new payslip, this one will have maximums
+ remaining_MI_UNEMP_wages = self.MI_UNEMP_MAX_WAGE - salary if (self.MI_UNEMP_MAX_WAGE - 2 * salary < salary) \
+ else salary
+
+ self._log('2019 Michigan tax second payslip semi-monthly:')
+ payslip = self._createPayslip(employee, '2019-02-01', '2019-02-28')
+ payslip.compute_sheet()
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['ER_US_SUTA'], remaining_MI_UNEMP_wages * self.MI_UNEMP)
+
+ def test_2019_taxes_monthly(self):
+ salary = 5000.0
+ schedule_pay = 'monthly'
+ allowance_amount = self.ANNUAL_EXEMPTION_AMOUNT / self.PAY_PERIOD_DIVISOR[schedule_pay]
+ exemption = 1
+
+ wh = -((salary - (allowance_amount * exemption)) * -self.MI_INC_TAX)
+
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('MI'),
+ state_income_tax_additional_withholding=0.0,
+ mi_w4_sit_exemptions=1.0,
+ schedule_pay='monthly')
+
+ self._log('2019 Michigan tax first payslip monthly:')
+ payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
+ payslip.compute_sheet()
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['ER_US_SUTA'], salary * self.MI_UNEMP)
+ self.assertPayrollEqual(cats['EE_US_SIT'], wh)
+
+ process_payslip(payslip)
+
+ # Make a new payslip, this one will have maximums
+ remaining_MI_UNEMP_wages = self.MI_UNEMP_MAX_WAGE - salary if (
+ self.MI_UNEMP_MAX_WAGE - (2 * salary) < salary) \
+ else salary
+
+ self._log('2019 Michigan tax second payslip monthly:')
+ payslip = self._createPayslip(employee, '2019-02-01', '2019-02-28')
+ payslip.compute_sheet()
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['ER_US_SUTA'], remaining_MI_UNEMP_wages * self.MI_UNEMP)
+
+ def test_additional_withholding(self):
+ salary = 5000.0
+ schedule_pay = 'weekly'
+ allowance_amount = 0.0
+ allowance_amount = self.ANNUAL_EXEMPTION_AMOUNT / self.PAY_PERIOD_DIVISOR[schedule_pay]
+ additional_wh = 40.0
+ exemption = 1
+
+ wh = -(((salary - (allowance_amount * exemption)) * -self.MI_INC_TAX) + additional_wh)
+
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('MI'),
+ state_income_tax_additional_withholding=40.0,
+ mi_w4_sit_exemptions=1.0,
+ schedule_pay='weekly')
+
+ self._log('2019 Michigan tax first payslip with additional withholding:')
+ payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
+ payslip.compute_sheet()
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['ER_US_SUTA'], salary * self.MI_UNEMP)
+ self.assertPayrollAlmostEqual(cats['EE_US_SIT'], wh)
+
+ process_payslip(payslip)
diff --git a/l10n_us_hr_payroll/tests/test_us_mi_michigan_payslip_2020.py b/l10n_us_hr_payroll/tests/test_us_mi_michigan_payslip_2020.py
new file mode 100755
index 00000000..6de7b664
--- /dev/null
+++ b/l10n_us_hr_payroll/tests/test_us_mi_michigan_payslip_2020.py
@@ -0,0 +1,35 @@
+# 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 TestUsMIPayslip(TestUsPayslip):
+ # Taxes and Rates
+ MI_UNEMP_MAX_WAGE = 9000.0
+ MI_UNEMP = 2.7
+
+ def _test_sit(self, wage, exemptions, additional_withholding, schedule_pay, date_start, expected_withholding):
+
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=wage,
+ state_id=self.get_us_state('MI'),
+ mi_w4_sit_exemptions=exemptions,
+ state_income_tax_additional_withholding=additional_withholding,
+ 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('MI', self.MI_UNEMP, date(2020, 1, 1), wage_base=self.MI_UNEMP_MAX_WAGE)
+ self._test_sit(750.0, 1, 100.0, 'weekly', date(2020, 1, 1), 127.99)
+ self._test_sit(1750.0, 1, 0.0, 'bi-weekly', date(2020, 1, 1), 66.61)
+ self._test_sit(5000.0, 1, 5.0, 'semi-monthly', date(2020, 1, 1), 209.09)
+ self._test_sit(8000.0, 1, 5.0, 'monthly', date(2020, 1, 1), 328.18)
+ self._test_sit(5000.0, 2, 0.0, 'monthly', date(2020, 1, 1), 178.86)
+
diff --git a/l10n_us_hr_payroll/tests/test_us_mn_minnesota_payslip_2019.py b/l10n_us_hr_payroll/tests/test_us_mn_minnesota_payslip_2019.py
new file mode 100755
index 00000000..2a64b57d
--- /dev/null
+++ b/l10n_us_hr_payroll/tests/test_us_mn_minnesota_payslip_2019.py
@@ -0,0 +1,159 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from .common import TestUsPayslip, process_payslip
+
+
+class TestUsMNPayslip(TestUsPayslip):
+ # TAXES AND RATES
+ MN_UNEMP_MAX_WAGE = 34000.0
+ MN_UNEMP = -1.11 / 100.0
+
+ def test_taxes_weekly(self):
+ salary = 30000.0
+ # Hand Calculated Amount to Test
+ # Step 1 -> 30000.00 for wages per period Step 2 -> 52.0 for weekly -> 30000 * 52 -> 1560000
+ # Step 3 -> allowances * 4250.0 -> 4250.00 in this case.
+ # Step 4 -> Step 2 - Step 3 -> 1560000 - 4250.00 -> 1555750
+ # Step 5 -> using chart -> we have last row -> ((1555750 - 166290) * (9.85 / 100)) + 11717.65 -> 148579.46
+ # Step 6 -> Convert back to pay period amount and round - > 2857.297 - > 2857.0
+ # wh = 2857.0
+ wh = -2857.0
+
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('MN'),
+ mn_w4mn_sit_filing_status='single',
+ state_income_tax_additional_withholding=0.0,
+ mn_w4mn_sit_allowances=1.0,
+ schedule_pay='weekly')
+
+ self._log('2019 Minnesota tax first payslip weekly:')
+ payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
+ payslip.compute_sheet()
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['ER_US_SUTA'], salary * self.MN_UNEMP)
+ self.assertPayrollAlmostEqual(cats['EE_US_SIT'], wh) # Test numbers are off by 1 penny
+
+ process_payslip(payslip)
+
+ # Make a new payslip, this one will have maximums
+ remaining_MN_UNEMP_wages = self.MN_UNEMP_MAX_WAGE - salary if (self.MN_UNEMP_MAX_WAGE - 2*salary < salary) \
+ else salary
+
+ self._log('2019 Minnesota tax second payslip weekly:')
+ payslip = self._createPayslip(employee, '2019-02-01', '2019-02-28')
+ payslip.compute_sheet()
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['ER_US_SUTA'], remaining_MN_UNEMP_wages * self.MN_UNEMP)
+
+ def test_taxes_married(self):
+ salary = 5000.00
+
+ # Hand Calculated Amount to Test
+ # Step 1 -> 5000.0 for wages per period Step 2 -> 52.0 for weekly -> 5000 * 52 -> 260,000
+ # Step 3 -> allowances * 4250.0 -> 4250.00 in this case.
+ # Step 4 -> Step 2 - Step 3 -> 260,000 - 4250.00 -> 255750.0
+ # For step five we used the married section
+ # Step 5 -> using chart -> we have 2nd last row -> ((255750 - 163070) * (7.85 / 100)) + 10199.33 ->
+ # Step 6 -> Convert back to pay period amount and round
+ # wh = 336.0
+ wh = -336.0
+
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('MN'),
+ mn_w4mn_sit_filing_status='married',
+ state_income_tax_additional_withholding=0.0,
+ mn_w4mn_sit_allowances=1.0,
+ schedule_pay='weekly')
+
+ self._log('2019 Minnesota tax first payslip married:')
+ payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
+ payslip.compute_sheet()
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['ER_US_SUTA'], salary * self.MN_UNEMP)
+ self.assertPayrollAlmostEqual(cats['EE_US_SIT'], wh)
+
+ def test_taxes_semimonthly(self):
+ salary = 6500.00
+ # Hand Calculated Amount to Test
+ # Step 1 -> 6500.00 for wages per period Step 2 -> 24 for semi-monthly -> 6500.00 * 24 -> 156000.00
+ # Step 3 -> allowances * 4250.0 -> 4250.00 in this case.
+ # Step 4 -> Step 2 - Step 3 -> 156000.00 - 4250.00 -> 151750.0
+ # Step 5 -> using chart -> we have 2nd last row -> ((151750.0- 89510) * (7.85 / 100)) + 5690.42 -> 10576.26
+ # Step 6 -> Convert back to pay period amount and round
+ # wh = -441
+ wh = -441.00
+
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('MN'),
+ mn_w4mn_sit_filing_status='single',
+ state_income_tax_additional_withholding=0.0,
+ mn_w4mn_sit_allowances=1.0,
+ schedule_pay='semi-monthly')
+
+
+ self._log('2019 Minnesota tax first payslip semimonthly:')
+ payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
+ payslip.compute_sheet()
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['ER_US_SUTA'], salary * self.MN_UNEMP)
+ self.assertPayrollAlmostEqual(cats['EE_US_SIT'], wh)
+
+ def test_tax_exempt(self):
+ salary = 5500.00
+ wh = 0
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('MN'),
+ mn_w4mn_sit_filing_status='',
+ state_income_tax_additional_withholding=0.0,
+ mn_w4mn_sit_allowances=2.0,
+ schedule_pay='weekly')
+
+ self._log('2019 Minnesota tax first payslip exempt:')
+ payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
+ payslip.compute_sheet()
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['ER_US_SUTA'], salary * self.MN_UNEMP)
+ self.assertPayrollAlmostEqual(cats['EE_US_SIT'], wh)
+
+ def test_additional_withholding(self):
+ salary = 5500.0
+ # Hand Calculated Amount to Test
+ # Step 1 -> 5500 for wages per period Step 2 -> 52 for weekly -> 5500 * 52 -> 286000.00
+ # Step 3 -> allowances * 4250.0 -> 8500 in this case.
+ # Step 4 -> Step 2 - Step 3 -> 286000.00 - 8500 -> 277500
+ # Step 5 -> using chart -> we have last row -> ((277500- 166290) * (9.85 / 100)) + 11717.65 -> 22671.835
+ # Step 6 -> Convert back to pay period amount and round
+ # wh = -436.0
+ # Add additional_withholding
+ # wh = -436.0 + 40.0
+ wh = -476.0
+
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('MN'),
+ mn_w4mn_sit_filing_status='single',
+ state_income_tax_additional_withholding=40.0,
+ mn_w4mn_sit_allowances=2.0,
+ schedule_pay='weekly')
+
+ self._log('2019 Minnesota tax first payslip additional withholding:')
+ payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
+ payslip.compute_sheet()
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['ER_US_SUTA'], salary * self.MN_UNEMP)
+ self.assertPayrollAlmostEqual(cats['EE_US_SIT'], wh)
diff --git a/l10n_us_hr_payroll/tests/test_us_mn_minnesota_payslip_2020.py b/l10n_us_hr_payroll/tests/test_us_mn_minnesota_payslip_2020.py
new file mode 100755
index 00000000..c91fa2a8
--- /dev/null
+++ b/l10n_us_hr_payroll/tests/test_us_mn_minnesota_payslip_2020.py
@@ -0,0 +1,36 @@
+# 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 TestUsMNPayslip(TestUsPayslip):
+ # TAXES AND RATES
+ MN_UNEMP_MAX_WAGE = 35000.0
+ MN_UNEMP = 1.11
+
+ def _test_sit(self, wage, filing_status, allowances, additional_withholding, schedule_pay, date_start, expected_withholding):
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=wage,
+ state_id=self.get_us_state('MN'),
+ mn_w4mn_sit_filing_status=filing_status,
+ state_income_tax_additional_withholding=additional_withholding,
+ mn_w4mn_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('MN', self.MN_UNEMP, date(2020, 1, 1), wage_base=self.MN_UNEMP_MAX_WAGE)
+ self._test_sit(5000.0, 'single', 1.0, 0.0, 'weekly', date(2020, 1, 1), 389.0)
+ self._test_sit(30000.0, 'single', 1.0, 0.0, 'weekly', date(2020, 1, 1), 2850.99)
+ self._test_sit(5000.0, 'married', 1.0, 0.0, 'weekly', date(2020, 1, 1), 325.0)
+ self._test_sit(6500.0, 'single', 1.0, 0.0, 'semi-monthly', date(2020, 1, 1), 429.0)
+ self._test_sit(5500.0, '', 2.0, 0.0, 'weekly', date(2020, 1, 1), 0.0)
+ self._test_sit(5500.0, 'single', 2.0, 40.0, 'weekly', date(2020, 1, 1), 470.0)
diff --git a/l10n_us_hr_payroll/tests/test_us_mo_missouri_payslip_2019.py b/l10n_us_hr_payroll/tests/test_us_mo_missouri_payslip_2019.py
new file mode 100755
index 00000000..27a0ad93
--- /dev/null
+++ b/l10n_us_hr_payroll/tests/test_us_mo_missouri_payslip_2019.py
@@ -0,0 +1,188 @@
+
+from datetime import date
+from .common import TestUsPayslip
+
+
+class TestUsMoPayslip(TestUsPayslip):
+ # Calculations from http://dor.mo.gov/forms/4282_2019.pdf
+ SALARY = 12000.0
+ MO_UNEMP = -2.376 / 100.0
+
+ TAX = [
+ (1053.0, 1.5),
+ (1053.0, 2.0),
+ (1053.0, 2.5),
+ (1053.0, 3.0),
+ (1053.0, 3.5),
+ (1053.0, 4.0),
+ (1053.0, 4.5),
+ (1053.0, 5.0),
+ (999999999.0, 5.4),
+ ]
+
+ def test_2019_taxes_single(self):
+ # Payroll Period Monthly
+ salary = self.SALARY
+ pp = 12.0
+ gross_salary = salary * pp
+ spouse_employed = False
+
+ # Single
+ standard_deduction = 12400.0
+
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('MO'),
+ mo_mow4_sit_filing_status='single',
+ state_income_tax_additional_withholding=0.0,
+ schedule_pay='monthly')
+
+ self._log('2019 Missouri tax single 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 * self.MO_UNEMP)
+
+ mo_taxable_income = gross_salary - standard_deduction
+ self._log('%s = %s - %s -' % (mo_taxable_income, gross_salary, standard_deduction))
+
+ remaining_taxable_income = mo_taxable_income
+ tax = 0.0
+ for amt, rate in self.TAX:
+ amt = float(amt)
+ rate = rate / 100.0
+ self._log(str(amt) + ' : ' + str(rate) + ' : ' + str(remaining_taxable_income))
+ if (remaining_taxable_income - amt) > 0.0 or (remaining_taxable_income - amt) == 0.0:
+ tax += rate * amt
+ else:
+ tax += rate * remaining_taxable_income
+ break
+ remaining_taxable_income = remaining_taxable_income - amt
+
+ tax = -tax
+ self._log('Computed annual tax: ' + str(tax))
+
+ tax = tax / pp
+ tax = round(tax)
+ self._log('Computed period tax: ' + str(tax))
+ self.assertPayrollEqual(cats['EE_US_SIT'], tax)
+
+ def test_2019_spouse_not_employed(self):
+ # Payroll Period Semi-monthly
+ salary = self.SALARY
+ pp = 24.0
+ gross_salary = salary * pp
+
+ # Married
+ standard_deduction = 24800.0
+
+ employee = self._createEmployee()
+
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('MO'),
+ mo_mow4_sit_filing_status='married',
+ state_income_tax_additional_withholding=0.0,
+ schedule_pay='semi-monthly')
+
+ self._log('2019 Missouri tax first payslip:')
+ payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+
+ mo_taxable_income = gross_salary - standard_deduction
+ self._log(mo_taxable_income)
+
+ remaining_taxable_income = mo_taxable_income
+ tax = 0.0
+ for amt, rate in self.TAX:
+ amt = float(amt)
+ rate = rate / 100.0
+ self._log(str(amt) + ' : ' + str(rate) + ' : ' + str(remaining_taxable_income))
+ if (remaining_taxable_income - amt) > 0.0 or (remaining_taxable_income - amt) == 0.0:
+ tax += rate * amt
+ else:
+ tax += rate * remaining_taxable_income
+ break
+ remaining_taxable_income = remaining_taxable_income - amt
+
+ tax = -tax
+ self._log('Computed annual tax: ' + str(tax))
+
+ tax = tax / pp
+ tax = round(tax)
+ self._log('Computed period tax: ' + str(tax))
+ self.assertPayrollEqual(cats['EE_US_SIT'], tax)
+
+ def test_2019_head_of_household(self):
+ # Payroll Period Weekly
+ salary = self.SALARY
+
+ # Payroll Period Weekly
+ salary = self.SALARY
+ pp = 52.0
+ gross_salary = salary * pp
+
+ # Single HoH
+ standard_deduction = 18650.0
+
+ employee = self._createEmployee()
+
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('MO'),
+ mo_mow4_sit_filing_status='head_of_household',
+ state_income_tax_additional_withholding=0.0,
+ schedule_pay='weekly')
+
+ self._log('2019 Missouri tax first payslip:')
+ payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+
+ mo_taxable_income = gross_salary - standard_deduction
+ self._log(mo_taxable_income)
+
+ remaining_taxable_income = mo_taxable_income
+ tax = 0.0
+ for amt, rate in self.TAX:
+ amt = float(amt)
+ rate = rate / 100.0
+ self._log(str(amt) + ' : ' + str(rate) + ' : ' + str(remaining_taxable_income))
+ if (remaining_taxable_income - amt) > 0.0 or (remaining_taxable_income - amt) == 0.0:
+ tax += rate * amt
+ else:
+ tax += rate * remaining_taxable_income
+ break
+ remaining_taxable_income = remaining_taxable_income - amt
+ tax = -tax
+ self._log('Computed annual tax: ' + str(tax))
+
+ tax = tax / pp
+ tax = round(tax)
+ self._log('Computed period tax: ' + str(tax))
+ self.assertPayrollEqual(cats['EE_US_SIT'], tax)
+
+ def test_2019_underflow(self):
+ # Payroll Period Weekly
+ salary = 200.0
+
+ employee = self._createEmployee()
+
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('MO'))
+
+ payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
+ payslip.compute_sheet()
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['EE_US_SIT'], 0.0)
diff --git a/l10n_us_hr_payroll/tests/test_us_mo_missouri_payslip_2020.py b/l10n_us_hr_payroll/tests/test_us_mo_missouri_payslip_2020.py
new file mode 100755
index 00000000..ff6a0ca1
--- /dev/null
+++ b/l10n_us_hr_payroll/tests/test_us_mo_missouri_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 TestUsMoPayslip(TestUsPayslip):
+ # Calculations from http://dor.mo.gov/forms/4282_2020.pdf
+ MO_UNEMP_MAX_WAGE = 11500.0
+ MO_UNEMP = 2.376
+
+ def _test_sit(self, wage, filing_status, additional_withholding, schedule_pay, date_start, expected_withholding):
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=wage,
+ state_id=self.get_us_state('MO'),
+ mo_mow4_sit_filing_status=filing_status,
+ state_income_tax_additional_withholding=additional_withholding,
+ 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.assertPayrollAlmostEqual(cats.get('EE_US_SIT', 0.0), -expected_withholding)
+
+ def test_2020_taxes_example(self):
+ self._test_er_suta('MO', self.MO_UNEMP, date(2020, 1, 1), wage_base=self.MO_UNEMP_MAX_WAGE)
+ self._test_sit(750.0, 'single', 0.0, 'weekly', date(2020, 1, 1), 24.00)
+ self._test_sit(2500.0, 'single', 5.0, 'bi-weekly', date(2020, 1, 1), 107.00)
+ self._test_sit(7000.0, 'married', 0.0, 'monthly', date(2020, 1, 1), 251.00)
+ self._test_sit(5000.0, 'married', 10.0, 'semi-monthly', date(2020, 1, 1), 217.00)
+ self._test_sit(6000.0, '', 0.0, 'monthly', date(2020, 1, 1), 0.00)
+
diff --git a/l10n_us_hr_payroll/tests/test_us_ms_mississippi_payslip_2019.py b/l10n_us_hr_payroll/tests/test_us_ms_mississippi_payslip_2019.py
new file mode 100755
index 00000000..e7ce35d0
--- /dev/null
+++ b/l10n_us_hr_payroll/tests/test_us_ms_mississippi_payslip_2019.py
@@ -0,0 +1,94 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from .common import TestUsPayslip
+
+
+class TestUsMsPayslip(TestUsPayslip):
+ # Calculations from https://www.dor.ms.gov/Documents/Computer%20Payroll%20Accounting%201-2-19.pdf
+ MS_UNEMP = -1.2 / 100.0
+
+ def test_2019_taxes_one(self):
+ salary = 1250.0
+ ms_89_350_exemption = 11000.0
+
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('MS'),
+ ms_89_350_sit_filing_status='head_of_household',
+ ms_89_350_sit_exemption_value=ms_89_350_exemption,
+ state_income_tax_additional_withholding=0.0,
+ schedule_pay='semi-monthly')
+
+ self._log('2019 Mississippi tax single 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 * self.MS_UNEMP)
+
+ STDED = 3400.0 # Head of Household
+ AGP = salary * 24 # Semi-Monthly
+ TI = AGP - (ms_89_350_exemption + STDED)
+ self.assertPayrollEqual(TI, 15600.0)
+ TAX = ((TI - 10000) * 0.05) + 290 # Over 10,000
+ self.assertPayrollEqual(TAX, 570.0)
+
+ ms_withhold = round(TAX / 24) # Semi-Monthly
+ self.assertPayrollEqual(cats['EE_US_SIT'], -ms_withhold)
+
+ def test_2019_taxes_one_exempt(self):
+ salary = 1250.0
+ ms_89_350_exemption = 11000.0
+
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('MS'),
+ ms_89_350_sit_filing_status='',
+ ms_89_350_sit_exemption_value=ms_89_350_exemption,
+ state_income_tax_additional_withholding=0.0,
+ schedule_pay='semi-monthly')
+
+ self._log('2019 Mississippi tax single first payslip:')
+ payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+ self.assertPayrollEqual(cats.get('EE_US_SIT', 0.0), 0.0)
+
+ def test_2019_taxes_additional(self):
+ salary = 1250.0
+ ms_89_350_exemption = 11000.0
+ additional = 40.0
+
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('MS'),
+ ms_89_350_sit_filing_status='head_of_household',
+ ms_89_350_sit_exemption_value=ms_89_350_exemption,
+ state_income_tax_additional_withholding=additional,
+ schedule_pay='semi-monthly')
+
+ self._log('2019 Mississippi tax single 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 * self.MS_UNEMP)
+
+ STDED = 3400.0 # Head of Household
+ AGP = salary * 24 # Semi-Monthly
+ TI = AGP - (ms_89_350_exemption + STDED)
+ self.assertPayrollEqual(TI, 15600.0)
+ TAX = ((TI - 10000) * 0.05) + 290 # Over 10,000
+ self.assertPayrollEqual(TAX, 570.0)
+
+ ms_withhold = round(TAX / 24) # Semi-Monthly
+ self.assertPayrollEqual(cats['EE_US_SIT'], -ms_withhold + -additional)
diff --git a/l10n_us_hr_payroll/tests/test_us_ms_mississippi_payslip_2020.py b/l10n_us_hr_payroll/tests/test_us_ms_mississippi_payslip_2020.py
new file mode 100755
index 00000000..ea0081ca
--- /dev/null
+++ b/l10n_us_hr_payroll/tests/test_us_ms_mississippi_payslip_2020.py
@@ -0,0 +1,35 @@
+# 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 TestUsMsPayslip(TestUsPayslip):
+ # Calculations from https://www.dor.ms.gov/Documents/Computer%20Payroll%20Flowchart.pdf
+ MS_UNEMP = 1.2
+ MS_UNEMP_MAX_WAGE = 14000.0
+
+ def _test_sit(self, wage, filing_status, additional_withholding, exemption, schedule_pay, date_start, expected_withholding):
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=wage,
+ state_id=self.get_us_state('MS'),
+ ms_89_350_sit_filing_status=filing_status,
+ state_income_tax_additional_withholding=additional_withholding,
+ ms_89_350_sit_exemption_value=exemption,
+ 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.assertPayrollAlmostEqual(cats.get('EE_US_SIT', 0.0), -expected_withholding)
+
+ def test_2020_taxes_example(self):
+ self._test_er_suta('MS', self.MS_UNEMP, date(2020, 1, 1), wage_base=self.MS_UNEMP_MAX_WAGE)
+ self._test_sit(1250.0, 'head_of_household', 0.0, 11000, 'semi-monthly', date(2020, 1, 1), 22.00)
+ self._test_sit(500.0, '', 5.0, 0, 'bi-weekly', date(2020, 1, 1), 0.00)
+ self._test_sit(12000.0, 'single', 0.0, 11000, 'monthly', date(2020, 1, 1), 525.00)
+ self._test_sit(2500.0, 'married', 5.0, 500, 'bi-weekly', date(2020, 1, 1), 111.00)
+
+
diff --git a/l10n_us_hr_payroll/tests/test_us_mt_montana_payslip_2019.py b/l10n_us_hr_payroll/tests/test_us_mt_montana_payslip_2019.py
new file mode 100755
index 00000000..ff6e2daf
--- /dev/null
+++ b/l10n_us_hr_payroll/tests/test_us_mt_montana_payslip_2019.py
@@ -0,0 +1,139 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from .common import TestUsPayslip, process_payslip
+
+
+class TestUsMtPayslip(TestUsPayslip):
+ # Calculations from https://app.mt.gov/myrevenue/Endpoint/DownloadPdf?yearId=705
+ MT_UNEMP = -1.18 / 100.0
+ MT_UNEMP_AFT = -0.13 / 100.0
+
+ def test_2019_taxes_one(self):
+ # Payroll Period Semi-Monthly example
+ salary = 550
+ mt_mw4_exemptions = 5
+
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('MT'),
+ mt_mw4_sit_exemptions=mt_mw4_exemptions,
+ schedule_pay='semi-monthly')
+
+ self._log('2019 Montana tax single 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 * (self.MT_UNEMP + self.MT_UNEMP_AFT)) # New non-combined...
+
+ mt_taxable_income = salary - (79.0 * mt_mw4_exemptions)
+ mt_withhold = round(0 + (0.018 * (mt_taxable_income - 0)))
+ self.assertPayrollEqual(mt_taxable_income, 155.0)
+ self.assertPayrollEqual(mt_withhold, 3.0)
+ self.assertPayrollEqual(cats['EE_US_SIT'], -mt_withhold)
+
+ def test_2019_taxes_two(self):
+ # Payroll Period Bi-Weekly example
+ salary = 2950
+ mt_mw4_exemptions = 2
+
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('MT'),
+ mt_mw4_sit_exemptions=mt_mw4_exemptions,
+ schedule_pay='bi-weekly')
+
+ self._log('2019 Montana tax single 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'], round(salary * (self.MT_UNEMP + self.MT_UNEMP_AFT), 2))
+
+ # Note!!
+ # The example calculation uses A = 16 but the actual table describes this as A = 18
+ mt_taxable_income = salary - (73.0 * mt_mw4_exemptions)
+ mt_withhold = round(18 + (0.06 * (mt_taxable_income - 577)))
+ self.assertPayrollEqual(mt_taxable_income, 2804.0)
+ self.assertPayrollEqual(mt_withhold, 152.0)
+ self.assertPayrollEqual(cats['EE_US_SIT'], -mt_withhold)
+
+ def test_2019_taxes_three(self):
+ # Payroll Period Weekly example
+ salary = 135
+ mt_mw4_exemptions = 1
+
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('MT'),
+ mt_mw4_sit_exemptions=mt_mw4_exemptions,
+ schedule_pay='weekly')
+
+ self._log('2019 Montana tax single 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'], round(salary * (self.MT_UNEMP + self.MT_UNEMP_AFT), 2))
+
+ mt_taxable_income = salary - (37.0 * mt_mw4_exemptions)
+ mt_withhold = round(0 + (0.018 * (mt_taxable_income - 0)))
+ self.assertPayrollEqual(mt_taxable_income, 98.0)
+ self.assertPayrollEqual(mt_withhold, 2.0)
+ self.assertPayrollEqual(cats['EE_US_SIT'], -mt_withhold)
+
+ def test_2019_taxes_three_exempt(self):
+ # Payroll Period Weekly example
+ salary = 135
+ mt_mw4_exemptions = 1
+
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('MT'),
+ mt_mw4_sit_exemptions=mt_mw4_exemptions,
+ mt_mw4_sit_exempt='reserve',
+ schedule_pay='weekly')
+
+ self._log('2019 Montana tax single first payslip:')
+ payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+ self.assertPayrollEqual(cats.get('EE_US_SIT', 0.0), 0.0)
+
+ def test_2019_taxes_three_additional(self):
+ # Payroll Period Weekly example
+ salary = 135
+ mt_mw4_exemptions = 1
+ mt_mw4_additional_withholding = 20.0
+
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('MT'),
+ mt_mw4_sit_exemptions=mt_mw4_exemptions,
+ state_income_tax_additional_withholding=mt_mw4_additional_withholding,
+ schedule_pay='weekly')
+
+ self._log('2019 Montana tax single first payslip:')
+ payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
+
+ payslip.compute_sheet()
+ cats = self._getCategories(payslip)
+
+ mt_taxable_income = salary - (37.0 * mt_mw4_exemptions)
+ mt_withhold = round(0 + (0.018 * (mt_taxable_income - 0)))
+ self.assertPayrollEqual(mt_taxable_income, 98.0)
+ self.assertPayrollEqual(mt_withhold, 2.0)
+ self.assertPayrollEqual(cats['EE_US_SIT'], -mt_withhold + -mt_mw4_additional_withholding)
diff --git a/l10n_us_hr_payroll/tests/test_us_mt_montana_payslip_2020.py b/l10n_us_hr_payroll/tests/test_us_mt_montana_payslip_2020.py
new file mode 100755
index 00000000..13a7e69f
--- /dev/null
+++ b/l10n_us_hr_payroll/tests/test_us_mt_montana_payslip_2020.py
@@ -0,0 +1,37 @@
+# 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 TestUsMtPayslip(TestUsPayslip):
+ ###
+ # 2020 Taxes and Rates
+ ###
+ MT_UNEMP_WAGE_MAX = 34100.0
+ MT_UNEMP = 1.18
+ MT_UNEMP_AFT = 0.13
+
+ # Calculations from https://app.mt.gov/myrevenue/Endpoint/DownloadPdf?yearId=705
+ def _test_sit(self, wage, additional_withholding, exemptions, schedule_pay, date_start, expected_withholding):
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=wage,
+ state_id=self.get_us_state('MT'),
+ state_income_tax_additional_withholding=additional_withholding,
+ mt_mw4_sit_exemptions=exemptions,
+ 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_one(self):
+ combined_rate = self.MT_UNEMP + self.MT_UNEMP_AFT # Combined for test as they both go to the same category and have the same cap
+ self._test_er_suta('MT', combined_rate, date(2020, 1, 1), wage_base=self.MT_UNEMP_WAGE_MAX)
+ self._test_sit(550.0, 0.0, 5.0, 'semi-monthly', date(2020, 1, 1), 3.0)
+ self._test_sit(2950.0, 10.0, 2.0, 'bi-weekly', date(2020, 1, 1), 162.0)
+ self._test_sit(5000.0, 0.0, 1.0, 'monthly', date(2020, 1, 1), 256.0)
+ self._test_sit(750.0, 0.0, 1.0, 'weekly', date(2020, 1, 1), 34.0)
diff --git a/l10n_us_hr_payroll/tests/test_us_nc_northcarolina_payslip_2019.py b/l10n_us_hr_payroll/tests/test_us_nc_northcarolina_payslip_2019.py
new file mode 100755
index 00000000..14c1c5b2
--- /dev/null
+++ b/l10n_us_hr_payroll/tests/test_us_nc_northcarolina_payslip_2019.py
@@ -0,0 +1,270 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from .common import TestUsPayslip, process_payslip
+
+
+class TestUsNCPayslip(TestUsPayslip):
+ ###
+ # Taxes and Rates
+ ###
+ NC_UNEMP_MAX_WAGE = 24300.0
+ NC_UNEMP = -1.0 / 100.0
+ NC_INC_TAX = -0.0535
+
+
+ def test_2019_taxes_weekly(self):
+ salary = 20000.0
+ # allowance_multiplier and Portion of Standard Deduction for weekly
+ allowance_multiplier = 48.08
+ PST = 192.31
+ exemption = 1
+ # Algorithm derived from percentage method in https://files.nc.gov/ncdor/documents/files/nc-30_book_web.pdf
+ wh = -round((salary - (PST + (allowance_multiplier * exemption))) * -self.NC_INC_TAX)
+
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('NC'),
+ nc_nc4_sit_filing_status='married',
+ state_income_tax_additional_withholding=0.0,
+ nc_nc4_sit_allowances=1.0,
+ schedule_pay='weekly')
+
+ self._log('2019 North Carolina tax first payslip weekly:')
+ payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['EE_US_SIT'], wh)
+
+ process_payslip(payslip)
+
+ # Make a new payslip, this one will have maximums
+
+ remaining_NC_UNEMP_wages = self.NC_UNEMP_MAX_WAGE - salary if (self.NC_UNEMP_MAX_WAGE - 2*salary < salary) \
+ else salary
+ self._log('2019 North Carolina tax second payslip weekly:')
+ payslip = self._createPayslip(employee, '2019-02-01', '2019-02-28')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['ER_US_SUTA'], remaining_NC_UNEMP_wages * self.NC_UNEMP)
+
+ def test_2019_taxes_with_external_weekly(self):
+ salary = 5000.0
+ schedule_pay = 'weekly'
+
+ employee = self._createEmployee()
+
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('NC'),
+ nc_nc4_sit_filing_status='married',
+ state_income_tax_additional_withholding=0.0,
+ nc_nc4_sit_allowances=1.0,
+ schedule_pay='weekly')
+
+ self._log('2019 NorthCarolina_external tax first payslip weekly:')
+ payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['ER_US_SUTA'], salary * self.NC_UNEMP)
+
+ def test_2019_taxes_biweekly(self):
+ salary = 5000.0
+ schedule_pay = 'bi-weekly'
+ # allowance_multiplier and Portion of Standard Deduction for weekly
+ allowance_multiplier = 96.15
+ PST = 384.62
+
+ allowances = 2
+ # Algorithm derived from percentage method in https://files.nc.gov/ncdor/documents/files/nc-30_book_web.pdf
+
+ wh = -round((salary - (PST + (allowance_multiplier * allowances))) * -self.NC_INC_TAX)
+
+ employee = self._createEmployee()
+
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('NC'),
+ nc_nc4_sit_filing_status='married',
+ state_income_tax_additional_withholding=0.0,
+ nc_nc4_sit_allowances=2.0,
+ schedule_pay='bi-weekly')
+
+ self._log('2019 North Carolina tax first payslip bi-weekly:')
+ payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['ER_US_SUTA'], salary * self.NC_UNEMP)
+ self.assertPayrollEqual(cats['EE_US_SIT'], wh)
+
+ process_payslip(payslip)
+
+ # Make a new payslip, this one will have maximums
+
+ remaining_NC_UNEMP_wages = self.NC_UNEMP_MAX_WAGE - salary if (self.NC_UNEMP_MAX_WAGE - 2*salary < salary) \
+ else salary
+
+ self._log('2019 North Carolina tax second payslip bi-weekly:')
+ payslip = self._createPayslip(employee, '2019-02-01', '2019-02-28')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['ER_US_SUTA'], remaining_NC_UNEMP_wages * self.NC_UNEMP)
+
+ def test_2019_taxes_semimonthly(self):
+ salary = 4000.0
+ # allowance_multiplier and Portion of Standard Deduction for weekly
+ allowance_multiplier = 104.17
+ PST = 625.00
+
+ allowances = 1
+ # Algorithm derived from percentage method in https://files.nc.gov/ncdor/documents/files/nc-30_book_web.pdf
+
+ wh = -round((salary - (PST + (allowance_multiplier * allowances))) * -self.NC_INC_TAX)
+
+ employee = self._createEmployee()
+
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('NC'),
+ nc_nc4_sit_filing_status='head_household',
+ state_income_tax_additional_withholding=0.0,
+ nc_nc4_sit_allowances=1.0,
+ schedule_pay='semi-monthly')
+
+ self._log('2019 North 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'], salary * self.NC_UNEMP)
+ self.assertPayrollEqual(cats['EE_US_SIT'], wh)
+
+ process_payslip(payslip)
+
+ # Make a new payslip, this one will have maximums
+
+ remaining_NC_UNEMP_wages = self.NC_UNEMP_MAX_WAGE - salary if (self.NC_UNEMP_MAX_WAGE - 2 * salary < salary) \
+ else salary
+
+ self._log('2019 North Carolina 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_NC_UNEMP_wages * self.NC_UNEMP)
+
+ def test_2019_taxes_monthly(self):
+ salary = 4000.0
+ schedule_pay = 'monthly'
+ # allowance_multiplier and Portion of Standard Deduction for weekly
+ allowance_multiplier = 208.33
+ PST = 833.33
+
+ allowances = 1
+ # Algorithm derived from percentage method in https://files.nc.gov/ncdor/documents/files/nc-30_book_web.pdf
+
+ wh = -round((salary - (PST + (allowance_multiplier * allowances))) * -self.NC_INC_TAX)
+
+ employee = self._createEmployee()
+
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('NC'),
+ nc_nc4_sit_filing_status='single',
+ state_income_tax_additional_withholding=0.0,
+ nc_nc4_sit_allowances=1.0,
+ schedule_pay='monthly')
+
+ self._log('2019 North 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'], salary * self.NC_UNEMP)
+ self.assertPayrollEqual(cats['EE_US_SIT'], wh)
+
+ process_payslip(payslip)
+
+ # Make a new payslip, this one will have maximums
+
+ remaining_NC_UNEMP_wages = self.NC_UNEMP_MAX_WAGE - salary if (
+ self.NC_UNEMP_MAX_WAGE - 2 * salary < salary) \
+ else salary
+
+ self._log('2019 North Carolina 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_NC_UNEMP_wages * self.NC_UNEMP)
+
+ def test_additional_withholding(self):
+ salary = 4000.0
+ # allowance_multiplier and Portion of Standard Deduction for weekly
+ allowance_multiplier = 48.08
+ PST = 192.31
+ additional_wh = 40.0
+
+ #4000 - (168.27 + (48.08 * 1)
+
+ allowances = 1
+ # Algorithm derived from percentage method in https://files.nc.gov/ncdor/documents/files/nc-30_book_web.pdf
+
+ wh = -round(((salary - (PST + (allowance_multiplier * allowances))) * -self.NC_INC_TAX) + additional_wh)
+
+ employee = self._createEmployee()
+
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('NC'),
+ nc_nc4_sit_filing_status='married',
+ state_income_tax_additional_withholding=40.0,
+ nc_nc4_sit_allowances=1.0,
+ schedule_pay='weekly')
+
+ self._log('2019 North Carolina tax first payslip weekly:')
+ payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+ self.assertPayrollEqual(cats['EE_US_SIT'], wh)
+
+ process_payslip(payslip)
+
+ # Make a new payslip, this one will have maximums
+
+ remaining_NC_UNEMP_wages = self.NC_UNEMP_MAX_WAGE - salary if (self.NC_UNEMP_MAX_WAGE - 2 * salary < salary) \
+ else salary
+
+ self._log('2019 North Carolina tax second payslip weekly:')
+ payslip = self._createPayslip(employee, '2019-02-01', '2019-02-28')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['ER_US_SUTA'], remaining_NC_UNEMP_wages * self.NC_UNEMP)
diff --git a/l10n_us_hr_payroll/tests/test_us_nc_northcarolina_payslip_2020.py b/l10n_us_hr_payroll/tests/test_us_nc_northcarolina_payslip_2020.py
new file mode 100755
index 00000000..8e2d69c1
--- /dev/null
+++ b/l10n_us_hr_payroll/tests/test_us_nc_northcarolina_payslip_2020.py
@@ -0,0 +1,37 @@
+# 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 TestUsNCPayslip(TestUsPayslip):
+ ###
+ # 2020 Taxes and Rates
+ ###
+ NC_UNEMP_MAX_WAGE = 25200.0
+ NC_UNEMP = 1.0
+ NC_INC_TAX = 0.0535
+ # Example based on https://files.nc.gov/ncdor/documents/files/NC-30_book_Web_1-16-19_v4_Final.pdf
+ def _test_sit(self, wage, filing_status, allowances, additional_withholding, schedule_pay, date_start, expected_withholding):
+
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=wage,
+ state_id=self.get_us_state('NC'),
+ nc_nc4_sit_filing_status=filing_status,
+ nc_nc4_sit_allowances=allowances,
+ state_income_tax_additional_withholding=additional_withholding,
+ 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 if filing_status else 0.0)
+
+ def test_2020_taxes_example(self):
+ self._test_er_suta('NC', self.NC_UNEMP, date(2020, 1, 1), wage_base=self.NC_UNEMP_MAX_WAGE)
+ self._test_sit(20000.0, 'single', 1, 100.0, 'weekly', date(2020, 1, 1), 1156.0)
+ self._test_sit(5000.0, 'married', 1, 0.0, 'weekly', date(2020, 1, 1), 254.0)
+ self._test_sit(4000.0, 'head_household', 1, 5.0, 'semi-monthly', date(2020, 1, 1), 177.0)
+ self._test_sit(7000.0, '', 1, 5.0, 'monthly', date(2020, 1, 1), 0.0)
diff --git a/l10n_us_hr_payroll/tests/test_us_nd_north_dakota_payslip_2020.py b/l10n_us_hr_payroll/tests/test_us_nd_north_dakota_payslip_2020.py
new file mode 100644
index 00000000..903cf816
--- /dev/null
+++ b/l10n_us_hr_payroll/tests/test_us_nd_north_dakota_payslip_2020.py
@@ -0,0 +1,37 @@
+# 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 TestUsNDPayslip(TestUsPayslip):
+ ###
+ # 2020 Taxes and Rates
+ ###
+ ND_UNEMP_MAX_WAGE = 37900.0
+ ND_UNEMP = 1.02
+ # Calculation based on this file page.47 https://www.nd.gov/tax/data/upfiles/media/rates-and-instructions.pdf?20200110115917
+
+ def _test_sit(self, wage, filing_status, additional_withholding, allowances, schedule_pay, date_start, expected_withholding):
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=wage,
+ state_id=self.get_us_state('ND'),
+ nd_w4_sit_filing_status=filing_status,
+ state_income_tax_additional_withholding=additional_withholding,
+ nd_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.assertPayrollAlmostEqual(cats.get('EE_US_SIT', 0.0), -expected_withholding)
+
+ def test_2020_taxes_example(self):
+ self._test_er_suta('ND', self.ND_UNEMP, date(2020, 1, 1), wage_base=self.ND_UNEMP_MAX_WAGE)
+ self._test_sit(700.0, 'single', 0.0, 0.0, 'weekly', date(2020, 1, 1), 6.0)
+ self._test_sit(5000.0, 'married', 0.0, 2.0, 'bi-weekly', date(2020, 1, 1), 76.0)
+ self._test_sit(25000.0, 'head_household', 0.0, 0.0, 'monthly', date(2020, 1, 1), 534.0)
+ self._test_sit(25000.0, 'head_household', 10.0, 2.0, 'monthly', date(2020, 1, 1), 525.0)
+ self._test_sit(3000.0, '', 10.0, 2.0, 'monthly', date(2020, 1, 1), 0.0)
diff --git a/l10n_us_hr_payroll/tests/test_us_ne_nebraska_payslip_2020.py b/l10n_us_hr_payroll/tests/test_us_ne_nebraska_payslip_2020.py
new file mode 100644
index 00000000..785e4417
--- /dev/null
+++ b/l10n_us_hr_payroll/tests/test_us_ne_nebraska_payslip_2020.py
@@ -0,0 +1,38 @@
+# 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 TestUsNEPayslip(TestUsPayslip):
+ ###
+ # 2020 Taxes and Rates
+ ###
+ NE_UNEMP_MAX_WAGE = 9000.0
+ NE_UNEMP = 1.25
+
+ def _test_sit(self, wage, filing_status, exempt, additional_withholding, allowances, schedule_pay, date_start, expected_withholding):
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=wage,
+ state_id=self.get_us_state('NE'),
+ ne_w4n_sit_filing_status=filing_status,
+ state_income_tax_exempt=exempt,
+ state_income_tax_additional_withholding=additional_withholding,
+ ne_w4n_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.assertPayrollAlmostEqual(cats.get('EE_US_SIT', 0.0), -expected_withholding)
+
+ def test_2020_taxes_example(self):
+ self._test_er_suta('NE', self.NE_UNEMP, date(2020, 1, 1), wage_base=self.NE_UNEMP_MAX_WAGE)
+ self._test_sit(750.0, 'single', False, 0.0, 2, 'weekly', date(2020, 1, 1), 27.53)
+ self._test_sit(9500.0, 'single', False, 0.0, 1, 'bi-weekly', date(2020, 1, 1), 612.63)
+ self._test_sit(10500.0, 'married', False, 0.0, 1, 'bi-weekly', date(2020, 1, 1), 659.85)
+ self._test_sit(9500.0, 'single', True, 0.0, 1, 'bi-weekly', date(2020, 1, 1), 0.0)
+ self._test_sit(10500.0, 'single', False, 10.0, 2, 'monthly', date(2020, 1, 1), 625.2)
+ self._test_sit(4000.0, 'single', False, 0.0, 1, 'monthly', date(2020, 1, 1), 179.44)
diff --git a/l10n_us_hr_payroll/tests/test_us_nh_new_hampshire_payslip_2020.py b/l10n_us_hr_payroll/tests/test_us_nh_new_hampshire_payslip_2020.py
new file mode 100644
index 00000000..1d85e700
--- /dev/null
+++ b/l10n_us_hr_payroll/tests/test_us_nh_new_hampshire_payslip_2020.py
@@ -0,0 +1,13 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from datetime import date
+from .common import TestUsPayslip
+
+
+class TestUsNHPayslip(TestUsPayslip):
+ # TAXES AND RATES
+ NH_UNEMP_MAX_WAGE = 14000.00
+ NH_UNEMP = 1.2
+
+ def test_2020_taxes(self):
+ self._test_er_suta('NH', self.NH_UNEMP, date(2020, 1, 1), wage_base=self.NH_UNEMP_MAX_WAGE)
diff --git a/l10n_us_hr_payroll/tests/test_us_nj_newjersey_payslip_2019.py b/l10n_us_hr_payroll/tests/test_us_nj_newjersey_payslip_2019.py
new file mode 100755
index 00000000..c28849b5
--- /dev/null
+++ b/l10n_us_hr_payroll/tests/test_us_nj_newjersey_payslip_2019.py
@@ -0,0 +1,128 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from .common import TestUsPayslip, process_payslip
+
+
+class TestUsNJPayslip(TestUsPayslip):
+ ###
+ # 2019 Taxes and Rates
+ ###
+ NJ_UNEMP_MAX_WAGE = 34400.0 # Note that this is used for SDI and FLI as well
+
+ ER_NJ_UNEMP = -2.6825 / 100.0
+ EE_NJ_UNEMP = -0.3825 / 100.0
+
+ ER_NJ_SDI = -0.5 / 100.0
+ EE_NJ_SDI = -0.17 / 100.0
+
+ ER_NJ_WF = -0.1175 / 100.0
+ EE_NJ_WF = -0.0425 / 100.0
+
+ ER_NJ_FLI = 0.0
+ EE_NJ_FLI = -0.08 / 100.0
+
+ # Examples found on page 24 of http://www.state.nj.us/treasury/taxation/pdf/current/njwt.pdf
+ def test_2019_taxes_example1(self):
+ salary = 300
+
+ # Tax Percentage Method for Single, taxable under $385
+ wh = -4.21
+
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('NJ'),
+ nj_njw4_sit_filing_status='single',
+ nj_njw4_sit_allowances=1,
+ state_income_tax_additional_withholding=0.0,
+ nj_njw4_sit_rate_table='A',
+ schedule_pay='weekly')
+
+ self._log('2019 New Jersey 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'], salary * (self.EE_NJ_UNEMP + self.EE_NJ_SDI + self.EE_NJ_WF + self.EE_NJ_FLI))
+ self.assertPayrollEqual(cats['ER_US_SUTA'], salary * (self.ER_NJ_UNEMP + self.ER_NJ_SDI + self.ER_NJ_WF + self.ER_NJ_FLI))
+ self.assertTrue(all((cats['EE_US_SUTA'], cats['ER_US_SUTA'])))
+ # self.assertPayrollEqual(cats['EE_US_NJ_SDI_SIT'], cats['EE_US_NJ_SDI_SIT'] * self.EE_NJ_SDI)
+ # self.assertPayrollEqual(cats['ER_US_NJ_SDI_SUTA'], cats['ER_US_NJ_SDI_SUTA'] * self.ER_NJ_SDI)
+ # self.assertPayrollEqual(cats['EE_US_NJ_FLI_SIT'], cats['EE_US_NJ_FLI_SIT'] * self.EE_NJ_FLI)
+ # self.assertPayrollEqual(cats['EE_US_NJ_WF_SIT'], cats['EE_US_NJ_WF_SIT'] * self.EE_NJ_WF)
+ self.assertPayrollEqual(cats['EE_US_SIT'], wh)
+
+ process_payslip(payslip)
+
+ # # Make a new payslip, this one will have maximums
+ #
+ remaining_nj_unemp_wages = self.NJ_UNEMP_MAX_WAGE - salary if (self.NJ_UNEMP_MAX_WAGE - 2 * salary < salary) \
+ else salary
+
+ self._log('2019 New Jersey tax second payslip:')
+ payslip = self._createPayslip(employee, '2019-02-01', '2019-02-28')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+
+ # self.assertPayrollEqual(cats['WAGE_US_NJ_UNEMP'], remaining_nj_unemp_wages)
+ self.assertPayrollEqual(cats['ER_US_SUTA'], remaining_nj_unemp_wages * (self.ER_NJ_UNEMP + self.ER_NJ_SDI + self.ER_NJ_WF + self.ER_NJ_FLI))
+ self.assertPayrollEqual(cats['EE_US_SUTA'], remaining_nj_unemp_wages * (self.EE_NJ_UNEMP + self.EE_NJ_SDI + self.EE_NJ_WF + self.EE_NJ_FLI))
+
+ def test_2019_taxes_example2(self):
+ salary = 1400.00
+
+ # Tax Percentage Method for Single, taxable pay over $962, under $1346
+ wh = -27.58
+
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('NJ'),
+ nj_njw4_sit_filing_status='married_separate',
+ nj_njw4_sit_allowances=3,
+ state_income_tax_additional_withholding=0.0,
+ #nj_njw4_sit_rate_table='B',
+ schedule_pay='weekly')
+
+ self.assertEqual(contract.schedule_pay, 'weekly')
+
+ self._log('2019 New Jersey 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_SIT'], wh)
+
+
+ def test_2019_taxes_to_the_limits(self):
+ salary = 30000.00
+
+ # Tax Percentage Method for Single, taxable pay over $18750, under 125000
+ wh = -1467.51
+
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('NJ'),
+ nj_njw4_sit_filing_status='married_joint',
+ nj_njw4_sit_allowances=3,
+ state_income_tax_additional_withholding=0.0,
+ # nj_njw4_sit_rate_table='B',
+ schedule_pay='quarterly')
+
+ self.assertEqual(contract.schedule_pay, 'quarterly')
+
+ self._log('2019 New Jersey tax first payslip:')
+ payslip = self._createPayslip(employee, '2019-01-01', '2019-03-31')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['EE_US_SIT'], wh)
diff --git a/l10n_us_hr_payroll/tests/test_us_nj_newjersey_payslip_2020.py b/l10n_us_hr_payroll/tests/test_us_nj_newjersey_payslip_2020.py
new file mode 100755
index 00000000..1df4af6a
--- /dev/null
+++ b/l10n_us_hr_payroll/tests/test_us_nj_newjersey_payslip_2020.py
@@ -0,0 +1,51 @@
+# 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 TestUsNJPayslip(TestUsPayslip):
+ ###
+ # 2020 Taxes and Rates
+ ###
+ NJ_UNEMP_MAX_WAGE = 35300.0 # Note that this is used for SDI and FLI as well
+
+ ER_NJ_UNEMP = 2.6825
+ EE_NJ_UNEMP = 0.3825
+
+ ER_NJ_SDI = 0.5
+ EE_NJ_SDI = 0.26
+
+ ER_NJ_WF = 0.1175
+ EE_NJ_WF = 0.0425
+
+ ER_NJ_FLI = 0.0
+ EE_NJ_FLI = 0.16
+
+ def _test_sit(self, wage, filing_status, allowances, schedule_pay, date_start, expected_withholding, rate_table=False):
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=wage,
+ state_id=self.get_us_state('NJ'),
+ nj_njw4_sit_filing_status=filing_status,
+ nj_njw4_sit_allowances=allowances,
+ state_income_tax_additional_withholding=0.0,
+ nj_njw4_sit_rate_table=rate_table,
+ schedule_pay=schedule_pay)
+ payslip = self._createPayslip(employee, date_start, date_start + timedelta(days=7))
+ payslip.compute_sheet()
+ cats = self._getCategories(payslip)
+ self.assertPayrollAlmostEqual(cats.get('EE_US_SIT', 0.0), -expected_withholding if filing_status else 0.0)
+
+ def test_2020_taxes_example1(self):
+ combined_er_rate = self.ER_NJ_UNEMP + self.ER_NJ_FLI + self.ER_NJ_SDI + self.ER_NJ_WF
+ self._test_er_suta('NJ', combined_er_rate, date(2020, 1, 1), wage_base=self.NJ_UNEMP_MAX_WAGE)
+ combined_ee_rate = self.EE_NJ_UNEMP + self.EE_NJ_FLI + self.EE_NJ_SDI + self.EE_NJ_WF
+ self._test_ee_suta('NJ', combined_ee_rate, date(2020, 1, 1), wage_base=self.NJ_UNEMP_MAX_WAGE, relaxed=True)
+ # these expected values come from https://www.state.nj.us/treasury/taxation/pdf/current/njwt.pdf
+ self._test_sit(300.0, 'single', 1, 'weekly', date(2020, 1, 1), 4.21)
+ self._test_sit(375.0, 'married_separate', 3, 'weekly', date(2020, 1, 1), 4.76)
+ self._test_sit(1400.0, 'head_household', 3, 'weekly', date(2020, 1, 1), 27.60)
+ self._test_sit(1400.0, '', 3, 'weekly', date(2020, 1, 1), 0.00)
+ self._test_sit(2500.0, 'single', 3, 'bi-weekly', date(2020, 1, 1), 82.66)
+ self._test_sit(15000.0, 'married_joint', 2, 'monthly', date(2020, 1, 1), 844.85)
diff --git a/l10n_us_hr_payroll/tests/test_us_nm_new_mexico_payslip_2020.py b/l10n_us_hr_payroll/tests/test_us_nm_new_mexico_payslip_2020.py
new file mode 100755
index 00000000..24f8c5a4
--- /dev/null
+++ b/l10n_us_hr_payroll/tests/test_us_nm_new_mexico_payslip_2020.py
@@ -0,0 +1,36 @@
+# 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 TestUsNMPayslip(TestUsPayslip):
+ ###
+ # 2020 Taxes and Rates
+ ###
+ NM_UNEMP_MAX_WAGE = 25800.0
+ NM_UNEMP = 1.0
+ # Calculation based on section 17. https://s3.amazonaws.com/realFile34821a95-73ca-43e7-b06d-fad20f5183fd/a9bf1098-533b-4a3d-806a-4bf6336af6e4?response-content-disposition=filename%3D%22FYI-104+-+New+Mexico+Withholding+Tax+-+Effective+January+1%2C+2020.pdf%22&response-content-type=application%2Fpdf&AWSAccessKeyId=AKIAJBI25DHBYGD7I7TA&Signature=feu%2F1oJvU6BciRfKcoR0iNxoVZE%3D&Expires=1585159702
+
+ def _test_sit(self, wage, filing_status, additional_withholding, schedule_pay, date_start, expected_withholding):
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=wage,
+ state_id=self.get_us_state('NM'),
+ fed_941_fit_w4_filing_status=filing_status,
+ state_income_tax_additional_withholding=additional_withholding,
+ 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('NM', self.NM_UNEMP, date(2020, 1, 1), wage_base=self.NM_UNEMP_MAX_WAGE)
+ self._test_sit(1000.0, 'married', 0.0, 'weekly', date(2020, 1, 1), 29.47)
+ self._test_sit(1000.0, 'married', 10.0, 'weekly', date(2020, 1, 1), 39.47)
+ self._test_sit(25000.0, 'single', 0.0, 'bi-weekly', date(2020, 1, 1), 1202.60)
+ self._test_sit(25000.0, 'married_as_single', 0.0, 'monthly', date(2020, 1, 1), 1152.95)
+ self._test_sit(4400.0, '', 0.0, 'monthly', date(2020, 1, 1), 0.00)
diff --git a/l10n_us_hr_payroll/tests/test_us_nv_nevada_payslip_2020.py b/l10n_us_hr_payroll/tests/test_us_nv_nevada_payslip_2020.py
new file mode 100755
index 00000000..52c2114b
--- /dev/null
+++ b/l10n_us_hr_payroll/tests/test_us_nv_nevada_payslip_2020.py
@@ -0,0 +1,16 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from datetime import date
+from .common import TestUsPayslip
+
+
+class TestUsNVPayslip(TestUsPayslip):
+ ###
+ # 2020 Taxes and Rates
+ ###
+ NV_UNEMP_MAX_WAGE = 32500.0
+ NV_UNEMP = 2.95
+
+ def test_2020_taxes(self):
+ # Only has state unemployment
+ self._test_er_suta('NV', self.NV_UNEMP, date(2020, 1, 1), wage_base=self.NV_UNEMP_MAX_WAGE)
diff --git a/l10n_us_hr_payroll/tests/test_us_ny_new_york_payslip_2019.py b/l10n_us_hr_payroll/tests/test_us_ny_new_york_payslip_2019.py
new file mode 100644
index 00000000..2c3b9306
--- /dev/null
+++ b/l10n_us_hr_payroll/tests/test_us_ny_new_york_payslip_2019.py
@@ -0,0 +1,133 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from .common import TestUsPayslip, process_payslip
+
+
+class TestUsNYPayslip(TestUsPayslip):
+ ###
+ # Taxes and Rates
+ ###
+ NY_UNEMP_MAX_WAGE = 11400.0
+ NY_UNEMP = 2.5
+ NY_RSF = 0.075
+ NY_MCTMT = 0.0
+
+ def test_single_example1(self):
+ salary = 400
+ schedule_pay = 'weekly'
+ allowances = 3
+ additional_withholding = 0
+ filing_status = 'single'
+ additional = 0.0
+ wh = -8.20
+
+ employee = self._createEmployee()
+
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('NY'),
+ ny_it2104_sit_filing_status=filing_status,
+ state_income_tax_additional_withholding=additional,
+ ny_it2104_sit_allowances=allowances,
+ schedule_pay=schedule_pay)
+
+ self.assertEqual(contract.schedule_pay, 'weekly')
+ self._log('2018 New York tax first payslip:')
+ payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['NY_UNEMP'], cats['NY_UNEMP_WAGES'] * self.NY_UNEMP)
+ self.assertPayrollEqual(cats['NY_RSF'], cats['NY_UNEMP_WAGES'] * self.NY_RSF)
+ self.assertPayrollEqual(cats['NY_MCTMT'], cats['NY_UNEMP_WAGES'] * self.NY_MCTMT)
+ self.assertPayrollEqual(cats['EE_US_SIT'], wh)
+
+ process_payslip(payslip)
+
+ def test_married_example2(self):
+ salary = 5000
+ schedule_pay = 'semi-monthly'
+ allowances = 3
+ additional = 0
+ filing_status = 'married'
+ wh = -284.19
+
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('NY'),
+ ny_it2104_sit_filing_status=filing_status,
+ state_income_tax_additional_withholding=additional,
+ ny_it2104_sit_allowances=allowances,
+ schedule_pay=schedule_pay)
+
+ self._log('2019 New York tax first payslip:')
+ payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['NY_UNEMP'], cats['NY_UNEMP_WAGES'] * self.NY_UNEMP)
+ self.assertPayrollEqual(cats['NY_RSF'], cats['NY_UNEMP_WAGES'] * self.NY_RSF)
+ self.assertPayrollEqual(cats['NY_MCTMT'], cats['NY_UNEMP_WAGES'] * self.NY_MCTMT)
+ self.assertPayrollEqual(cats['EE_US_SIT'], wh)
+
+ process_payslip(payslip)
+
+ def test_single_example3(self):
+ salary = 50000
+ schedule_pay = 'monthly'
+ allowances = 3
+ additional = 0
+ filing_status = 'single'
+ wh = -3575.63
+
+ employee = self._createEmployee()
+
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('NY'),
+ ny_it2104_sit_filing_status=filing_status,
+ state_income_tax_additional_withholding=additional,
+ ny_it2104_sit_allowances=allowances,
+ schedule_pay=schedule_pay)
+
+ self._log('2019 New York 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_SIT'], wh)
+
+ def test_exempt_example3(self):
+ salary = 50000
+ schedule_pay = 'monthly'
+ allowances = 3
+ additional = 0
+ filing_status = ''
+ wh = 0.0
+
+ employee = self._createEmployee()
+
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('NY'),
+ ny_it2104_sit_filing_status=filing_status,
+ state_income_tax_additional_withholding=additional,
+ ny_it2104_sit_allowances=allowances,
+ schedule_pay=schedule_pay)
+
+ self._log('2019 New York 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_SIT'], wh)
+
diff --git a/l10n_us_hr_payroll/tests/test_us_ny_new_york_payslip_2020.py b/l10n_us_hr_payroll/tests/test_us_ny_new_york_payslip_2020.py
new file mode 100644
index 00000000..05e50792
--- /dev/null
+++ b/l10n_us_hr_payroll/tests/test_us_ny_new_york_payslip_2020.py
@@ -0,0 +1,39 @@
+# 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 TestUsNYPayslip(TestUsPayslip):
+ ###
+ # 2020 Taxes and Rates
+ ###
+ NY_UNEMP_MAX_WAGE = 11600.0
+ NY_UNEMP = 2.5
+ NY_RSF = 0.075
+ NY_MCTMT = 0.0
+
+ def _test_sit(self, wage, filing_status, additional_withholding, allowances, schedule_pay, date_start, expected_withholding):
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=wage,
+ state_id=self.get_us_state('NY'),
+ ny_it2104_sit_filing_status=filing_status,
+ state_income_tax_additional_withholding=additional_withholding,
+ ny_it2104_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.assertPayrollAlmostEqual(cats.get('EE_US_SIT', 0.0), -expected_withholding)
+
+ def test_2020_taxes_example(self):
+ combined_er_rate = self.NY_UNEMP + self.NY_RSF + self.NY_MCTMT
+ self._test_er_suta('NY', combined_er_rate, date(2020, 1, 1), wage_base=self.NY_UNEMP_MAX_WAGE, relaxed=True)
+ self._test_sit(400.0, 'single', 0.0, 3, 'weekly', date(2020, 1, 1), 8.20)
+ self._test_sit(10000.0, 'single', 0.0, 3, 'monthly', date(2020, 1, 1), 554.09)
+ self._test_sit(8000.0, 'married', 0.0, 5, 'monthly', date(2020, 1, 1), 400.32)
+ self._test_sit(4500.0, 'married', 10.0, 3, 'semi-monthly', date(2020, 1, 1), 247.69)
+ self._test_sit(50000.0, '', 0.0, 0, 'monthly', date(2020, 1, 1), 0.00)
diff --git a/l10n_us_hr_payroll/tests/test_us_oh_ohio_payslip_2019.py b/l10n_us_hr_payroll/tests/test_us_oh_ohio_payslip_2019.py
new file mode 100755
index 00000000..d1f65f05
--- /dev/null
+++ b/l10n_us_hr_payroll/tests/test_us_oh_ohio_payslip_2019.py
@@ -0,0 +1,96 @@
+# 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
+
+
+class TestUsOhPayslip(TestUsPayslip):
+ ###
+ # Taxes and Rates
+ ###
+ OH_UNEMP_MAX_WAGE = 9500.0
+ OH_UNEMP = -2.7 / 100.0
+
+ def test_2019_taxes(self):
+ salary = 5000.0
+
+ # For formula here
+ # http://www.tax.ohio.gov/Portals/0/employer_withholding/August2015Rates/WTH_OptionalComputerFormula_073015.pdf
+ tw = salary * 12 # = 60000
+ wd = ((tw - 40000) * 0.035 + 900) / 12 * 1.075
+
+ employee = self._createEmployee()
+
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('OH'),
+ )
+
+ self._log('2019 Ohio 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 * self.OH_UNEMP)
+ self.assertPayrollAlmostEqual(cats['EE_US_SIT'], -wd) # Off by 0.6 cents so it rounds off by a penny
+ #self.assertPayrollEqual(cats['EE_US_SIT'], -wd)
+
+ process_payslip(payslip)
+
+ # Make a new payslip, this one will have maximums
+
+ remaining_oh_unemp_wages = self.OH_UNEMP_MAX_WAGE - salary if (self.OH_UNEMP_MAX_WAGE - 2*salary < salary) \
+ else salary
+
+ self._log('2019 Ohio 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_oh_unemp_wages * self.OH_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('OH'),
+ external_wages=external_wages,
+ )
+
+ self._log('2019 Ohio_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.OH_UNEMP_MAX_WAGE - external_wages) * self.OH_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('OH'),
+ external_wages=external_wages,
+ futa_type=USHRContract.FUTA_TYPE_BASIC)
+
+ self._log('2019 Ohio exempt tax first payslip:')
+ payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+
+ # FUTA_TYPE_BASIC
+ self.assertPayrollEqual(cats.get('ER_US_SUTA', 0.0), salary * 0.0)
diff --git a/l10n_us_hr_payroll/tests/test_us_oh_ohio_payslip_2020.py b/l10n_us_hr_payroll/tests/test_us_oh_ohio_payslip_2020.py
new file mode 100755
index 00000000..9026da92
--- /dev/null
+++ b/l10n_us_hr_payroll/tests/test_us_oh_ohio_payslip_2020.py
@@ -0,0 +1,108 @@
+# 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
+
+
+class TestUsOhPayslip(TestUsPayslip):
+ ###
+ # Taxes and Rates
+ ###
+ OH_UNEMP_MAX_WAGE = 9000.0
+ OH_UNEMP = 2.7
+
+ def test_2020_taxes(self):
+ self._test_er_suta('OH', self.OH_UNEMP, date(2020, 1, 1), wage_base=self.OH_UNEMP_MAX_WAGE)
+
+ 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,
+ oh_it4_sit_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,
+ oh_it4_sit_exemptions=oh_it4_sit_exemptions,
+ state_id=self.get_us_state('OH'),
+ )
+ 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_sit_1(self):
+ wage = 400.0
+ exemptions = 1
+ additional = 10.0
+ pay_periods = 12.0
+ annual_adjusted_wage = (wage * pay_periods) - (650.0 * exemptions)
+ self.assertPayrollEqual(4150.0, annual_adjusted_wage)
+ WD = ((annual_adjusted_wage * 0.005) / pay_periods) * 1.032
+ self.assertPayrollEqual(WD, 1.7845)
+ expected = WD + additional
+ self._run_test_sit(wage=wage,
+ schedule_pay='monthly',
+ state_income_tax_exempt=False,
+ state_income_tax_additional_withholding=additional,
+ oh_it4_sit_exemptions=exemptions,
+ expected=expected,
+ )
+
+ # the above agrees with online calculator to the penny 0.01
+ # below expected coming from calculator to 0.10
+ #
+ # semi-monthly
+ self._run_test_sit(wage=1200,
+ schedule_pay='semi-monthly',
+ state_income_tax_exempt=False,
+ state_income_tax_additional_withholding=20.0,
+ oh_it4_sit_exemptions=2,
+ expected=42.58,
+ )
+
+ # bi-weekly
+ self._run_test_sit(wage=3000,
+ schedule_pay='bi-weekly',
+ state_income_tax_exempt=False,
+ #state_income_tax_additional_withholding=0.0,
+ oh_it4_sit_exemptions=0,
+ expected=88.51,
+ )
+ # weekly
+ self._run_test_sit(wage=355,
+ schedule_pay='weekly',
+ state_income_tax_exempt=False,
+ # state_income_tax_additional_withholding=0.0,
+ oh_it4_sit_exemptions=1,
+ expected=4.87,
+ )
+
+ # Exempt!
+ self._run_test_sit(wage=355,
+ schedule_pay='weekly',
+ state_income_tax_exempt=True,
+ # state_income_tax_additional_withholding=0.0,
+ oh_it4_sit_exemptions=1,
+ expected=0.0,
+ )
diff --git a/l10n_us_hr_payroll/tests/test_us_ok_oklahoma_payslip_2020.py b/l10n_us_hr_payroll/tests/test_us_ok_oklahoma_payslip_2020.py
new file mode 100755
index 00000000..cacdcc16
--- /dev/null
+++ b/l10n_us_hr_payroll/tests/test_us_ok_oklahoma_payslip_2020.py
@@ -0,0 +1,38 @@
+# 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 TestUsOKPayslip(TestUsPayslip):
+ ###
+ # 2020 Taxes and Rates
+ ###
+ OK_UNEMP_MAX_WAGE = 18700.0
+ OK_UNEMP = 1.5
+ # Calculation based on example https://www.ok.gov/tax/documents/2020WHTables.pdf
+
+ def _test_sit(self, wage, filing_status, allowances, additional_withholding, exempt, schedule_pay, date_start, expected_withholding):
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=wage,
+ state_id=self.get_us_state('OK'),
+ ok_w4_sit_filing_status=filing_status,
+ ok_w4_sit_allowances=allowances,
+ state_income_tax_additional_withholding=additional_withholding,
+ state_income_tax_exempt=exempt,
+ 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('OK', self.OK_UNEMP, date(2020, 1, 1), wage_base=self.OK_UNEMP_MAX_WAGE)
+ self._test_sit(1825, 'married', 2, 0, False, 'semi-monthly', date(2020, 1, 1), 46.00)
+ self._test_sit(1825, 'married', 2, 0, True, 'monthly', date(2020, 1, 1), 0.00)
+ self._test_sit(1000, 'single', 1, 0, False, 'weekly', date(2020, 1, 1), 39.00)
+ self._test_sit(1000, 'single', 1, 10, False, 'weekly', date(2020, 1, 1), 49.00)
+ self._test_sit(5000, 'head_household', 2, 10, False, 'monthly', date(2020, 1, 1), 210.00)
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_ri_rhode_island_payslip_2020.py b/l10n_us_hr_payroll/tests/test_us_ri_rhode_island_payslip_2020.py
new file mode 100755
index 00000000..9d92fec1
--- /dev/null
+++ b/l10n_us_hr_payroll/tests/test_us_ri_rhode_island_payslip_2020.py
@@ -0,0 +1,37 @@
+# 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 TestUsRIPayslip(TestUsPayslip):
+ ###
+ # 2020 Taxes and Rates
+ ###
+ RI_UNEMP_MAX_WAGE = 24000.0
+ RI_UNEMP = 1.06
+ # Calculation based on example http://www.tax.ri.gov/forms/2020/Withholding/2020%20Withhholding%20Tax%20Booklet.pdf
+
+ def _test_sit(self, wage, allowances, additional_withholding, exempt, schedule_pay, date_start, expected_withholding):
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=wage,
+ state_id=self.get_us_state('RI'),
+ ri_w4_sit_allowances=allowances,
+ state_income_tax_additional_withholding=additional_withholding,
+ state_income_tax_exempt=exempt,
+ 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('RI', self.RI_UNEMP, date(2020, 1, 1), wage_base=self.RI_UNEMP_MAX_WAGE)
+ self._test_sit(2195, 1, 0, False, 'weekly', date(2020, 1, 1), 90.80)
+ self._test_sit(1800, 2, 10, True, 'weekly', date(2020, 1, 1), 0.00)
+ self._test_sit(10000, 1, 0, False, 'bi-weekly', date(2020, 1, 1), 503.15)
+ self._test_sit(18000, 2, 0, False, 'monthly', date(2020, 1, 1), 860.54)
+ self._test_sit(18000, 2, 10, False, 'monthly', date(2020, 1, 1), 870.55)
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..170c3bf5
--- /dev/null
+++ b/l10n_us_hr_payroll/tests/test_us_sc_south_carolina_payslip_2020.py
@@ -0,0 +1,36 @@
+# 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 = 0.55
+ # Calculation based on https://dor.sc.gov/forms-site/Forms/WH1603F_2020.pdf
+
+ def _test_sit(self, wage, additional_withholding, 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_additional_withholding=additional_withholding,
+ 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.assertPayrollAlmostEqual(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, 0.0, False, 3.0, 'weekly', date(2020, 1, 1), 28.73)
+ self._test_sit(800.0, 0.0, True, 0.0, 'weekly', date(2020, 1, 1), 0.00)
+ self._test_sit(9000.0, 0.0, False, 0.0, 'monthly', date(2020, 1, 1), 594.61)
+ self._test_sit(5000.0, 10.0, False, 2.0, 'semi-monthly', date(2020, 1, 1), 316.06)
diff --git a/l10n_us_hr_payroll/tests/test_us_sd_south_dakota_payslip_2020.py b/l10n_us_hr_payroll/tests/test_us_sd_south_dakota_payslip_2020.py
new file mode 100644
index 00000000..bdbb7858
--- /dev/null
+++ b/l10n_us_hr_payroll/tests/test_us_sd_south_dakota_payslip_2020.py
@@ -0,0 +1,13 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from datetime import date
+from .common import TestUsPayslip
+
+
+class TestUsSDPayslip(TestUsPayslip):
+ # TAXES AND RATES
+ SD_UNEMP_MAX_WAGE = 15000.00
+ SD_UNEMP = 1.75
+
+ def test_2020_taxes(self):
+ self._test_er_suta('SD', self.SD_UNEMP, date(2020, 1, 1), wage_base=self.SD_UNEMP_MAX_WAGE)
diff --git a/l10n_us_hr_payroll/tests/test_us_tn_tennessee_payslip_2020.py b/l10n_us_hr_payroll/tests/test_us_tn_tennessee_payslip_2020.py
new file mode 100644
index 00000000..54acfa9a
--- /dev/null
+++ b/l10n_us_hr_payroll/tests/test_us_tn_tennessee_payslip_2020.py
@@ -0,0 +1,13 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from datetime import date
+from .common import TestUsPayslip
+
+
+class TestUsTNPayslip(TestUsPayslip):
+ # TAXES AND RATES
+ TN_UNEMP_MAX_WAGE = 7000.00
+ TN_UNEMP = 2.7
+
+ def test_2020_taxes(self):
+ self._test_er_suta('TN', self.TN_UNEMP, date(2020, 1, 1), wage_base=self.TN_UNEMP_MAX_WAGE)
diff --git a/l10n_us_hr_payroll/tests/test_us_tx_texas_payslip_2019.py b/l10n_us_hr_payroll/tests/test_us_tx_texas_payslip_2019.py
new file mode 100755
index 00000000..15e657ae
--- /dev/null
+++ b/l10n_us_hr_payroll/tests/test_us_tx_texas_payslip_2019.py
@@ -0,0 +1,100 @@
+# 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
+
+class TestUsTXPayslip(TestUsPayslip):
+ ###
+ # 2019 Taxes and Rates
+ ###
+ TX_UNEMP_MAX_WAGE = 9000.0
+ TX_UNEMP = -2.7 / 100.0
+ TX_OA = 0.0
+ TX_ETIA = -0.1 / 100.0
+
+ def test_2019_taxes(self):
+ salary = 5000.0
+
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('TX'),
+ )
+
+ self._log('2019 Texas tax first payslip:')
+ payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+ rules = self._getRules(payslip)
+
+ self.assertPayrollEqual(rules['ER_US_TX_SUTA'], salary * self.TX_UNEMP)
+ self.assertPayrollEqual(rules['ER_US_TX_SUTA_OA'], salary * self.TX_OA)
+ self.assertPayrollEqual(rules['ER_US_TX_SUTA_ETIA'], salary * self.TX_ETIA)
+
+ process_payslip(payslip)
+
+ # Make a new payslip, this one will have maximums
+
+ remaining_tx_unemp_wages = self.TX_UNEMP_MAX_WAGE - salary if (self.TX_UNEMP_MAX_WAGE - 2*salary < salary) \
+ else salary
+
+ self._log('2019 Texas tax second payslip:')
+ payslip = self._createPayslip(employee, '2019-02-01', '2019-02-28')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+ rules = self._getRules(payslip)
+
+ self.assertPayrollEqual(rules['ER_US_TX_SUTA'], remaining_tx_unemp_wages * self.TX_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('TX'),
+ external_wages=external_wages,
+ )
+
+ self._log('2019 Texas_external tax first payslip:')
+ payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+ rules = self._getRules(payslip)
+
+ expected_wage = self.TX_UNEMP_MAX_WAGE - external_wages
+ self.assertPayrollEqual(rules['ER_US_TX_SUTA'], expected_wage * self.TX_UNEMP)
+ self.assertPayrollEqual(rules['ER_US_TX_SUTA_OA'], expected_wage * self.TX_OA)
+ self.assertPayrollEqual(rules['ER_US_TX_SUTA_ETIA'], expected_wage * self.TX_ETIA)
+
+ 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('TX'),
+ external_wages=external_wages,
+ futa_type=USHRContract.FUTA_TYPE_BASIC)
+
+ self._log('2019 Texas_external tax first payslip:')
+ payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+ rules = self._getRules(payslip)
+
+ self.assertPayrollEqual(rules.get('ER_US_TX_SUTA', 0.0), 0.0)
+ self.assertPayrollEqual(rules.get('ER_US_TX_SUTA_OA', 0.0), 0.0)
+ self.assertPayrollEqual(rules.get('ER_US_TX_SUTA_ETIA', 0.0), 0.0)
diff --git a/l10n_us_hr_payroll/tests/test_us_tx_texas_payslip_2020.py b/l10n_us_hr_payroll/tests/test_us_tx_texas_payslip_2020.py
new file mode 100755
index 00000000..8dba312c
--- /dev/null
+++ b/l10n_us_hr_payroll/tests/test_us_tx_texas_payslip_2020.py
@@ -0,0 +1,17 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from datetime import date
+from .common import TestUsPayslip
+
+class TestUsTXPayslip(TestUsPayslip):
+ ###
+ # 2020 Taxes and Rates
+ ###
+ TX_UNEMP_MAX_WAGE = 9000.0
+ TX_UNEMP = 2.7
+ TX_OA = 0.0
+ TX_ETIA = 0.1
+
+ def test_2020_taxes(self):
+ combined_rate = self.TX_UNEMP + self.TX_OA + self.TX_ETIA
+ self._test_er_suta('TX', combined_rate, date(2020, 1, 1), wage_base=self.TX_UNEMP_MAX_WAGE)
diff --git a/l10n_us_hr_payroll/tests/test_us_us_utah_payslip_2020.py b/l10n_us_hr_payroll/tests/test_us_us_utah_payslip_2020.py
new file mode 100755
index 00000000..a6881acb
--- /dev/null
+++ b/l10n_us_hr_payroll/tests/test_us_us_utah_payslip_2020.py
@@ -0,0 +1,36 @@
+# 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 TestUsUTPayslip(TestUsPayslip):
+ ###
+ # 2020 Taxes and Rates
+ ###
+ UT_UNEMP_MAX_WAGE = 36600.0
+ UT_UNEMP = 0.1
+ # Calculation based on example https://tax.utah.gov/forms/pubs/pub-14.pdf
+
+ def _test_sit(self, wage, filing_status, additional_withholding, schedule_pay, date_start, expected_withholding):
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=wage,
+ state_id=self.get_us_state('UT'),
+ ut_w4_sit_filing_status=filing_status,
+ state_income_tax_additional_withholding=additional_withholding,
+ 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.assertPayrollAlmostEqual(cats.get('EE_US_SIT', 0.0), -expected_withholding)
+
+ def test_2020_taxes_example(self):
+ self._test_er_suta('UT', self.UT_UNEMP, date(2020, 1, 1), wage_base=self.UT_UNEMP_MAX_WAGE)
+ self._test_sit(400, 'single', 0, 'weekly', date(2020, 1, 1), 16.00)
+ self._test_sit(1000, 'single', 0, 'bi-weekly', date(2020, 1, 1), 45.00)
+ self._test_sit(855, 'married', 0, 'semi-monthly', date(2020, 1, 1), 16.00)
+ self._test_sit(2500, 'married', 0, 'monthly', date(2020, 1, 1), 81.00)
+ self._test_sit(8000, 'head_household', 10, 'quarterly', date(2020, 1, 1), 397.00)
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/tests/test_us_vt_vermont_payslip_2020.py b/l10n_us_hr_payroll/tests/test_us_vt_vermont_payslip_2020.py
new file mode 100755
index 00000000..7807bed7
--- /dev/null
+++ b/l10n_us_hr_payroll/tests/test_us_vt_vermont_payslip_2020.py
@@ -0,0 +1,37 @@
+# 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 TestUsVTPayslip(TestUsPayslip):
+ ###
+ # 2020 Taxes and Rates
+ ###
+ VT_UNEMP_MAX_WAGE = 16100.0
+ VT_UNEMP = 1.0
+ # Calculation based on example https://tax.vermont.gov/sites/tax/files/documents/WithholdingInstructions.pdf
+
+ def _test_sit(self, wage, filing_status, allowances, additional_withholding, exempt, schedule_pay, date_start, expected_withholding):
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=wage,
+ state_id=self.get_us_state('VT'),
+ vt_w4vt_sit_filing_status=filing_status,
+ vt_w4vt_sit_allowances=allowances,
+ state_income_tax_additional_withholding=additional_withholding,
+ state_income_tax_exempt=exempt,
+ 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('VT', self.VT_UNEMP, date(2020, 1, 1), wage_base=self.VT_UNEMP_MAX_WAGE)
+ self._test_sit(1800, 'married', 2, 0, False, 'weekly', date(2020, 1, 1), 53.73)
+ self._test_sit(1800, 'married', 2, 10, False, 'weekly', date(2020, 1, 1), 63.73)
+ self._test_sit(1000, 'single', 1, 0, True, 'weekly', date(2020, 1, 1), 0.00)
+ self._test_sit(8000, 'single', 1, 10, False, 'bi-weekly', date(2020, 1, 1), 506.58)
diff --git a/l10n_us_hr_payroll/tests/test_us_wa_washington_payslip_2019.py b/l10n_us_hr_payroll/tests/test_us_wa_washington_payslip_2019.py
new file mode 100755
index 00000000..b67f69c6
--- /dev/null
+++ b/l10n_us_hr_payroll/tests/test_us_wa_washington_payslip_2019.py
@@ -0,0 +1,92 @@
+# 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
+
+
+class TestUsWAPayslip(TestUsPayslip):
+ ###
+ # Taxes and Rates
+ ###
+ WA_UNEMP_MAX_WAGE = 49800.0
+ WA_UNEMP_RATE = 1.18
+ WA_FML_RATE = 0.4
+ WA_FML_RATE_EE = 66.33
+ WA_FML_RATE_ER = 33.67
+
+ def setUp(self):
+ super(TestUsWAPayslip, self).setUp()
+ # self.lni = self.env['hr.contract.lni.wa'].create({
+ # 'name': '5302 Computer Consulting',
+ # 'rate': 0.1261,
+ # 'rate_emp_withhold': 0.05575,
+ # })
+ self.test_ee_lni = 0.05575 # per 100 hours
+ self.test_er_lni = 0.1261 # per 100 hours
+ self.parameter_lni_ee = self.env['hr.rule.parameter'].create({
+ 'name': 'Test LNI EE',
+ 'code': 'test_lni_ee',
+ 'parameter_version_ids': [(0, 0, {
+ 'date_from': date(2019, 1, 1),
+ 'parameter_value': str(self.test_ee_lni * 100),
+ })],
+ })
+ self.parameter_lni_er = self.env['hr.rule.parameter'].create({
+ 'name': 'Test LNI ER',
+ 'code': 'test_lni_er',
+ 'parameter_version_ids': [(0, 0, {
+ 'date_from': date(2019, 1, 1),
+ 'parameter_value': str(self.test_er_lni * 100),
+ })],
+ })
+
+ def test_2019_taxes(self):
+ salary = 25000.0
+
+ employee = self._createEmployee()
+
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('WA'),
+ workers_comp_ee_code=self.parameter_lni_ee.code,
+ workers_comp_er_code=self.parameter_lni_er.code,
+ )
+ self._log(str(contract.resource_calendar_id) + ' ' + contract.resource_calendar_id.name)
+
+
+ # tax rates
+ wa_unemp = self.WA_UNEMP_RATE / -100.0
+
+ self._log('2019 Washington tax first payslip:')
+ payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
+ hours_in_period = payslip.worked_days_line_ids.filtered(lambda l: l.code == 'WORK100').number_of_hours
+ self.assertEqual(hours_in_period, 184) # only asserted to test algorithm
+ payslip.compute_sheet()
+
+
+ cats = self._getCategories(payslip)
+ rules = self._getRules(payslip)
+
+ self.assertPayrollEqual(cats['ER_US_SUTA'], salary * wa_unemp)
+ self.assertPayrollEqual(rules['EE_US_WA_LNI'], -(self.test_ee_lni * hours_in_period))
+ self.assertPayrollEqual(rules['ER_US_WA_LNI'], -(self.test_er_lni * hours_in_period))
+ # Both of these are known to be within 1 penny
+ self.assertPayrollAlmostEqual(rules['EE_US_WA_FML'], -(salary * (self.WA_FML_RATE / 100.0) * (self.WA_FML_RATE_EE / 100.0)))
+ self.assertPayrollAlmostEqual(rules['ER_US_WA_FML'], -(salary * (self.WA_FML_RATE / 100.0) * (self.WA_FML_RATE_ER / 100.0)))
+
+ # FML
+
+ process_payslip(payslip)
+
+ # Make a new payslip, this one will have maximums
+
+ remaining_wa_unemp_wages = self.WA_UNEMP_MAX_WAGE - salary if (self.WA_UNEMP_MAX_WAGE - 2*salary < salary) \
+ else salary
+
+ self._log('2019 Washington 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_wa_unemp_wages * wa_unemp)
diff --git a/l10n_us_hr_payroll/tests/test_us_wa_washington_payslip_2020.py b/l10n_us_hr_payroll/tests/test_us_wa_washington_payslip_2020.py
new file mode 100755
index 00000000..509e19b1
--- /dev/null
+++ b/l10n_us_hr_payroll/tests/test_us_wa_washington_payslip_2020.py
@@ -0,0 +1,90 @@
+# 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
+
+
+class TestUsWAPayslip(TestUsPayslip):
+ ###
+ # Taxes and Rates
+ ###
+ WA_UNEMP_MAX_WAGE = 52700.00
+ WA_UNEMP_RATE = 1.0
+ WA_FML_MAX_WAGE = 137700.00
+ WA_FML_RATE = 0.4
+ WA_FML_RATE_EE = 66.33
+ WA_FML_RATE_ER = 33.67
+
+ def setUp(self):
+ super(TestUsWAPayslip, self).setUp()
+ # self.lni = self.env['hr.contract.lni.wa'].create({
+ # 'name': '5302 Computer Consulting',
+ # 'rate': 0.1261,
+ # 'rate_emp_withhold': 0.05575,
+ # })
+ self.test_ee_lni = 0.05575 # per 100 hours
+ self.test_er_lni = 0.1261 # per 100 hours
+ self.parameter_lni_ee = self.env['hr.rule.parameter'].create({
+ 'name': 'Test LNI EE',
+ 'code': 'test_lni_ee',
+ 'parameter_version_ids': [(0, 0, {
+ 'date_from': date(2020, 1, 1),
+ 'parameter_value': str(self.test_ee_lni * 100),
+ })],
+ })
+ self.parameter_lni_er = self.env['hr.rule.parameter'].create({
+ 'name': 'Test LNI ER',
+ 'code': 'test_lni_er',
+ 'parameter_version_ids': [(0, 0, {
+ 'date_from': date(2020, 1, 1),
+ 'parameter_value': str(self.test_er_lni * 100),
+ })],
+ })
+
+ def test_2020_taxes(self):
+ self._test_er_suta('WA', self.WA_UNEMP_RATE, date(2020, 1, 1), wage_base=self.WA_UNEMP_MAX_WAGE)
+
+ salary = (self.WA_FML_MAX_WAGE / 2.0) + 1000.0
+
+ employee = self._createEmployee()
+
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('WA'),
+ workers_comp_ee_code=self.parameter_lni_ee.code,
+ workers_comp_er_code=self.parameter_lni_er.code,
+ )
+ self._log(str(contract.resource_calendar_id) + ' ' + contract.resource_calendar_id.name)
+
+
+ # Non SUTA
+ self._log('2020 Washington tax first payslip:')
+ payslip = self._createPayslip(employee, '2020-01-01', '2020-01-31')
+ hours_in_period = payslip.worked_days_line_ids.filtered(lambda l: l.code == 'WORK100').number_of_hours
+ self.assertEqual(hours_in_period, 184) # only asserted to test algorithm
+ payslip.compute_sheet()
+
+ rules = self._getRules(payslip)
+
+ self.assertPayrollEqual(rules['EE_US_WA_LNI'], -(self.test_ee_lni * hours_in_period))
+ self.assertPayrollEqual(rules['ER_US_WA_LNI'], -(self.test_er_lni * hours_in_period))
+ # Both of these are known to be within 1 penny
+ self.assertPayrollAlmostEqual(rules['EE_US_WA_FML'], -(salary * (self.WA_FML_RATE / 100.0) * (self.WA_FML_RATE_EE / 100.0)))
+ self.assertPayrollAlmostEqual(rules['ER_US_WA_FML'], -(salary * (self.WA_FML_RATE / 100.0) * (self.WA_FML_RATE_ER / 100.0)))
+ process_payslip(payslip)
+
+ # Second payslip
+ remaining_wage = self.WA_FML_MAX_WAGE - salary
+ payslip = self._createPayslip(employee, '2020-03-01', '2020-03-31')
+ payslip.compute_sheet()
+ rules = self._getRules(payslip)
+ self.assertPayrollAlmostEqual(rules['EE_US_WA_FML'], -(remaining_wage * (self.WA_FML_RATE / 100.0) * (self.WA_FML_RATE_EE / 100.0)))
+ self.assertPayrollAlmostEqual(rules['ER_US_WA_FML'], -(remaining_wage * (self.WA_FML_RATE / 100.0) * (self.WA_FML_RATE_ER / 100.0)))
+ process_payslip(payslip)
+
+ # Third payslip
+ payslip = self._createPayslip(employee, '2020-04-01', '2020-04-30')
+ payslip.compute_sheet()
+ rules = self._getRules(payslip)
+ self.assertPayrollAlmostEqual(rules['EE_US_WA_FML'], 0.0)
+ self.assertPayrollAlmostEqual(rules['ER_US_WA_FML'], 0.0)
diff --git a/l10n_us_hr_payroll/tests/test_us_wi_wisconsin_payslip_2020.py b/l10n_us_hr_payroll/tests/test_us_wi_wisconsin_payslip_2020.py
new file mode 100755
index 00000000..32bdfa30
--- /dev/null
+++ b/l10n_us_hr_payroll/tests/test_us_wi_wisconsin_payslip_2020.py
@@ -0,0 +1,39 @@
+# 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 TestUsWIPayslip(TestUsPayslip):
+ ###
+ # 2020 Taxes and Rates
+ ###
+ WI_UNEMP_MAX_WAGE = 14000.0
+ WI_UNEMP = 3.05
+ # Calculation based on example https://www.revenue.wi.gov/DOR%20Publications/pb166.pdf
+
+ def _test_sit(self, wage, filing_status, exemption, additional_withholding, exempt, schedule_pay, date_start, expected_withholding):
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=wage,
+ state_id=self.get_us_state('WI'),
+ wi_wt4_sit_filing_status=filing_status,
+ wi_wt4_sit_exemptions=exemption,
+ state_income_tax_additional_withholding=additional_withholding,
+ state_income_tax_exempt=exempt,
+ 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.assertPayrollAlmostEqual(cats.get('EE_US_SIT', 0.0), -expected_withholding)
+
+ def test_2020_taxes_example(self):
+ self._test_er_suta('WI', self.WI_UNEMP, date(2020, 1, 1), wage_base=self.WI_UNEMP_MAX_WAGE)
+ self._test_sit(300, 'single', 1, 0, False, 'weekly', date(2020, 1, 1), 7.21)
+ self._test_sit(700, 'married', 3, 0, False, 'bi-weekly', date(2020, 1, 1), 13.35)
+ self._test_sit(7000, 'single', 1, 10, True, 'bi-weekly', date(2020, 1, 1), 0.00)
+ self._test_sit(10000, 'married', 3, 10, False, 'bi-weekly', date(2020, 1, 1), 633.65)
+ # ((48000 - 26227) * (7.0224 /100) + 1073.55 - 44) / 12
+ self._test_sit(4000, 'single', 2, 0, False, 'monthly', date(2020, 1, 1), 213.21)
diff --git a/l10n_us_hr_payroll/tests/test_us_wv_west_virginia_payslip_2020.py b/l10n_us_hr_payroll/tests/test_us_wv_west_virginia_payslip_2020.py
new file mode 100755
index 00000000..acef111e
--- /dev/null
+++ b/l10n_us_hr_payroll/tests/test_us_wv_west_virginia_payslip_2020.py
@@ -0,0 +1,36 @@
+# 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 TestUsWVPayslip(TestUsPayslip):
+ ###
+ # 2020 Taxes and Rates
+ ###
+ WV_UNEMP_MAX_WAGE = 12000.0
+ WV_UNEMP = 2.7
+ # Calculation based on example https://tax.wv.gov/Documents/TaxForms/it100.1a.pdf
+
+ def _test_sit(self, wage, filing_status, exemption, additional_withholding, schedule_pay, date_start, expected_withholding):
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=wage,
+ state_id=self.get_us_state('WV'),
+ wv_it104_sit_filing_status=filing_status,
+ wv_it104_sit_exemptions=exemption,
+ state_income_tax_additional_withholding=additional_withholding,
+ 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('WV', self.WV_UNEMP, date(2020, 1, 1), wage_base=self.WV_UNEMP_MAX_WAGE)
+ self._test_sit(1250, 'married', 2, 0, 'semi-monthly', date(2020, 1, 1), 44.00)
+ self._test_sit(1300, 'single', 1, 0, 'bi-weekly', date(2020, 1, 1), 46.00)
+ self._test_sit(1300, 'single', 1, 10, 'bi-weekly', date(2020, 1, 1), 56.00)
+ self._test_sit(15000, 'single', 2, 0, 'monthly', date(2020, 1, 1), 860.00)
diff --git a/l10n_us_hr_payroll/tests/test_us_wy_wyoming_payslip_2019.py b/l10n_us_hr_payroll/tests/test_us_wy_wyoming_payslip_2019.py
new file mode 100644
index 00000000..a8fa3df8
--- /dev/null
+++ b/l10n_us_hr_payroll/tests/test_us_wy_wyoming_payslip_2019.py
@@ -0,0 +1,58 @@
+# 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
+
+
+class TestUsWYPayslip(TestUsPayslip):
+
+ # TAXES AND RATES
+ WY_UNEMP_MAX_WAGE = 25400
+ WY_UNEMP = -2.10 / 100.0
+
+ def test_2019_taxes(self):
+ salary = 15000.00
+
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('WY'))
+
+ self._log('2019 Wyoming 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 * self.WY_UNEMP)
+
+ process_payslip(payslip)
+
+ # Make a new payslip, this one will have maximums
+
+ remaining_wy_unemp_wages = self.WY_UNEMP_MAX_WAGE - salary if (self.WY_UNEMP_MAX_WAGE - 2*salary < salary) \
+ else salary
+
+ self._log('2019 Wyoming 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_wy_unemp_wages * self.WY_UNEMP)
+
+ def test_2019_taxes_with_external(self):
+ # Wage is the cap itself, 25400
+ # so salary is equal to self.WY_UNEMP
+ salary = 25400
+
+ employee = self._createEmployee()
+ contract = self._createContract(employee,
+ wage=salary,
+ state_id=self.get_us_state('WY'))
+
+ self._log('2019 Wyoming 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'], salary * self.WY_UNEMP)
diff --git a/l10n_us_hr_payroll/tests/test_us_wy_wyoming_payslip_2020.py b/l10n_us_hr_payroll/tests/test_us_wy_wyoming_payslip_2020.py
new file mode 100644
index 00000000..b6ca4482
--- /dev/null
+++ b/l10n_us_hr_payroll/tests/test_us_wy_wyoming_payslip_2020.py
@@ -0,0 +1,13 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from datetime import date
+from .common import TestUsPayslip
+
+
+class TestUsWYPayslip(TestUsPayslip):
+ # TAXES AND RATES
+ WY_UNEMP_MAX_WAGE = 26400.00
+ WY_UNEMP = 8.5
+
+ def test_2020_taxes(self):
+ self._test_er_suta('WY', self.WY_UNEMP, date(2020, 1, 1), wage_base=self.WY_UNEMP_MAX_WAGE)
diff --git a/l10n_us_hr_payroll/views/res_config_settings_views.xml b/l10n_us_hr_payroll/views/res_config_settings_views.xml
new file mode 100644
index 00000000..3c69b42f
--- /dev/null
+++ b/l10n_us_hr_payroll/views/res_config_settings_views.xml
@@ -0,0 +1,32 @@
+
+
+
+
+ res.config.settings.view.form.inherit
+ res.config.settings
+
+
+
+
+
+
+
Payslip Sum Behavior
+
+ Customize the behavior of what payslips are eligible when summing over date ranges in rules.
+ Generally, "Date To" or "Accounting Date" would be preferred in the United States and anywhere
+ else where the ending date on the payslip is used to calculate wage bases.
+
+
+
+
+
+
+
+
+
+
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 dfe0e301..4b38115f 100644
--- a/l10n_us_hr_payroll/views/us_payroll_config_views.xml
+++ b/l10n_us_hr_payroll/views/us_payroll_config_views.xml
@@ -26,7 +26,6 @@
-
Form 940 - Federal Unemployment
Form 941 / W4 - Federal Income Tax
@@ -39,6 +38,266 @@
+ State Information and Extra
+
+
+
+
+
+
+ Form A4 - State Income Tax
+
+
+
+
+
+
+ Form AR4EC - State Income Tax
+
+
+
+
+
+ Form A-4 - State Income Tax
+
+
+
+
+ Form W-4 - State Income Tax
+
+
+
+
+
+
+ Form W-4 - State Income Tax
+
+
+
+
+ Form DE W-4 - State Income Tax
+
+
+
+
+
+ Form CT-W4 - State Income Tax
+
+
+
+
+ No additional fields.
+
+
+ Form G-4 - State Income Tax
+
+
+
+
+
+
+
+ Form HI HW-4 - State Income Tax
+
+
+
+
+
+ Form IA W-4 - State Income Tax
+
+
+
+
+
+ Form ID W-4 - State Income Tax
+
+
+
+
+ Form IL-W-4 - State Income Tax
+
+
+
+
+
+ Form IN WH-4 - State Income Tax
+
+
+
+
+
+ Form KS K-4 - State Income Tax
+
+
+
+
+
+
+ No additional fields.
+
+
+
+
+ Form LA L-4 - State Income Tax
+
+
+
+
+
+
+ Form W-4ME - State Income Tax
+
+
+
+
+
+
+ Form MI-W4 - State Income Tax
+
+
+
+
+
+ Form W-4MN - State Income Tax
+
+
+
+
+
+ Form MO W-4 - State Income Tax
+
+
+
+
+
+ Form 89-350 - State Income Tax
+
+
+
+
+
+ Form MT-4 - State Income Tax
+
+
+
+
+
+ Form NC-4 - State Income Tax
+
+
+
+
+
+ Form ND W-4 - State Income Tax
+
+
+
+
+ Form NC-4 - State Income Tax
+
+
+
+
+
+
+ No additional fields.
+
+
+ Form NJ-W4 - State Income Tax
+
+
+
+
+
+
+ Form NM W-4 - State Income Tax
+
+
+
+ No additional fields.
+
+
+ Form NY IT-2104 - State Income Tax
+
+
+
+
+
+ Form IT-4 - State Income Tax
+
+
+
+
+
+ Form OK-W-4 - State Income Tax
+
+
+
+
+
+
+
+
+
+
+ Form RI W-4 - State Income Tax
+
+
+
+
+
+ Form SC W-4 - State Income Tax
+
+
+
+
+
+ No additional fields.
+
+
+ No additional fields.
+
+
+ No additional fields.
+
+
+ Form UT W-4 - State Income Tax
+
+
+
+
+ Form VT W-4VT - State Income Tax
+
+
+
+
+
+
+ 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.
+
+
+ Form WV/IT-104 - State Income Tax
+
+
+
+
+
+ Form WT-4 - State Income Tax
+
+
+
+
+
+
+ No additional fields.