From c537b2f35fffa39411fe6348fc1673f6f1b5cddf Mon Sep 17 00:00:00 2001 From: Jared Kipe Date: Mon, 7 Jan 2019 14:40:42 -0800 Subject: [PATCH] Update `l10n_us_ca_hr_payroll` for 2019 Rates and Limits. --- l10n_us_ca_hr_payroll/__init__.py | 2 +- l10n_us_ca_hr_payroll/__manifest__.py | 7 +- l10n_us_ca_hr_payroll/data/base.xml | 47 +- l10n_us_ca_hr_payroll/data/rates.xml | 46 + l10n_us_ca_hr_payroll/data/rules.xml | 1005 +++++++++++++++++ l10n_us_ca_hr_payroll/data/rules_2018.xml | 576 ---------- l10n_us_ca_hr_payroll/hr_payroll.py | 56 - l10n_us_ca_hr_payroll/models/__init__.py | 1 + l10n_us_ca_hr_payroll/models/hr_payroll.py | 16 + l10n_us_ca_hr_payroll/tests/__init__.py | 3 +- .../tests/test_us_ca_payslip_2018.py | 148 +-- .../tests/test_us_ca_payslip_2019.py | 425 +++++++ .../hr_payroll_views.xml} | 13 - 13 files changed, 1567 insertions(+), 778 deletions(-) create mode 100755 l10n_us_ca_hr_payroll/data/rates.xml create mode 100755 l10n_us_ca_hr_payroll/data/rules.xml delete mode 100755 l10n_us_ca_hr_payroll/data/rules_2018.xml delete mode 100755 l10n_us_ca_hr_payroll/hr_payroll.py create mode 100644 l10n_us_ca_hr_payroll/models/__init__.py create mode 100755 l10n_us_ca_hr_payroll/models/hr_payroll.py create mode 100755 l10n_us_ca_hr_payroll/tests/test_us_ca_payslip_2019.py rename l10n_us_ca_hr_payroll/{hr_payroll_view.xml => views/hr_payroll_views.xml} (62%) diff --git a/l10n_us_ca_hr_payroll/__init__.py b/l10n_us_ca_hr_payroll/__init__.py index 1027e233..0650744f 100755 --- a/l10n_us_ca_hr_payroll/__init__.py +++ b/l10n_us_ca_hr_payroll/__init__.py @@ -1 +1 @@ -from . import hr_payroll \ No newline at end of file +from . import models diff --git a/l10n_us_ca_hr_payroll/__manifest__.py b/l10n_us_ca_hr_payroll/__manifest__.py index b712cbae..7486370d 100755 --- a/l10n_us_ca_hr_payroll/__manifest__.py +++ b/l10n_us_ca_hr_payroll/__manifest__.py @@ -4,7 +4,7 @@ 'license': 'AGPL-3', 'category': 'Localization', 'depends': ['l10n_us_hr_payroll'], - 'version': '11.0.2018.1.0', + 'version': '11.0.2019.0.0', 'description': """ USA::California Payroll Rules. ============================== @@ -22,9 +22,10 @@ USA::California Payroll Rules. 'auto_install': False, 'website': 'https://hibou.io/', 'data': [ - 'hr_payroll_view.xml', + 'views/hr_payroll_views.xml', 'data/base.xml', - 'data/rules_2018.xml', + 'data/rates.xml', + 'data/rules.xml', 'data/final.xml', ], 'installable': True diff --git a/l10n_us_ca_hr_payroll/data/base.xml b/l10n_us_ca_hr_payroll/data/base.xml index 6ec2431a..ea585d66 100755 --- a/l10n_us_ca_hr_payroll/data/base.xml +++ b/l10n_us_ca_hr_payroll/data/base.xml @@ -32,62 +32,43 @@ - - - - - - - - - - - - - - - - - - - - California Unemployment Insurance Tax - Wages - CA_UIT_WAGES + Wage: US-CA Unemployment Insurance + WAGE_US_CA_UNEMP - California Unemployment Insurance Tax - CA_UIT + ER: US-CA Unemployment Insurance + ER_US_CA_UNEMP - California Employee Training Tax - Wages - CA_ETT_WAGES + Wage: US-CA Employee Training Tax + WAGE_US_CA_ETT - California Employee Training Tax - CA_ETT + ER: US-CA Employee Training Tax + ER_US_CA_ETT - California State Disability Insurance - Wages - CA_SDI_WAGES + Wage: US-CA State Disability Insurance + WAGE_US_CA_SDI - California State Disability Insurance - CA_SDI + EE: US-CA State Disability Insurance + EE_US_CA_SDI - California Income Withholding - CA_WITHHOLD + EE: US-CA Income Withholding + EE_US_CA_INC_WITHHOLD diff --git a/l10n_us_ca_hr_payroll/data/rates.xml b/l10n_us_ca_hr_payroll/data/rates.xml new file mode 100755 index 00000000..86dd0f2c --- /dev/null +++ b/l10n_us_ca_hr_payroll/data/rates.xml @@ -0,0 +1,46 @@ + + + + US California Unemployment + US_CA_UNEMP + 2.6 + 2018-01-01 + + + + US California Employment Training Tax + US_CA_ETT + 0.1 + 2018-01-01 + + + + US California State Disability Insurance + US_CA_SDI + 1.0 + 2018-01-01 + + + + + US California Unemployment + US_CA_UNEMP + 3.4 + 2019-01-01 + + + + US California Employment Training Tax + US_CA_ETT + 0.1 + 2019-01-01 + + + + US California State Disability Insurance + US_CA_SDI + 1.0 + 2019-01-01 + + + diff --git a/l10n_us_ca_hr_payroll/data/rules.xml b/l10n_us_ca_hr_payroll/data/rules.xml new file mode 100755 index 00000000..9a31434f --- /dev/null +++ b/l10n_us_ca_hr_payroll/data/rules.xml @@ -0,0 +1,1005 @@ + + + + + + + + + + Wage: US-CA Unemployment Insurance + WAGE_US_CA_UNEMP + python + result = (contract.futa_type != contract.FUTA_TYPE_BASIC) + code + +### +rate = payslip.dict.get_rate('US_CA_UNEMP') +year = int(payslip.dict.date_from[:4]) +ytd = payslip.sum('WAGE_US_CA_UNEMP', str(year) + '-01-01', str(year+1) + '-01-01') +ytd += contract.external_wages +remaining = rate.wage_limit_year - ytd +if remaining <= 0.0: + result = 0 +elif remaining < categories.BASIC: + result = remaining +else: + result = categories.BASIC + + + + + + + + ER: US-CA Unemployment Insurance Tax + ER_US_CA_UNEMP + python + result = (contract.futa_type != contract.FUTA_TYPE_BASIC) + code + +rate = payslip.dict.get_rate('US_CA_UNEMP') +result_rate = -rate.rate +result = categories.WAGE_US_CA_UNEMP + +# result_rate of 0 implies 100% due to bug +if result_rate == 0.0: + result = 0.0 + + + + + + + + + + Wage: US-CA Employment Training Tax + WAGE_US_CA_ETT + python + result = (contract.futa_type != contract.FUTA_TYPE_BASIC) + code + +### +rate = payslip.dict.get_rate('US_CA_ETT') +year = int(payslip.dict.date_from[:4]) +ytd = payslip.sum('WAGE_US_CA_ETT', str(year) + '-01-01', str(year+1) + '-01-01') +ytd += contract.external_wages +remaining = rate.wage_limit_year - ytd +if remaining <= 0.0: + result = 0 +elif remaining < categories.BASIC: + result = remaining +else: + result = categories.BASIC + + + + + + + + ER: US-CA Employee Training Tax + ER_US_CA_ETT + python + result = (contract.futa_type != contract.FUTA_TYPE_BASIC) + code + +rate = payslip.dict.get_rate('US_CA_ETT') +result_rate = -rate.rate +result = categories.WAGE_US_CA_ETT + +# result_rate of 0 implies 100% due to bug +if result_rate == 0.0: + result = 0.0 + + + + + + + + + + Wage: US-CA State Disability Insurance + WAGE_US_CA_SDI + python + result = (contract.futa_type != contract.FUTA_TYPE_BASIC) + code + +### +rate = payslip.dict.get_rate('US_CA_SDI') +year = int(payslip.dict.date_from[:4]) +ytd = payslip.sum('WAGE_US_CA_SDI', str(year) + '-01-01', str(year+1) + '-01-01') +ytd += contract.external_wages +remaining = rate.wage_limit_year - ytd +if remaining <= 0.0: + result = 0 +elif remaining < categories.BASIC: + result = remaining +else: + result = categories.BASIC + + + + + + + + EE: US-CA State Disability Insurance + EE_US_CA_SDI + python + result = (contract.futa_type != contract.FUTA_TYPE_BASIC) + code + +rate = payslip.dict.get_rate('US_CA_SDI') +result_rate = -rate.rate +result = categories.WAGE_US_CA_SDI + +# result_rate of 0 implies 100% due to bug +if result_rate == 0.0: + result = 0.0 + + + + + + + + + + EE: US-CA Income Withholding + EE_US_CA_INC_WITHHOLD + python + result = True + code + +year = int(payslip.dict.date_from[:4]) +wages = categories.GROSS +allowances = contract.ca_de4_allowances +additional_allowances = contract.ca_additional_allowances +schedule_pay = contract.schedule_pay +filing_status = contract.ca_de4_filing_status +low_income = False + +if year == 2018: + # Tables are found in http://www.edd.ca.gov/pdf_pub_ctr/18methb.pdf + # First check low income exemption table (Step 1) + low_income_exemption_table = { + 'weekly': (270, 270, 540, 540), + 'bi-weekly': (540, 540, 1081, 1081), + 'semi-monthly': (585, 585, 1171, 1171), + 'monthly': (1171, 1171, 2341, 2341), + 'quarterly': (3512, 3512, 7024, 7024), + 'semi-annual': (7024, 7024, 14048, 14048), + 'annually': (14048, 14048, 28095, 28095), + } + + if filing_status == 'head_household': + _, _, _, income = low_income_exemption_table[schedule_pay] + if wages <= income: + result = 0 + low_income = True + elif filing_status == 'married': + if allowances >= 2: + _, _, income, _ = low_income_exemption_table[schedule_pay] + if wages <= income: + result = 0 + low_income = True + else: + _, income, _, _ = low_income_exemption_table[schedule_pay] + if wages <= income: + result = 0 + low_income = True + else: + income, _, _, _ = low_income_exemption_table[schedule_pay] + if wages <= income: + result = 0 + low_income = True + + if not low_income: + # Estimated deduction table (Step 2) + estimated_deduction_table = { + '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), + } + + allowance_index = additional_allowances - 1 + if additional_allowances > 10: + deduction = (estimated_deduction_table[schedule_pay][0]) * additional_allowances + wages -= deduction + elif additional_allowances > 0: + deduction = estimated_deduction_table[schedule_pay][allowance_index] + wages -= deduction + + # Standard deduction table (Step 3) + standard_deduction_table = { + 'weekly': (81, 81, 163, 163), + 'bi-weekly': (163, 163, 326, 326), + 'semi-monthly': (177, 177, 353, 353), + 'monthly': (353, 353, 706, 706), + 'quarterly': (1059, 1059, 2118, 2118), + 'semi-annual': (2118, 2118, 4236, 4236), + 'annually': (4236, 4236, 8472, 8472), + } + + if filing_status == 'head_household': + _, _, _, deduction = standard_deduction_table[schedule_pay] + wages -= deduction + elif filing_status == 'married': + if allowances >= 2: + _, _, deduction, _ = standard_deduction_table[schedule_pay] + wages -= deduction + else: + _, deduction, _, _ = standard_deduction_table[schedule_pay] + wages -= deduction + else: + deduction, _, _, _ = standard_deduction_table[schedule_pay] + wages -= deduction + + # Tax Rate Tables (Step 4) + #### WEEKLY #### + if schedule_pay == 'weekly': + if filing_status == 'head_household' and wages > 0: + tax_rate_table = [ + (316, 0.011, 0.0), + (750, 0.022, 3.48), + (967, 0.044, 13.03), + (1196, 0.066, 22.58), + (1413, 0.088, 37.69), + (7212, 0.1023, 56.79), + (8654, 0.1133, 650.03), + (14423, 0.1243, 813.41), + (19231, 0.1353, 1530.50), + (float('inf'), 0.1463, 2181.02), + ] + + elif filing_status == 'married': + tax_rate_table = [ + (316, 0.011, 0.0), + (750, 0.022, 3.48), + (1184, 0.044, 13.03), + (1642, 0.066, 32.13), + (2076, 0.088, 62.36), + (10606, 0.1023, 100.55), + (12726, 0.1133, 973.17), + (19231, 0.1243, 1213.37), + (21210, 0.1353, 2021.94), + (float('inf'), 0.1463, 2289.70), + ] + + else: + tax_rate_table = [ + (158, 0.011, 0.0), + (375, 0.022, 1.74), + (592, 0.044, 6.51), + (821, 0.066, 16.06), + (1038, 0.088, 31.17), + (5303, 0.1023, 50.27), + (6363, 0.1133, 486.58), + (10605, 0.1243, 606.68), + (19231, 0.1353, 1133.96), + (float('inf'), 0.1463, 2301.06), + ] + + #### BI-WEEKLY #### + elif schedule_pay == 'bi-weekly': + if filing_status == 'head_household': + tax_rate_table = [ + (632, 0.011, 0.0), + (1500, 0.022, 6.95), + (1934, 0.044, 26.05), + (2392, 0.066, 45.15), + (2826, 0.088, 75.38), + (14424, 0.1023, 113.57), + (17308, 0.1133, 1300.05), + (28846, 0.1243, 1626.81), + (38462, 0.1353, 3060.98), + (float('inf'), 0.1463, 4362.02), + ] + + elif filing_status == 'married': + tax_rate_table = [ + (632, 0.011, 0.0), + (1500, 0.022, 6.95), + (2368, 0.044, 26.05), + (3284, 0.066, 64.24), + (4152, 0.088, 124.70), + (21212, 0.1023, 201.08), + (25452, 0.1133, 1946.32), + (38462, 0.1243, 2426.71), + (42420, 0.1353, 4043.85), + (float('inf'), 0.1463, 4579.37), + ] + + else: + tax_rate_table = [ + (316, 0.011, 0.0), + (750, 0.022, 3.48), + (1184, 0.044, 13.03), + (1642, 0.066, 32.13), + (2076, 0.088, 62.36), + (10606, 0.1023, 100.55), + (12726, 0.1133, 973.17), + (21210, 0.1243, 1213.37), + (38462, 0.1353, 2267.93), + (float('inf'), 0.1463, 4602.13), + ] + + #### SEMI-MONTHLY #### + elif schedule_pay == 'semi-monthly': + if filing_status == 'head_household': + tax_rate_table = [ + (686, 0.011, 0.0), + (1625, 0.022, 7.55), + (2094, 0.044, 28.21), + (2592, 0.066, 48.85), + (3062, 0.088, 81.72), + (15625, 0.1023, 123.08), + (18750, 0.1133, 1408.27), + (31250, 0.1243, 1762.33), + (41667, 0.1353, 3316.08), + (float('inf'), 0.1463, 4725.50), + ] + + elif filing_status == 'married': + tax_rate_table = [ + (686, 0.011, 0.0), + (1624, 0.022, 7.55), + (2564, 0.044, 28.19), + (3560, 0.066, 69.55), + (4498, 0.088, 135.29), + (22978, 0.1023, 217.83), + (27574, 0.1133, 2108.33), + (41667, 0.1243, 2629.06), + (45956, 0.1353, 4380.82), + (float('inf'), 0.1463, 4961.12), + ] + + else: + tax_rate_table = [ + (343, 0.011, 0.0), + (812, 0.022, 3.77), + (1282, 0.044, 14.09), + (1780, 0.066, 34.77), + (2249, 0.088, 67.64), + (11489, 0.1023, 108.91), + (13787, 0.1133, 1054.16), + (22978, 0.1243, 1314.52), + (41667, 0.1353, 2456.96), + (float('inf'), 0.1463, 4985.58), + ] + + #### MONTHLY #### + elif schedule_pay == 'monthly': + if filing_status == 'head_household': + tax_rate_table = [ + (1372, 0.011, 0.0), + (3250, 0.022, 15.09), + (4188, 0.044, 56.41), + (5184, 0.066, 97.68), + (6124, 0.088, 163.42), + (31250, 0.1023, 246.148), + (37500, 0.1133, 2816.53), + (62500, 0.1243, 3524.66), + (83334, 0.1353, 6632.16), + (float('inf'), 0.1463, 9451.00), + ] + + elif filing_status == 'married': + tax_rate_table = [ + (1372, 0.011, 0.0), + (3248, 0.022, 15.09), + (5128, 0.044, 56.36), + (7120, 0.066, 139.08), + (8996, 0.088, 270.55), + (45956, 0.1023, 435.64), + (55148, 0.1133, 4216.65), + (83334, 0.1243, 5258.10), + (91912, 0.1353, 8761.62), + (float('inf'), 0.1463, 9922.22), + ] + + else: + tax_rate_table = [ + (686, 0.011, 0.0), + (1624, 0.022, 7.55), + (2564, 0.044, 28.19), + (3560, 0.066, 69.55), + (4498, 0.088, 135.29), + (22978, 0.1023, 217.83), + (27574, 0.1133, 2108.33), + (45956, 0.1243, 2629.06), + (83334, 0.1353, 4913.94), + (float('inf'), 0.1463, 9971.18), + ] + + #### QUARTERLY #### + elif schedule_pay == 'quarterly': + if filing_status == 'head_household': + tax_rate_table = [ + (4114, 0.011, 0.0), + (9748, 0.022, 45.25), + (12566, 0.044, 169.20), + (15552, 0.066, 293.19), + (18369, 0.088, 490.27), + (93751, 0.1023, 738.17), + (112501, 0.1133, 8449.75), + (187501, 0.1243, 10574.13), + (250000, 0.1353, 19896.63), + (float('inf'), 0.1463, 28352.74), + ] + + elif filing_status == 'married': + tax_rate_table = [ + (4112, 0.011, 0.0), + (9748, 0.022, 45.23), + (15384, 0.044, 169.22), + (21356, 0.066, 417.20), + (26990, 0.088, 811.35), + (137870, 0.1023, 1307.14), + (165442, 0.1133, 12650.16), + (250000, 0.1243, 15774.07), + (275736, 0.1353, 26284.63), + (float('inf'), 0.1463, 29766.71), + ] + + else: + tax_rate_table = [ + (2056, 0.011, 0.0), + (4874, 0.022, 22.62), + (7692, 0.044, 84.62), + (10678, 0.066, 208.61), + (13495, 0.088, 405.69), + (68935, 0.1023, 653.59), + (82721, 0.1133, 6325.10), + (137868, 0.1243, 7887.05), + (250000, 0.1353, 14741.82), + (float('inf'), 0.1463, 29913.28), + ] + + #### SEMI-ANNUAL #### + elif schedule_pay == 'semi-annual': + if filing_status == 'head_household': + tax_rate_table = [ + (8228, 0.011, 0.0), + (19496, 0.022, 90.51), + (25132, 0.044, 338.41), + (31104, 0.066, 586.39), + (36738, 0.088, 980.54), + (187502, 0.1023, 1476.33), + (225002, 0.1133, 16899.49), + (375002, 0.1243, 21148.24), + (500000, 0.1353, 39793.24), + (float('inf'), 0.1463, 56705.47), + ] + + elif filing_status == 'married': + tax_rate_table = [ + (8224, 0.011, 0.0), + (19496, 0.022, 90.46), + (30768, 0.044, 338.44), + (42712, 0.066, 834.41), + (53980, 0.088, 1622.71), + (275740, 0.1023, 2614.29), + (330884, 0.1133, 25300.34), + (500000, 0.1243, 31548.16), + (551472, 0.1353, 52569.28), + (float('inf'), 0.1463, 59533.44), + ] + + else: + tax_rate_table = [ + (4112, 0.011, 0.0), + (9748, 0.022, 45.23), + (15384, 0.044, 169.22), + (21356, 0.066, 417.20), + (26990, 0.088, 811.35), + (137870, 0.1023, 1307.14), + (165442, 0.1133, 12650.16), + (275736, 0.1243, 15774.07), + (500000, 0.1353, 29483.61), + (float('inf'), 0.1463, 59826.53), + ] + + #### ANNUAL #### + elif schedule_pay == 'annually': + if filing_status == 'head_household': + tax_rate_table = [ + (16457, 0.011, 0.0), + (38991, 0.022, 181.03), + (50264, 0.044, 676.78), + (62206, 0.066, 1172.79), + (73477, 0.088, 1960.96), + (375002, 0.1023, 2952.81), + (450003, 0.1133, 33798.82), + (750003, 0.1243, 42296.43), + (1000000, 0.1353, 79586.43), + (float('inf'), 0.1463, 113411.02), + ] + + elif filing_status == 'married': + tax_rate_table = [ + (16446, 0.011, 0.0), + (38990, 0.022, 180.91), + (61538, 0.044, 676.88), + (85422, 0.066, 1668.99), + (107960, 0.088, 3245.33), + (551476, 0.1023, 5228.67), + (661768, 0.1133, 50600.36), + (1000000, 0.1243, 63096.44), + (1102946, 0.1353, 105138.68), + (float('inf'), 0.1463, 119067.26), + ] + + else: + tax_rate_table = [ + (8223, 0.011, 0.0), + (19495, 0.022, 90.45), + (30769, 0.044, 338.43), + (42711, 0.066, 834.49), + (53980, 0.088, 1622.66), + (275738, 0.1023, 2614.33), + (330884, 0.1133, 25300.17), + (551473, 0.1243, 31548.21), + (1000000, 0.1353, 58967.42), + (float('inf'), 0.1463, 119653.12), + ] + + over = 0.0 + tax = 0.0 + for row in tax_rate_table: + if wages <= row[0]: + tax = ((wages - over) * row[1]) + row[2] + break + over = row[0] + + # Exemption allowance table (Step 5) + exemption_allowance_table = { + '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), + } + + allowance_index = allowances - 1 + if allowances > 10: + deduction = (exemption_allowance_table[schedule_pay][0]) * allowances + tax -= deduction + elif allowances > 0: + deduction = exemption_allowance_table[schedule_pay][allowance_index] + tax -= deduction + + result = -tax +else: + # 2019 + # Tables are found in http://www.edd.ca.gov/pdf_pub_ctr/18methb.pdf + # First check low income exemption table (Step 1) + low_income_exemption_table = { + '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), + } + + if filing_status == 'head_household': + _, _, _, income = low_income_exemption_table[schedule_pay] + if wages <= income: + result = 0 + low_income = True + elif filing_status == 'married': + if allowances >= 2: + _, _, income, _ = low_income_exemption_table[schedule_pay] + if wages <= income: + result = 0 + low_income = True + else: + _, income, _, _ = low_income_exemption_table[schedule_pay] + if wages <= income: + result = 0 + low_income = True + else: + income, _, _, _ = low_income_exemption_table[schedule_pay] + if wages <= income: + result = 0 + low_income = True + + if not low_income: + # Estimated deduction table (Step 2) + estimated_deduction_table = { + '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), + } + + allowance_index = additional_allowances - 1 + if additional_allowances > 10: + deduction = (estimated_deduction_table[schedule_pay][0]) * additional_allowances + wages -= deduction + elif additional_allowances > 0: + deduction = estimated_deduction_table[schedule_pay][allowance_index] + wages -= deduction + + # Standard deduction table (Step 3) + standard_deduction_table = { + '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), + } + + if filing_status == 'head_household': + _, _, _, deduction = standard_deduction_table[schedule_pay] + wages -= deduction + elif filing_status == 'married': + if allowances >= 2: + _, _, deduction, _ = standard_deduction_table[schedule_pay] + wages -= deduction + else: + _, deduction, _, _ = standard_deduction_table[schedule_pay] + wages -= deduction + else: + deduction, _, _, _ = standard_deduction_table[schedule_pay] + wages -= deduction + + # Tax Rate Tables (Step 4) + #### WEEKLY #### + if schedule_pay == 'weekly': + if filing_status == 'head_household' and wages > 0: + tax_rate_table = [ + (316, 0.011, 0.0), + (750, 0.022, 3.48), + (967, 0.044, 13.03), + (1196, 0.066, 22.58), + (1413, 0.088, 37.69), + (7212, 0.1023, 56.79), + (8654, 0.1133, 650.03), + (14423, 0.1243, 813.41), + (19231, 0.1353, 1530.50), + (float('inf'), 0.1463, 2181.02), + ] + + elif filing_status == 'married': + tax_rate_table = [ + (316, 0.011, 0.0), + (750, 0.022, 3.48), + (1184, 0.044, 13.03), + (1642, 0.066, 32.13), + (2076, 0.088, 62.36), + (10606, 0.1023, 100.55), + (12726, 0.1133, 973.17), + (19231, 0.1243, 1213.37), + (21210, 0.1353, 2021.94), + (float('inf'), 0.1463, 2289.70), + ] + + else: + tax_rate_table = [ + (158, 0.011, 0.0), + (375, 0.022, 1.74), + (592, 0.044, 6.51), + (821, 0.066, 16.06), + (1038, 0.088, 31.17), + (5303, 0.1023, 50.27), + (6363, 0.1133, 486.58), + (10605, 0.1243, 606.68), + (19231, 0.1353, 1133.96), + (float('inf'), 0.1463, 2301.06), + ] + + #### BI-WEEKLY #### + elif schedule_pay == 'bi-weekly': + if filing_status == 'head_household': + tax_rate_table = [ + (632, 0.011, 0.0), + (1500, 0.022, 6.95), + (1934, 0.044, 26.05), + (2392, 0.066, 45.15), + (2826, 0.088, 75.38), + (14424, 0.1023, 113.57), + (17308, 0.1133, 1300.05), + (28846, 0.1243, 1626.81), + (38462, 0.1353, 3060.98), + (float('inf'), 0.1463, 4362.02), + ] + + elif filing_status == 'married': + tax_rate_table = [ + (632, 0.011, 0.0), + (1500, 0.022, 6.95), + (2368, 0.044, 26.05), + (3284, 0.066, 64.24), + (4152, 0.088, 124.70), + (21212, 0.1023, 201.08), + (25452, 0.1133, 1946.32), + (38462, 0.1243, 2426.71), + (42420, 0.1353, 4043.85), + (float('inf'), 0.1463, 4579.37), + ] + + else: + tax_rate_table = [ + (316, 0.011, 0.0), + (750, 0.022, 3.48), + (1184, 0.044, 13.03), + (1642, 0.066, 32.13), + (2076, 0.088, 62.36), + (10606, 0.1023, 100.55), + (12726, 0.1133, 973.17), + (21210, 0.1243, 1213.37), + (38462, 0.1353, 2267.93), + (float('inf'), 0.1463, 4602.13), + ] + + #### SEMI-MONTHLY #### + elif schedule_pay == 'semi-monthly': + if filing_status == 'head_household': + tax_rate_table = [ + (686, 0.011, 0.0), + (1625, 0.022, 7.55), + (2094, 0.044, 28.21), + (2592, 0.066, 48.85), + (3062, 0.088, 81.72), + (15625, 0.1023, 123.08), + (18750, 0.1133, 1408.27), + (31250, 0.1243, 1762.33), + (41667, 0.1353, 3316.08), + (float('inf'), 0.1463, 4725.50), + ] + + elif filing_status == 'married': + tax_rate_table = [ + (686, 0.011, 0.0), + (1624, 0.022, 7.55), + (2564, 0.044, 28.19), + (3560, 0.066, 69.55), + (4498, 0.088, 135.29), + (22978, 0.1023, 217.83), + (27574, 0.1133, 2108.33), + (41667, 0.1243, 2629.06), + (45956, 0.1353, 4380.82), + (float('inf'), 0.1463, 4961.12), + ] + + else: + tax_rate_table = [ + (343, 0.011, 0.0), + (812, 0.022, 3.77), + (1282, 0.044, 14.09), + (1780, 0.066, 34.77), + (2249, 0.088, 67.64), + (11489, 0.1023, 108.91), + (13787, 0.1133, 1054.16), + (22978, 0.1243, 1314.52), + (41667, 0.1353, 2456.96), + (float('inf'), 0.1463, 4985.58), + ] + + #### MONTHLY #### + elif schedule_pay == 'monthly': + if filing_status == 'head_household': + tax_rate_table = [ + (1372, 0.011, 0.0), + (3250, 0.022, 15.09), + (4188, 0.044, 56.41), + (5184, 0.066, 97.68), + (6124, 0.088, 163.42), + (31250, 0.1023, 246.148), + (37500, 0.1133, 2816.53), + (62500, 0.1243, 3524.66), + (83334, 0.1353, 6632.16), + (float('inf'), 0.1463, 9451.00), + ] + + elif filing_status == 'married': + tax_rate_table = [ + (1372, 0.011, 0.0), + (3248, 0.022, 15.09), + (5128, 0.044, 56.36), + (7120, 0.066, 139.08), + (8996, 0.088, 270.55), + (45956, 0.1023, 435.64), + (55148, 0.1133, 4216.65), + (83334, 0.1243, 5258.10), + (91912, 0.1353, 8761.62), + (float('inf'), 0.1463, 9922.22), + ] + + else: + tax_rate_table = [ + (686, 0.011, 0.0), + (1624, 0.022, 7.55), + (2564, 0.044, 28.19), + (3560, 0.066, 69.55), + (4498, 0.088, 135.29), + (22978, 0.1023, 217.83), + (27574, 0.1133, 2108.33), + (45956, 0.1243, 2629.06), + (83334, 0.1353, 4913.94), + (float('inf'), 0.1463, 9971.18), + ] + + #### QUARTERLY #### + elif schedule_pay == 'quarterly': + if filing_status == 'head_household': + tax_rate_table = [ + (4114, 0.011, 0.0), + (9748, 0.022, 45.25), + (12566, 0.044, 169.20), + (15552, 0.066, 293.19), + (18369, 0.088, 490.27), + (93751, 0.1023, 738.17), + (112501, 0.1133, 8449.75), + (187501, 0.1243, 10574.13), + (250000, 0.1353, 19896.63), + (float('inf'), 0.1463, 28352.74), + ] + + elif filing_status == 'married': + tax_rate_table = [ + (4112, 0.011, 0.0), + (9748, 0.022, 45.23), + (15384, 0.044, 169.22), + (21356, 0.066, 417.20), + (26990, 0.088, 811.35), + (137870, 0.1023, 1307.14), + (165442, 0.1133, 12650.16), + (250000, 0.1243, 15774.07), + (275736, 0.1353, 26284.63), + (float('inf'), 0.1463, 29766.71), + ] + + else: + tax_rate_table = [ + (2056, 0.011, 0.0), + (4874, 0.022, 22.62), + (7692, 0.044, 84.62), + (10678, 0.066, 208.61), + (13495, 0.088, 405.69), + (68935, 0.1023, 653.59), + (82721, 0.1133, 6325.10), + (137868, 0.1243, 7887.05), + (250000, 0.1353, 14741.82), + (float('inf'), 0.1463, 29913.28), + ] + + #### SEMI-ANNUAL #### + elif schedule_pay == 'semi-annual': + if filing_status == 'head_household': + tax_rate_table = [ + (8228, 0.011, 0.0), + (19496, 0.022, 90.51), + (25132, 0.044, 338.41), + (31104, 0.066, 586.39), + (36738, 0.088, 980.54), + (187502, 0.1023, 1476.33), + (225002, 0.1133, 16899.49), + (375002, 0.1243, 21148.24), + (500000, 0.1353, 39793.24), + (float('inf'), 0.1463, 56705.47), + ] + + elif filing_status == 'married': + tax_rate_table = [ + (8224, 0.011, 0.0), + (19496, 0.022, 90.46), + (30768, 0.044, 338.44), + (42712, 0.066, 834.41), + (53980, 0.088, 1622.71), + (275740, 0.1023, 2614.29), + (330884, 0.1133, 25300.34), + (500000, 0.1243, 31548.16), + (551472, 0.1353, 52569.28), + (float('inf'), 0.1463, 59533.44), + ] + + else: + tax_rate_table = [ + (4112, 0.011, 0.0), + (9748, 0.022, 45.23), + (15384, 0.044, 169.22), + (21356, 0.066, 417.20), + (26990, 0.088, 811.35), + (137870, 0.1023, 1307.14), + (165442, 0.1133, 12650.16), + (275736, 0.1243, 15774.07), + (500000, 0.1353, 29483.61), + (float('inf'), 0.1463, 59826.53), + ] + + #### ANNUAL #### + elif schedule_pay == 'annually': + if filing_status == 'head_household': + tax_rate_table = [ + (16457, 0.011, 0.0), + (38991, 0.022, 181.03), + (50264, 0.044, 676.78), + (62206, 0.066, 1172.79), + (73477, 0.088, 1960.96), + (375002, 0.1023, 2952.81), + (450003, 0.1133, 33798.82), + (750003, 0.1243, 42296.43), + (1000000, 0.1353, 79586.43), + (float('inf'), 0.1463, 113411.02), + ] + + elif filing_status == 'married': + tax_rate_table = [ + (16446, 0.011, 0.0), + (38990, 0.022, 180.91), + (61538, 0.044, 676.88), + (85422, 0.066, 1668.99), + (107960, 0.088, 3245.33), + (551476, 0.1023, 5228.67), + (661768, 0.1133, 50600.36), + (1000000, 0.1243, 63096.44), + (1102946, 0.1353, 105138.68), + (float('inf'), 0.1463, 119067.26), + ] + + else: + tax_rate_table = [ + (8223, 0.011, 0.0), + (19495, 0.022, 90.45), + (30769, 0.044, 338.43), + (42711, 0.066, 834.49), + (53980, 0.088, 1622.66), + (275738, 0.1023, 2614.33), + (330884, 0.1133, 25300.17), + (551473, 0.1243, 31548.21), + (1000000, 0.1353, 58967.42), + (float('inf'), 0.1463, 119653.12), + ] + + over = 0.0 + tax = 0.0 + for row in tax_rate_table: + if wages <= row[0]: + tax = ((wages - over) * row[1]) + row[2] + break + over = row[0] + + # Exemption allowance table (Step 5) (Table 4) + exemption_allowance_table = { + '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), + } + + allowance_index = allowances - 1 + if allowances > 10: + deduction = (exemption_allowance_table[schedule_pay][0]) * allowances + tax -= deduction + elif allowances > 0: + deduction = exemption_allowance_table[schedule_pay][allowance_index] + tax -= deduction + + result = -tax + + + + + + diff --git a/l10n_us_ca_hr_payroll/data/rules_2018.xml b/l10n_us_ca_hr_payroll/data/rules_2018.xml deleted file mode 100755 index 0cd42d36..00000000 --- a/l10n_us_ca_hr_payroll/data/rules_2018.xml +++ /dev/null @@ -1,576 +0,0 @@ - - - - - - - - - - California Unemployment Insurance Tax - Wages (2018) - CA_UIT_WAGES_2018 - python - result = (payslip.date_to[:4] == '2018') - code - -### -ytd = payslip.sum('CA_UIT_WAGES_2018', '2018-01-01', '2019-01-01') -ytd += contract.external_wages -remaining = 7000.0 - ytd -if remaining <= 0.0: - result = 0 -elif remaining < categories.BASIC: - result = remaining -else: - result = categories.BASIC - - - - - - - - California Unemployment Insurance Tax(2018) - CA_UIT_2018 - python - result = (payslip.date_to[:4] == '2018') - code - -result_rate = -contract.ca_uit_rate(2018) -result = categories.CA_UIT_WAGES - -# result_rate of 0 implies 100% due to bug -if result_rate == 0.0: - result = 0.0 - - - - - - - - - - California Employment Training Tax - Wages (2018) - CA_ETT_WAGES_2018 - python - result = (payslip.date_to[:4] == '2018') - code - -### -ytd = payslip.sum('CA_ETT_WAGES_2018', '2018-01-01', '2019-01-01') -ytd += contract.external_wages -remaining = 7000.0 - ytd -if remaining <= 0.0: - result = 0 -elif remaining < categories.BASIC: - result = remaining -else: - result = categories.BASIC - - - - - - - - California Employee Training Tax(2018) - CA_ETT_2018 - python - result = (payslip.date_to[:4] == '2018') - code - -result_rate = -contract.ca_ett_rate(2018) -result = categories.CA_ETT_WAGES - -# result_rate of 0 implies 100% due to bug -if result_rate == 0.0: - result = 0.0 - - - - - - - - - - California State Disability Insurance Tax - Wages (2018) - CA_SDI_WAGES_2018 - python - result = (payslip.date_to[:4] == '2018') - code - -### -ytd = payslip.sum('CA_SDI_WAGES_2018', '2018-01-01', '2019-01-01') -ytd += contract.external_wages -remaining = 114967.0 - ytd -if remaining <= 0.0: - result = 0 -elif remaining < categories.BASIC: - result = remaining -else: - result = categories.BASIC - - - - - - - - California State Disability Insurance(2018) - CA_SDI_2018 - python - result = (payslip.date_to[:4] == '2018') - code - -result_rate = -contract.ca_sdi_rate(2018) -result = categories.CA_SDI_WAGES - -# result_rate of 0 implies 100% due to bug -if result_rate == 0.0: - result = 0.0 - - - - - - - - - - California Income Withholding - CA_INC_WITHHOLD_2018 - python - result = (payslip.date_to[:4] == '2018') - code - -wages = categories.GROSS -allowances = contract.ca_de4_allowances -additional_allowances = contract.ca_additional_allowances -schedule_pay = contract.schedule_pay -filing_status = contract.ca_de4_filing_status -low_income = False - -# Tables are found in http://www.edd.ca.gov/pdf_pub_ctr/18methb.pdf -# First check low income exemption table (Step 1) -low_income_exemption_table = { -'weekly': (270, 270, 540, 540), -'bi-weekly': (540, 540, 1081, 1081), -'semi-monthly': (585, 585, 1171, 1171), -'monthly': (1171, 1171, 2341, 2341), -'quarterly': (3512, 3512, 7024, 7024), -'semi-annual': (7024, 7024, 14048, 14048), -'annually': (14048, 14048, 28095, 28095), -} - -if filing_status == 'head_household': - _, _, _, income = low_income_exemption_table[schedule_pay] - if wages <= income: - result = 0 - low_income = True -elif filing_status == 'married': - if allowances >= 2: - _, _, income, _ = low_income_exemption_table[schedule_pay] - if wages <= income: - result = 0 - low_income = True - else: - _, income, _, _ = low_income_exemption_table[schedule_pay] - if wages <= income: - result = 0 - low_income = True -else: - income, _, _, _ = low_income_exemption_table[schedule_pay] - if wages <= income: - result = 0 - low_income = True - -if not low_income: - # Estimated deduction table (Step 2) - estimated_deduction_table = { - '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), - } - - allowance_index = additional_allowances - 1 - if additional_allowances > 10: - deduction = (estimated_deduction_table[schedule_pay][0]) * additional_allowances - wages -= deduction - elif additional_allowances > 0: - deduction = estimated_deduction_table[schedule_pay][allowance_index] - wages -= deduction - - # Standard deduction table (Step 3) - standard_deduction_table = { - 'weekly': (81, 81, 163, 163), - 'bi-weekly': (163, 163, 326, 326), - 'semi-monthly': (177, 177, 353, 353), - 'monthly': (353, 353, 706, 706), - 'quarterly': (1059, 1059, 2118, 2118), - 'semi-annual': (2118, 2118, 4236, 4236), - 'annually': (4236, 4236, 8472, 8472), - } - - if filing_status == 'head_household': - _, _, _, deduction = standard_deduction_table[schedule_pay] - wages -= deduction - elif filing_status == 'married': - if allowances >= 2: - _, _, deduction, _ = standard_deduction_table[schedule_pay] - wages -= deduction - else: - _, deduction, _, _ = standard_deduction_table[schedule_pay] - wages -= deduction - else: - deduction, _, _, _ = standard_deduction_table[schedule_pay] - wages -= deduction - - # Tax Rate Tables (Step 4) - #### WEEKLY #### - if schedule_pay == 'weekly': - if filing_status == 'head_household' and wages > 0: - tax_rate_table = [ - (316, 0.011, 0.0), - (750, 0.022, 3.48), - (967, 0.044, 13.03), - (1196, 0.066, 22.58), - (1413, 0.088, 37.69), - (7212, 0.1023, 56.79), - (8654, 0.1133, 650.03), - (14423, 0.1243, 813.41), - (19231, 0.1353, 1530.50), - (float('inf'), 0.1463, 2181.02), - ] - - elif filing_status == 'married': - tax_rate_table = [ - (316, 0.011, 0.0), - (750, 0.022, 3.48), - (1184, 0.044, 13.03), - (1642, 0.066, 32.13), - (2076, 0.088, 62.36), - (10606, 0.1023, 100.55), - (12726, 0.1133, 973.17), - (19231, 0.1243, 1213.37), - (21210, 0.1353, 2021.94), - (float('inf'), 0.1463, 2289.70), - ] - - else: - tax_rate_table = [ - (158, 0.011, 0.0), - (375, 0.022, 1.74), - (592, 0.044, 6.51), - (821, 0.066, 16.06), - (1038, 0.088, 31.17), - (5303, 0.1023, 50.27), - (6363, 0.1133, 486.58), - (10605, 0.1243, 606.68), - (19231, 0.1353, 1133.96), - (float('inf'), 0.1463, 2301.06), - ] - - #### BI-WEEKLY #### - elif schedule_pay == 'bi-weekly': - if filing_status == 'head_household': - tax_rate_table = [ - (632, 0.011, 0.0), - (1500, 0.022, 6.95), - (1934, 0.044, 26.05), - (2392, 0.066, 45.15), - (2826, 0.088, 75.38), - (14424, 0.1023, 113.57), - (17308, 0.1133, 1300.05), - (28846, 0.1243, 1626.81), - (38462, 0.1353, 3060.98), - (float('inf'), 0.1463, 4362.02), - ] - - elif filing_status == 'married': - tax_rate_table = [ - (632, 0.011, 0.0), - (1500, 0.022, 6.95), - (2368, 0.044, 26.05), - (3284, 0.066, 64.24), - (4152, 0.088, 124.70), - (21212, 0.1023, 201.08), - (25452, 0.1133, 1946.32), - (38462, 0.1243, 2426.71), - (42420, 0.1353, 4043.85), - (float('inf'), 0.1463, 4579.37), - ] - - else: - tax_rate_table = [ - (316, 0.011, 0.0), - (750, 0.022, 3.48), - (1184, 0.044, 13.03), - (1642, 0.066, 32.13), - (2076, 0.088, 62.36), - (10606, 0.1023, 100.55), - (12726, 0.1133, 973.17), - (21210, 0.1243, 1213.37), - (38462, 0.1353, 2267.93), - (float('inf'), 0.1463, 4602.13), - ] - - #### SEMI-MONTHLY #### - elif schedule_pay == 'semi-monthly': - if filing_status == 'head_household': - tax_rate_table = [ - (686, 0.011, 0.0), - (1625, 0.022, 7.55), - (2094, 0.044, 28.21), - (2592, 0.066, 48.85), - (3062, 0.088, 81.72), - (15625, 0.1023, 123.08), - (18750, 0.1133, 1408.27), - (31250, 0.1243, 1762.33), - (41667, 0.1353, 3316.08), - (float('inf'), 0.1463, 4725.50), - ] - - elif filing_status == 'married': - tax_rate_table = [ - (686, 0.011, 0.0), - (1624, 0.022, 7.55), - (2564, 0.044, 28.19), - (3560, 0.066, 69.55), - (4498, 0.088, 135.29), - (22978, 0.1023, 217.83), - (27574, 0.1133, 2108.33), - (41667, 0.1243, 2629.06), - (45956, 0.1353, 4380.82), - (float('inf'), 0.1463, 4961.12), - ] - - else: - tax_rate_table = [ - (343, 0.011, 0.0), - (812, 0.022, 3.77), - (1282, 0.044, 14.09), - (1780, 0.066, 34.77), - (2249, 0.088, 67.64), - (11489, 0.1023, 108.91), - (13787, 0.1133, 1054.16), - (22978, 0.1243, 1314.52), - (41667, 0.1353, 2456.96), - (float('inf'), 0.1463, 4985.58), - ] - - #### MONTHLY #### - elif schedule_pay == 'monthly': - if filing_status == 'head_household': - tax_rate_table = [ - (1372, 0.011, 0.0), - (3250, 0.022, 15.09), - (4188, 0.044, 56.41), - (5184, 0.066, 97.68), - (6124, 0.088, 163.42), - (31250, 0.1023, 246.148), - (37500, 0.1133, 2816.53), - (62500, 0.1243, 3524.66), - (83334, 0.1353, 6632.16), - (float('inf'), 0.1463, 9451.00), - ] - - elif filing_status == 'married': - tax_rate_table = [ - (1372, 0.011, 0.0), - (3248, 0.022, 15.09), - (5128, 0.044, 56.36), - (7120, 0.066, 139.08), - (8996, 0.088, 270.55), - (45956, 0.1023, 435.64), - (55148, 0.1133, 4216.65), - (83334, 0.1243, 5258.10), - (91912, 0.1353, 8761.62), - (float('inf'), 0.1463, 9922.22), - ] - - else: - tax_rate_table = [ - (686, 0.011, 0.0), - (1624, 0.022, 7.55), - (2564, 0.044, 28.19), - (3560, 0.066, 69.55), - (4498, 0.088, 135.29), - (22978, 0.1023, 217.83), - (27574, 0.1133, 2108.33), - (45956, 0.1243, 2629.06), - (83334, 0.1353, 4913.94), - (float('inf'), 0.1463, 9971.18), - ] - - #### QUARTERLY #### - elif schedule_pay == 'quarterly': - if filing_status == 'head_household': - tax_rate_table = [ - (4114, 0.011, 0.0), - (9748, 0.022, 45.25), - (12566, 0.044, 169.20), - (15552, 0.066, 293.19), - (18369, 0.088, 490.27), - (93751, 0.1023, 738.17), - (112501, 0.1133, 8449.75), - (187501, 0.1243, 10574.13), - (250000, 0.1353, 19896.63), - (float('inf'), 0.1463, 28352.74), - ] - - elif filing_status == 'married': - tax_rate_table = [ - (4112, 0.011, 0.0), - (9748, 0.022, 45.23), - (15384, 0.044, 169.22), - (21356, 0.066, 417.20), - (26990, 0.088, 811.35), - (137870, 0.1023, 1307.14), - (165442, 0.1133, 12650.16), - (250000, 0.1243, 15774.07), - (275736, 0.1353, 26284.63), - (float('inf'), 0.1463, 29766.71), - ] - - else: - tax_rate_table = [ - (2056, 0.011, 0.0), - (4874, 0.022, 22.62), - (7692, 0.044, 84.62), - (10678, 0.066, 208.61), - (13495, 0.088, 405.69), - (68935, 0.1023, 653.59), - (82721, 0.1133, 6325.10), - (137868, 0.1243, 7887.05), - (250000, 0.1353, 14741.82), - (float('inf'), 0.1463, 29913.28), - ] - - #### SEMI-ANNUAL #### - elif schedule_pay == 'semi-annual': - if filing_status == 'head_household': - tax_rate_table = [ - (8228, 0.011, 0.0), - (19496, 0.022, 90.51), - (25132, 0.044, 338.41), - (31104, 0.066, 586.39), - (36738, 0.088, 980.54), - (187502, 0.1023, 1476.33), - (225002, 0.1133, 16899.49), - (375002, 0.1243, 21148.24), - (500000, 0.1353, 39793.24), - (float('inf'), 0.1463, 56705.47), - ] - - elif filing_status == 'married': - tax_rate_table = [ - (8224, 0.011, 0.0), - (19496, 0.022, 90.46), - (30768, 0.044, 338.44), - (42712, 0.066, 834.41), - (53980, 0.088, 1622.71), - (275740, 0.1023, 2614.29), - (330884, 0.1133, 25300.34), - (500000, 0.1243, 31548.16), - (551472, 0.1353, 52569.28), - (float('inf'), 0.1463, 59533.44), - ] - - else: - tax_rate_table = [ - (4112, 0.011, 0.0), - (9748, 0.022, 45.23), - (15384, 0.044, 169.22), - (21356, 0.066, 417.20), - (26990, 0.088, 811.35), - (137870, 0.1023, 1307.14), - (165442, 0.1133, 12650.16), - (275736, 0.1243, 15774.07), - (500000, 0.1353, 29483.61), - (float('inf'), 0.1463, 59826.53), - ] - - #### ANNUAL #### - elif schedule_pay == 'annually': - if filing_status == 'head_household': - tax_rate_table = [ - (16457, 0.011, 0.0), - (38991, 0.022, 181.03), - (50264, 0.044, 676.78), - (62206, 0.066, 1172.79), - (73477, 0.088, 1960.96), - (375002, 0.1023, 2952.81), - (450003, 0.1133, 33798.82), - (750003, 0.1243, 42296.43), - (1000000, 0.1353, 79586.43), - (float('inf'), 0.1463, 113411.02), - ] - - elif filing_status == 'married': - tax_rate_table = [ - (16446, 0.011, 0.0), - (38990, 0.022, 180.91), - (61538, 0.044, 676.88), - (85422, 0.066, 1668.99), - (107960, 0.088, 3245.33), - (551476, 0.1023, 5228.67), - (661768, 0.1133, 50600.36), - (1000000, 0.1243, 63096.44), - (1102946, 0.1353, 105138.68), - (float('inf'), 0.1463, 119067.26), - ] - - else: - tax_rate_table = [ - (8223, 0.011, 0.0), - (19495, 0.022, 90.45), - (30769, 0.044, 338.43), - (42711, 0.066, 834.49), - (53980, 0.088, 1622.66), - (275738, 0.1023, 2614.33), - (330884, 0.1133, 25300.17), - (551473, 0.1243, 31548.21), - (1000000, 0.1353, 58967.42), - (float('inf'), 0.1463, 119653.12), - ] - - over = 0.0 - tax = 0.0 - for row in tax_rate_table: - if wages <= row[0]: - tax = ((wages - over) * row[1]) + row[2] - break - over = row[0] - - # Exemption allowance table (Step 5) - exemption_allowance_table = { - '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), - } - - allowance_index = allowances - 1 - if allowances > 10: - deduction = (exemption_allowance_table[schedule_pay][0]) * allowances - tax -= deduction - elif allowances > 0: - deduction = exemption_allowance_table[schedule_pay][allowance_index] - tax -= deduction - - result = -tax - - - - - - diff --git a/l10n_us_ca_hr_payroll/hr_payroll.py b/l10n_us_ca_hr_payroll/hr_payroll.py deleted file mode 100755 index b1a3eebf..00000000 --- a/l10n_us_ca_hr_payroll/hr_payroll.py +++ /dev/null @@ -1,56 +0,0 @@ -from odoo import models, fields, api - - -class USCAHrContract(models.Model): - _inherit = 'hr.contract' - - ca_de4_allowances = fields.Integer(string="California CA-4 Allowances", - default=0, - help="Estimated Deductions claimed on DE-4") - ca_additional_allowances = fields.Integer(string="Additional Allowances", default=0) - ca_de4_filing_status = fields.Selection([ - ('exempt', 'Exempt'), - ('single', 'Single'), - ('married', 'Married'), - ('head_household', 'Head of Household') - ], string='CA Filing Status', default='single') - - @api.multi - def ca_uit_rate(self, year): - self.ensure_one() - if self.futa_type == self.FUTA_TYPE_BASIC: - return 0.0 - - if hasattr(self.employee_id.company_id, 'ca_uit_rate_' + str(year)): - return self.employee_id.company_id['ca_uit_rate_' + str(year)] - - raise NotImplemented('Year (' + str(year) + ') Not implemented for US California.') - - def ca_ett_rate(self, year): - self.ensure_one() - if self.futa_type == self.FUTA_TYPE_BASIC: - return 0.0 - - if hasattr(self.employee_id.company_id, 'ca_ett_rate_' + str(year)): - return self.employee_id.company_id['ca_ett_rate_' + str(year)] - - raise NotImplemented('Year (' + str(year) + ') Not implemented for US California.') - - def ca_sdi_rate(self, year): - self.ensure_one() - if self.futa_type == self.FUTA_TYPE_BASIC: - return 0.0 - - if hasattr(self.employee_id.company_id, 'ca_sdi_rate_' + str(year)): - return self.employee_id.company_id['ca_sdi_rate_' + str(year)] - - raise NotImplemented('Year (' + str(year) + ') Not implemented for US California.') - - -class CACompany(models.Model): - _inherit = 'res.company' - - # UIT can be calculated using http://www.edd.ca.gov/pdf_pub_ctr/de44.pdf ETT is default. - ca_uit_rate_2018 = fields.Float(string="California Unemployment Insurance Tax Rate 2018", default=2.6) - ca_ett_rate_2018 = fields.Float(string="California Employment Training Tax Rate 2018", default=0.1) - ca_sdi_rate_2018 = fields.Float(string="California State Disability Insurance Rate 2018", default=1.0) diff --git a/l10n_us_ca_hr_payroll/models/__init__.py b/l10n_us_ca_hr_payroll/models/__init__.py new file mode 100644 index 00000000..e99aa24a --- /dev/null +++ b/l10n_us_ca_hr_payroll/models/__init__.py @@ -0,0 +1 @@ +from . import hr_payroll diff --git a/l10n_us_ca_hr_payroll/models/hr_payroll.py b/l10n_us_ca_hr_payroll/models/hr_payroll.py new file mode 100755 index 00000000..28590d82 --- /dev/null +++ b/l10n_us_ca_hr_payroll/models/hr_payroll.py @@ -0,0 +1,16 @@ +from odoo import models, fields, api + + +class USCAHrContract(models.Model): + _inherit = 'hr.contract' + + ca_de4_allowances = fields.Integer(string="California CA-4 Allowances", + default=0, + help="Estimated Deductions claimed on DE-4") + ca_additional_allowances = fields.Integer(string="Additional Allowances", default=0) + ca_de4_filing_status = fields.Selection([ + ('exempt', 'Exempt'), + ('single', 'Single'), + ('married', 'Married'), + ('head_household', 'Head of Household') + ], string='CA Filing Status', default='single') diff --git a/l10n_us_ca_hr_payroll/tests/__init__.py b/l10n_us_ca_hr_payroll/tests/__init__.py index 8011621b..545b0768 100755 --- a/l10n_us_ca_hr_payroll/tests/__init__.py +++ b/l10n_us_ca_hr_payroll/tests/__init__.py @@ -1 +1,2 @@ -from . import test_us_ca_payslip_2018 \ No newline at end of file +from . import test_us_ca_payslip_2018 +from . import test_us_ca_payslip_2019 diff --git a/l10n_us_ca_hr_payroll/tests/test_us_ca_payslip_2018.py b/l10n_us_ca_hr_payroll/tests/test_us_ca_payslip_2018.py index d0835917..b8f13bf9 100755 --- a/l10n_us_ca_hr_payroll/tests/test_us_ca_payslip_2018.py +++ b/l10n_us_ca_hr_payroll/tests/test_us_ca_payslip_2018.py @@ -1,5 +1,4 @@ from odoo.addons.l10n_us_hr_payroll.tests.test_us_payslip import TestUsPayslip, process_payslip -from odoo.addons.l10n_us_hr_payroll.models.l10n_us_hr_payroll import USHrContract class TestUsCAPayslip(TestUsPayslip): @@ -10,6 +9,10 @@ class TestUsCAPayslip(TestUsPayslip): CA_UIT_MAX_WAGE = 7000 CA_SDI_MAX_WAGE = 114967 + CA_UIT = -2.6 / 100.0 + CA_ETT = -0.1 / 100.0 + CA_SDI = -1.0 / 100.0 + # Examples from http://www.edd.ca.gov/pdf_pub_ctr/18methb.pdf def test_example_a(self): salary = 210 @@ -32,11 +35,6 @@ class TestUsCAPayslip(TestUsPayslip): self.assertEqual(contract.schedule_pay, 'weekly') - # tax rates - ca_uit = contract.ca_uit_rate(2018) / -100.0 - ca_ett = contract.ca_ett_rate(2018) / -100.0 - ca_sdi = contract.ca_sdi_rate(2018) / -100.0 - self._log('2017 California tax last payslip:') payslip = self._createPayslip(employee, '2017-12-01', '2017-12-31') payslip.compute_sheet() @@ -49,13 +47,13 @@ class TestUsCAPayslip(TestUsPayslip): cats = self._getCategories(payslip) - self.assertPayrollEqual(cats['CA_UIT_WAGES'], salary) - self.assertPayrollEqual(cats['CA_UIT'], cats['CA_UIT_WAGES'] * ca_uit) - self.assertPayrollEqual(cats['CA_ETT_WAGES'], salary) - self.assertPayrollEqual(cats['CA_ETT'], cats['CA_ETT_WAGES'] * ca_ett) - self.assertPayrollEqual(cats['CA_SDI_WAGES'], salary) - self.assertPayrollEqual(cats['CA_SDI'], cats['CA_SDI_WAGES'] * ca_sdi) - self.assertPayrollEqual(cats['CA_WITHHOLD'], wh) + self.assertPayrollEqual(cats['WAGE_US_CA_UNEMP'], salary) + self.assertPayrollEqual(cats['ER_US_CA_UNEMP'], cats['WAGE_US_CA_UNEMP'] * self.CA_UIT) + self.assertPayrollEqual(cats['WAGE_US_CA_ETT'], salary) + self.assertPayrollEqual(cats['ER_US_CA_ETT'], cats['WAGE_US_CA_ETT'] * self.CA_ETT) + self.assertPayrollEqual(cats['WAGE_US_CA_SDI'], salary) + self.assertPayrollEqual(cats['EE_US_CA_SDI'], cats['WAGE_US_CA_SDI'] * self.CA_SDI) + self.assertPayrollEqual(cats['EE_US_CA_INC_WITHHOLD'], wh) process_payslip(payslip) @@ -71,8 +69,8 @@ class TestUsCAPayslip(TestUsPayslip): cats = self._getCategories(payslip) - self.assertPayrollEqual(cats['CA_UIT_WAGES'], remaining_ca_uit_wages) - self.assertPayrollEqual(cats['CA_UIT'], remaining_ca_uit_wages * ca_uit) + self.assertPayrollEqual(cats['WAGE_US_CA_UNEMP'], remaining_ca_uit_wages) + self.assertPayrollEqual(cats['ER_US_CA_UNEMP'], remaining_ca_uit_wages * self.CA_UIT) def test_example_b(self): salary = 1250 @@ -81,16 +79,8 @@ class TestUsCAPayslip(TestUsPayslip): additional_allowances = 1 wh = -2.89 - # tax rates - ca_uit = 2.6 - ca_ett = 0.1 - ca_sdi = 1.0 employee = self._createEmployee() - employee.company_id.ca_uit_rate_2018 = ca_uit - employee.company_id.ca_ett_rate_2018 = ca_ett - employee.company_id.ca_sdi_rate_2018 = ca_sdi - contract = self._createContract(employee, salary, struct_id=self.ref( @@ -117,13 +107,13 @@ class TestUsCAPayslip(TestUsPayslip): cats = self._getCategories(payslip) - self.assertPayrollEqual(cats['CA_UIT_WAGES'], salary) - self.assertPayrollEqual(cats['CA_UIT'], round((cats['CA_UIT_WAGES'] * ca_uit)/-100, 2)) - self.assertPayrollEqual(cats['CA_ETT_WAGES'], salary) - self.assertPayrollEqual(cats['CA_ETT'], round((cats['CA_ETT_WAGES'] * ca_ett)/-100, 2)) - self.assertPayrollEqual(cats['CA_SDI_WAGES'], salary) - self.assertPayrollEqual(cats['CA_SDI'], round((cats['CA_SDI_WAGES'] * ca_sdi)/-100, 2)) - self.assertPayrollEqual(cats['CA_WITHHOLD'], wh) + self.assertPayrollEqual(cats['WAGE_US_CA_UNEMP'], salary) + self.assertPayrollEqual(cats['ER_US_CA_UNEMP'], round((cats['WAGE_US_CA_UNEMP'] * self.CA_UIT), 2)) + self.assertPayrollEqual(cats['WAGE_US_CA_ETT'], salary) + self.assertPayrollEqual(cats['ER_US_CA_ETT'], round((cats['WAGE_US_CA_ETT'] * self.CA_ETT), 2)) + self.assertPayrollEqual(cats['WAGE_US_CA_SDI'], salary) + self.assertPayrollEqual(cats['EE_US_CA_SDI'], round((cats['WAGE_US_CA_SDI'] * self.CA_SDI), 2)) + self.assertPayrollEqual(cats['EE_US_CA_INC_WITHHOLD'], wh) process_payslip(payslip) @@ -139,8 +129,8 @@ class TestUsCAPayslip(TestUsPayslip): cats = self._getCategories(payslip) - self.assertPayrollEqual(cats['CA_UIT_WAGES'], remaining_ca_uit_wages) - self.assertPayrollEqual(cats['CA_UIT'], round((remaining_ca_uit_wages * ca_uit)/-100, 2)) + self.assertPayrollEqual(cats['WAGE_US_CA_UNEMP'], remaining_ca_uit_wages) + self.assertPayrollEqual(cats['ER_US_CA_UNEMP'], round((remaining_ca_uit_wages * self.CA_UIT), 2)) def test_example_c(self): salary = 3800 @@ -149,16 +139,8 @@ class TestUsCAPayslip(TestUsPayslip): additional_allowances = 0.72 wh = -0.72 - # tax rates - ca_uit = 2.6 - ca_ett = 0.1 - ca_sdi = 1.0 employee = self._createEmployee() - employee.company_id.ca_uit_rate_2018 = ca_uit - employee.company_id.ca_ett_rate_2018 = ca_ett - employee.company_id.ca_sdi_rate_2018 = ca_sdi - contract = self._createContract(employee, salary, struct_id=self.ref( @@ -185,13 +167,13 @@ class TestUsCAPayslip(TestUsPayslip): cats = self._getCategories(payslip) - self.assertPayrollEqual(cats['CA_UIT_WAGES'], salary) - self.assertPayrollEqual(cats['CA_UIT'], round((cats['CA_UIT_WAGES'] * ca_uit)/-100, 2)) - self.assertPayrollEqual(cats['CA_ETT_WAGES'], salary) - self.assertPayrollEqual(cats['CA_ETT'], round((cats['CA_ETT_WAGES'] * ca_ett)/-100, 2)) - self.assertPayrollEqual(cats['CA_SDI_WAGES'], salary) - self.assertPayrollEqual(cats['CA_SDI'], round((cats['CA_SDI_WAGES'] * ca_sdi)/-100, 2)) - self.assertPayrollEqual(cats['CA_WITHHOLD'], wh) + self.assertPayrollEqual(cats['WAGE_US_CA_UNEMP'], salary) + self.assertPayrollEqual(cats['ER_US_CA_UNEMP'], round((cats['WAGE_US_CA_UNEMP'] * self.CA_UIT), 2)) + self.assertPayrollEqual(cats['WAGE_US_CA_ETT'], salary) + self.assertPayrollEqual(cats['ER_US_CA_ETT'], round((cats['WAGE_US_CA_ETT'] * self.CA_ETT), 2)) + self.assertPayrollEqual(cats['WAGE_US_CA_SDI'], salary) + self.assertPayrollEqual(cats['EE_US_CA_SDI'], round((cats['WAGE_US_CA_SDI'] * self.CA_SDI), 2)) + self.assertPayrollEqual(cats['EE_US_CA_INC_WITHHOLD'], wh) process_payslip(payslip) @@ -207,8 +189,8 @@ class TestUsCAPayslip(TestUsPayslip): cats = self._getCategories(payslip) - self.assertPayrollEqual(cats['CA_UIT_WAGES'], remaining_ca_uit_wages) - self.assertPayrollEqual(cats['CA_UIT'], round((remaining_ca_uit_wages * ca_uit)/-100, 2)) + self.assertPayrollEqual(cats['WAGE_US_CA_UNEMP'], remaining_ca_uit_wages) + self.assertPayrollEqual(cats['ER_US_CA_UNEMP'], round((remaining_ca_uit_wages * self.CA_UIT), 2)) def test_example_d(self): salary = 800 @@ -217,16 +199,8 @@ class TestUsCAPayslip(TestUsPayslip): additional_allowances = 0 wh = -3.31 - # tax rates - ca_uit = 2.6 - ca_ett = 0.1 - ca_sdi = 1.0 employee = self._createEmployee() - employee.company_id.ca_uit_rate_2018 = ca_uit - employee.company_id.ca_ett_rate_2018 = ca_ett - employee.company_id.ca_sdi_rate_2018 = ca_sdi - contract = self._createContract(employee, salary, struct_id=self.ref( @@ -253,13 +227,13 @@ class TestUsCAPayslip(TestUsPayslip): cats = self._getCategories(payslip) - self.assertPayrollEqual(cats['CA_UIT_WAGES'], salary) - self.assertPayrollEqual(cats['CA_UIT'], round((cats['CA_UIT_WAGES'] * ca_uit)/-100, 2)) - self.assertPayrollEqual(cats['CA_ETT_WAGES'], salary) - self.assertPayrollEqual(cats['CA_ETT'], round((cats['CA_ETT_WAGES'] * ca_ett)/-100, 2)) - self.assertPayrollEqual(cats['CA_SDI_WAGES'], salary) - self.assertPayrollEqual(cats['CA_SDI'], round((cats['CA_SDI_WAGES'] * ca_sdi)/-100, 2)) - self.assertPayrollEqual(cats['CA_WITHHOLD'], wh) + self.assertPayrollEqual(cats['WAGE_US_CA_UNEMP'], salary) + self.assertPayrollEqual(cats['ER_US_CA_UNEMP'], round((cats['WAGE_US_CA_UNEMP'] * self.CA_UIT), 2)) + self.assertPayrollEqual(cats['WAGE_US_CA_ETT'], salary) + self.assertPayrollEqual(cats['ER_US_CA_ETT'], round((cats['WAGE_US_CA_ETT'] * self.CA_ETT), 2)) + self.assertPayrollEqual(cats['WAGE_US_CA_SDI'], salary) + self.assertPayrollEqual(cats['EE_US_CA_SDI'], round((cats['WAGE_US_CA_SDI'] * self.CA_SDI), 2)) + self.assertPayrollEqual(cats['EE_US_CA_INC_WITHHOLD'], wh) process_payslip(payslip) @@ -275,8 +249,8 @@ class TestUsCAPayslip(TestUsPayslip): cats = self._getCategories(payslip) - self.assertPayrollEqual(cats['CA_UIT_WAGES'], remaining_ca_uit_wages) - self.assertPayrollEqual(cats['CA_UIT'], round((remaining_ca_uit_wages * ca_uit)/-100, 2)) + self.assertPayrollEqual(cats['WAGE_US_CA_UNEMP'], remaining_ca_uit_wages) + self.assertPayrollEqual(cats['ER_US_CA_UNEMP'], round((remaining_ca_uit_wages * self.CA_UIT), 2)) def test_example_e(self): salary = 1800 @@ -285,16 +259,8 @@ class TestUsCAPayslip(TestUsPayslip): additional_allowances = 0 wh = -3.39 - # tax rates - ca_uit = 2.6 - ca_ett = 0.1 - ca_sdi = 1.0 employee = self._createEmployee() - employee.company_id.ca_uit_rate_2018 = ca_uit - employee.company_id.ca_ett_rate_2018 = ca_ett - employee.company_id.ca_sdi_rate_2018 = ca_sdi - contract = self._createContract(employee, salary, struct_id=self.ref( @@ -321,13 +287,13 @@ class TestUsCAPayslip(TestUsPayslip): cats = self._getCategories(payslip) - self.assertPayrollEqual(cats['CA_UIT_WAGES'], salary) - self.assertPayrollEqual(cats['CA_UIT'], round((cats['CA_UIT_WAGES'] * ca_uit)/-100, 2)) - self.assertPayrollEqual(cats['CA_ETT_WAGES'], salary) - self.assertPayrollEqual(cats['CA_ETT'], round((cats['CA_ETT_WAGES'] * ca_ett)/-100, 2)) - self.assertPayrollEqual(cats['CA_SDI_WAGES'], salary) - self.assertPayrollEqual(cats['CA_SDI'], round((cats['CA_SDI_WAGES'] * ca_sdi)/-100, 2)) - self.assertPayrollEqual(cats['CA_WITHHOLD'], wh) + self.assertPayrollEqual(cats['WAGE_US_CA_UNEMP'], salary) + self.assertPayrollEqual(cats['ER_US_CA_UNEMP'], round((cats['WAGE_US_CA_UNEMP'] * self.CA_UIT), 2)) + self.assertPayrollEqual(cats['WAGE_US_CA_ETT'], salary) + self.assertPayrollEqual(cats['ER_US_CA_ETT'], round((cats['WAGE_US_CA_ETT'] * self.CA_ETT), 2)) + self.assertPayrollEqual(cats['WAGE_US_CA_SDI'], salary) + self.assertPayrollEqual(cats['EE_US_CA_SDI'], round((cats['WAGE_US_CA_SDI'] * self.CA_SDI), 2)) + self.assertPayrollEqual(cats['EE_US_CA_INC_WITHHOLD'], wh) process_payslip(payslip) @@ -343,8 +309,8 @@ class TestUsCAPayslip(TestUsPayslip): cats = self._getCategories(payslip) - self.assertPayrollEqual(cats['CA_UIT_WAGES'], remaining_ca_uit_wages) - self.assertPayrollEqual(cats['CA_UIT'], round((remaining_ca_uit_wages * ca_uit)/-100, 2)) + self.assertPayrollEqual(cats['WAGE_US_CA_UNEMP'], remaining_ca_uit_wages) + self.assertPayrollEqual(cats['ER_US_CA_UNEMP'], round((remaining_ca_uit_wages * self.CA_UIT), 2)) def test_example_f(self): salary = 45000 @@ -353,16 +319,8 @@ class TestUsCAPayslip(TestUsPayslip): additional_allowances = 0 wh = -121.11 - # tax rates - ca_uit = 2.6 - ca_ett = 0.1 - ca_sdi = 1.0 employee = self._createEmployee() - employee.company_id.ca_uit_rate_2018 = ca_uit - employee.company_id.ca_ett_rate_2018 = ca_ett - employee.company_id.ca_sdi_rate_2018 = ca_sdi - contract = self._createContract(employee, salary, struct_id=self.ref( @@ -389,10 +347,10 @@ class TestUsCAPayslip(TestUsPayslip): cats = self._getCategories(payslip) - self.assertPayrollEqual(cats['CA_UIT'], round((cats['CA_UIT_WAGES'] * ca_uit)/-100, 2)) - self.assertPayrollEqual(cats['CA_ETT'], round((cats['CA_ETT_WAGES'] * ca_ett)/-100, 2)) - self.assertPayrollEqual(cats['CA_SDI'], round((cats['CA_SDI_WAGES'] * ca_sdi)/-100, 2)) - self.assertPayrollEqual(cats['CA_WITHHOLD'], wh) + self.assertPayrollEqual(cats['ER_US_CA_UNEMP'], round((cats['WAGE_US_CA_UNEMP'] * self.CA_UIT), 2)) + self.assertPayrollEqual(cats['ER_US_CA_ETT'], round((cats['WAGE_US_CA_ETT'] * self.CA_ETT), 2)) + self.assertPayrollEqual(cats['EE_US_CA_SDI'], round((cats['WAGE_US_CA_SDI'] * self.CA_SDI), 2)) + self.assertPayrollEqual(cats['EE_US_CA_INC_WITHHOLD'], wh) process_payslip(payslip) diff --git a/l10n_us_ca_hr_payroll/tests/test_us_ca_payslip_2019.py b/l10n_us_ca_hr_payroll/tests/test_us_ca_payslip_2019.py new file mode 100755 index 00000000..b9a3e71d --- /dev/null +++ b/l10n_us_ca_hr_payroll/tests/test_us_ca_payslip_2019.py @@ -0,0 +1,425 @@ +from odoo.addons.l10n_us_hr_payroll.tests.test_us_payslip import TestUsPayslip, process_payslip + + +class TestUsCAPayslip(TestUsPayslip): + ### + # Taxes and Rates + ### + CA_UIT_MAX_WAGE = 7000 + CA_UIT_MAX_WAGE = 7000 + CA_SDI_MAX_WAGE = 18371 + + CA_UIT = -3.4 / 100.0 + CA_ETT = -0.1 / 100.0 + CA_SDI = -1.0 / 100.0 + + # Examples from http://www.edd.ca.gov/pdf_pub_ctr/18methb.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, + salary, + struct_id=self.ref( + 'l10n_us_ca_hr_payroll.hr_payroll_salary_structure_us_ca_employee'), + schedule_pay=schedule_pay) + contract.ca_c4_exemptions = allowances + contract.ca_additional_allowances = additional_allowances + contract.ca_de4_filing_status = 'single' + + self.assertEqual(contract.schedule_pay, 'weekly') + + self._log('2018 California tax last payslip:') + payslip = self._createPayslip(employee, '2018-12-01', '2018-12-31') + payslip.compute_sheet() + process_payslip(payslip) + + 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['WAGE_US_CA_UNEMP'], salary) + self.assertPayrollEqual(cats['ER_US_CA_UNEMP'], cats['WAGE_US_CA_UNEMP'] * self.CA_UIT) + self.assertPayrollEqual(cats['WAGE_US_CA_ETT'], salary) + self.assertPayrollEqual(cats['ER_US_CA_ETT'], cats['WAGE_US_CA_ETT'] * self.CA_ETT) + self.assertPayrollEqual(cats['WAGE_US_CA_SDI'], salary) + self.assertPayrollEqual(cats['EE_US_CA_SDI'], cats['WAGE_US_CA_SDI'] * self.CA_SDI) + self.assertPayrollEqual(cats['EE_US_CA_INC_WITHHOLD'], wh) + + process_payslip(payslip) + + # Make a new payslip, this one will have maximums + + remaining_ca_uit_wages = self.CA_UIT_MAX_WAGE - salary if (self.CA_UIT_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['WAGE_US_CA_UNEMP'], remaining_ca_uit_wages) + self.assertPayrollEqual(cats['ER_US_CA_UNEMP'], remaining_ca_uit_wages * self.CA_UIT) + + def test_example_b(self): + salary = 1250 + schedule_pay = 'bi-weekly' + allowances = 2 + additional_allowances = 1 + + # for additional allowances + wh = salary - 38 + wh = wh - 339 + wh = (wh - 632) * 0.022 + 6.95 + wh = wh - 9.65 + # 2.651 - 9.65 + wh = -wh + + employee = self._createEmployee() + contract = self._createContract(employee, + salary, + struct_id=self.ref( + 'l10n_us_ca_hr_payroll.hr_payroll_salary_structure_us_ca_employee'), + schedule_pay=schedule_pay) + contract.ca_de4_allowances = allowances + contract.ca_additional_allowances = additional_allowances + contract.ca_de4_filing_status = 'married' + + self.assertEqual(contract.schedule_pay, 'bi-weekly') + self.assertEqual(contract.ca_de4_filing_status, 'married') + self.assertEqual(contract.ca_de4_allowances, 2) + self.assertEqual(contract.ca_additional_allowances, 1) + + self._log('2018 California tax last payslip:') + payslip = self._createPayslip(employee, '2018-12-01', '2018-12-31') + payslip.compute_sheet() + process_payslip(payslip) + + 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['WAGE_US_CA_UNEMP'], salary) + self.assertPayrollEqual(cats['ER_US_CA_UNEMP'], round((cats['WAGE_US_CA_UNEMP'] * self.CA_UIT), 2)) + self.assertPayrollEqual(cats['WAGE_US_CA_ETT'], salary) + self.assertPayrollEqual(cats['ER_US_CA_ETT'], round((cats['WAGE_US_CA_ETT'] * self.CA_ETT), 2)) + self.assertPayrollEqual(cats['WAGE_US_CA_SDI'], salary) + self.assertPayrollEqual(cats['EE_US_CA_SDI'], round((cats['WAGE_US_CA_SDI'] * self.CA_SDI), 2)) + self.assertPayrollEqual(cats['EE_US_CA_INC_WITHHOLD'], wh) + + process_payslip(payslip) + + # Make a new payslip, this one will have maximums + + remaining_ca_uit_wages = self.CA_UIT_MAX_WAGE - salary if (self.CA_UIT_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['WAGE_US_CA_UNEMP'], remaining_ca_uit_wages) + self.assertPayrollEqual(cats['ER_US_CA_UNEMP'], round((remaining_ca_uit_wages * self.CA_UIT), 2)) + + def test_example_c(self): + salary = 3800 + schedule_pay = 'monthly' + allowances = 5 + additional_allowances = 0.72 + + wh = -0.11 + + employee = self._createEmployee() + contract = self._createContract(employee, + salary, + struct_id=self.ref( + 'l10n_us_ca_hr_payroll.hr_payroll_salary_structure_us_ca_employee'), + schedule_pay=schedule_pay) + contract.ca_de4_allowances = allowances + contract.ca_additional_allowances = additional_allowances + contract.ca_de4_filing_status = 'married' + + self.assertEqual(contract.schedule_pay, 'monthly') + self.assertEqual(contract.ca_de4_filing_status, 'married') + self.assertEqual(contract.ca_de4_allowances, 5) + self.assertEqual(contract.ca_additional_allowances, 0) + + self._log('2018 California tax last payslip:') + payslip = self._createPayslip(employee, '2018-12-01', '2018-12-31') + payslip.compute_sheet() + process_payslip(payslip) + + 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['WAGE_US_CA_UNEMP'], salary) + self.assertPayrollEqual(cats['ER_US_CA_UNEMP'], round((cats['WAGE_US_CA_UNEMP'] * self.CA_UIT), 2)) + self.assertPayrollEqual(cats['WAGE_US_CA_ETT'], salary) + self.assertPayrollEqual(cats['ER_US_CA_ETT'], round((cats['WAGE_US_CA_ETT'] * self.CA_ETT), 2)) + self.assertPayrollEqual(cats['WAGE_US_CA_SDI'], salary) + self.assertPayrollEqual(cats['EE_US_CA_SDI'], round((cats['WAGE_US_CA_SDI'] * self.CA_SDI), 2)) + self.assertPayrollEqual(cats['EE_US_CA_INC_WITHHOLD'], wh) + + process_payslip(payslip) + + # Make a new payslip, this one will have maximums + + remaining_ca_uit_wages = self.CA_UIT_MAX_WAGE - salary if (self.CA_UIT_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['WAGE_US_CA_UNEMP'], remaining_ca_uit_wages) + self.assertPayrollEqual(cats['ER_US_CA_UNEMP'], round((remaining_ca_uit_wages * self.CA_UIT), 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, + salary, + struct_id=self.ref( + 'l10n_us_ca_hr_payroll.hr_payroll_salary_structure_us_ca_employee'), + schedule_pay=schedule_pay) + contract.ca_de4_allowances = allowances + contract.ca_additional_allowances = additional_allowances + contract.ca_de4_filing_status = 'head_household' + + self.assertEqual(contract.schedule_pay, 'weekly') + self.assertEqual(contract.ca_de4_filing_status, 'head_household') + self.assertEqual(contract.ca_de4_allowances, 3) + self.assertEqual(contract.ca_additional_allowances, 0) + + self._log('2018 California tax last payslip:') + payslip = self._createPayslip(employee, '2018-12-01', '2018-12-31') + payslip.compute_sheet() + process_payslip(payslip) + + 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['WAGE_US_CA_UNEMP'], salary) + self.assertPayrollEqual(cats['ER_US_CA_UNEMP'], round((cats['WAGE_US_CA_UNEMP'] * self.CA_UIT), 2)) + self.assertPayrollEqual(cats['WAGE_US_CA_ETT'], salary) + self.assertPayrollEqual(cats['ER_US_CA_ETT'], round((cats['WAGE_US_CA_ETT'] * self.CA_ETT), 2)) + self.assertPayrollEqual(cats['WAGE_US_CA_SDI'], salary) + self.assertPayrollEqual(cats['EE_US_CA_SDI'], round((cats['WAGE_US_CA_SDI'] * self.CA_SDI), 2)) + self.assertPayrollEqual(cats['EE_US_CA_INC_WITHHOLD'], wh) + + process_payslip(payslip) + + # Make a new payslip, this one will have maximums + + remaining_ca_uit_wages = self.CA_UIT_MAX_WAGE - salary if (self.CA_UIT_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['WAGE_US_CA_UNEMP'], remaining_ca_uit_wages) + self.assertPayrollEqual(cats['ER_US_CA_UNEMP'], round((remaining_ca_uit_wages * self.CA_UIT), 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, + salary, + struct_id=self.ref( + 'l10n_us_ca_hr_payroll.hr_payroll_salary_structure_us_ca_employee'), + schedule_pay=schedule_pay) + contract.ca_de4_allowances = allowances + contract.ca_additional_allowances = additional_allowances + contract.ca_de4_filing_status = 'married' + + self.assertEqual(contract.schedule_pay, 'semi-monthly') + self.assertEqual(contract.ca_de4_filing_status, 'married') + self.assertEqual(contract.ca_de4_allowances, 4) + self.assertEqual(contract.ca_additional_allowances, 0) + + self._log('2018 California tax last payslip:') + payslip = self._createPayslip(employee, '2018-12-01', '2018-12-31') + payslip.compute_sheet() + process_payslip(payslip) + + 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['WAGE_US_CA_UNEMP'], salary) + self.assertPayrollEqual(cats['ER_US_CA_UNEMP'], round((cats['WAGE_US_CA_UNEMP'] * self.CA_UIT), 2)) + self.assertPayrollEqual(cats['WAGE_US_CA_ETT'], salary) + self.assertPayrollEqual(cats['ER_US_CA_ETT'], round((cats['WAGE_US_CA_ETT'] * self.CA_ETT), 2)) + self.assertPayrollEqual(cats['WAGE_US_CA_SDI'], salary) + self.assertPayrollEqual(cats['EE_US_CA_SDI'], round((cats['WAGE_US_CA_SDI'] * self.CA_SDI), 2)) + self.assertPayrollEqual(cats['EE_US_CA_INC_WITHHOLD'], wh) + + process_payslip(payslip) + + # Make a new payslip, this one will have maximums + + remaining_ca_uit_wages = self.CA_UIT_MAX_WAGE - salary if (self.CA_UIT_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['WAGE_US_CA_UNEMP'], remaining_ca_uit_wages) + self.assertPayrollEqual(cats['ER_US_CA_UNEMP'], round((remaining_ca_uit_wages * self.CA_UIT), 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, + salary, + struct_id=self.ref( + 'l10n_us_ca_hr_payroll.hr_payroll_salary_structure_us_ca_employee'), + schedule_pay=schedule_pay) + contract.ca_de4_allowances = allowances + contract.ca_additional_allowances = additional_allowances + contract.ca_de4_filing_status = 'married' + + self.assertEqual(contract.schedule_pay, 'annually') + self.assertEqual(contract.ca_de4_filing_status, 'married') + self.assertEqual(contract.ca_de4_allowances, 4) + self.assertEqual(contract.ca_additional_allowances, 0) + + self._log('2018 California tax last payslip:') + payslip = self._createPayslip(employee, '2018-12-01', '2018-12-31') + payslip.compute_sheet() + process_payslip(payslip) + + 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_CA_UNEMP'], round((cats['WAGE_US_CA_UNEMP'] * self.CA_UIT), 2)) + self.assertPayrollEqual(cats['ER_US_CA_ETT'], round((cats['WAGE_US_CA_ETT'] * self.CA_ETT), 2)) + self.assertPayrollEqual(cats['EE_US_CA_SDI'], round((cats['WAGE_US_CA_SDI'] * self.CA_SDI), 2)) + self.assertPayrollEqual(cats['EE_US_CA_INC_WITHHOLD'], wh) + + process_payslip(payslip) + + def test_estimated_deduction_table(self): + salary = 600 + allowances = 5 + schedule_pay = 'bi-weekly' + expected_deduction = 192 + deduction = 0 + taxable_pay = 0 + estimated_deduction_table = { + '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), + 'annual': (1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000), + } + + allowance_index = allowances - 1 + if allowances > 10: + deduction = (estimated_deduction_table[schedule_pay][0]) * allowances + taxable_pay = salary - deduction + elif allowances > 0: + deduction = estimated_deduction_table[schedule_pay][allowance_index] + taxable_pay = salary - deduction + + self.assertEqual(expected_deduction, deduction) + self.assertTrue(taxable_pay < salary) + self.assertEqual(taxable_pay, salary - deduction) + + def test_standard_deduction_table(self): + salary = 3000 + schedule_pay = 'monthly' + filing_status = 'head_household' + expected_deduction = 706 + deduction = 0 + taxable_pay = 0 + standard_deduction_table = { + 'weekly': (81, 81, 163, 163), + 'bi-weekly': (163, 163, 326, 326), + 'semi-monthly': (177, 177, 353, 353), + 'monthly': (353, 352, 706, 706), + 'quarterly': (1059, 1059, 2188, 2188), + 'semi-annual': (2118, 2118, 4236, 4236), + 'annual': (4236, 4236, 8471, 8472), + } + + if filing_status == 'head_household': + _, _, _, deduction = standard_deduction_table[schedule_pay] + taxable_pay = salary - deduction + elif filing_status == 'married': + if allowances >= 2: + _, _, deduction, _ = standard_deduction_table[schedule_pay] + taxable_pay = salary - deduction + else: + _, deduction, _, _ = standard_deduction_table[schedule_pay] + taxable_pay = salary - deduction + else: + deduction, _, _, _ = standard_deduction_table[schedule_pay] + taxable_pay = salary - deduction + + self.assertEqual(expected_deduction, deduction) + self.assertTrue(taxable_pay < salary) + self.assertEqual(taxable_pay, salary - deduction) diff --git a/l10n_us_ca_hr_payroll/hr_payroll_view.xml b/l10n_us_ca_hr_payroll/views/hr_payroll_views.xml similarity index 62% rename from l10n_us_ca_hr_payroll/hr_payroll_view.xml rename to l10n_us_ca_hr_payroll/views/hr_payroll_views.xml index 15707d29..2231f38a 100755 --- a/l10n_us_ca_hr_payroll/hr_payroll_view.xml +++ b/l10n_us_ca_hr_payroll/views/hr_payroll_views.xml @@ -1,19 +1,6 @@ - - res.company.form - res.company - 64 - - - - - - - - - hr.contract.form.inherit hr.contract