diff --git a/l10n_us_hr_payroll/__init__.py b/l10n_us_hr_payroll/__init__.py new file mode 100755 index 00000000..e50a95b6 --- /dev/null +++ b/l10n_us_hr_payroll/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import l10n_us_hr_payroll diff --git a/l10n_us_hr_payroll/__manifest__.py b/l10n_us_hr_payroll/__manifest__.py new file mode 100755 index 00000000..d9201dff --- /dev/null +++ b/l10n_us_hr_payroll/__manifest__.py @@ -0,0 +1,32 @@ +# -*- encoding: utf-8 -*- +{ + 'name': 'USA - Payroll', + 'author': 'Hibou Corp. ', + 'license': 'AGPL-3', + 'category': 'Localization', + 'depends': ['hr_payroll'], + 'version': '11.0.2017.0.0', + 'description': """ +USA Payroll Rules. +================== + + * Contract W4 Filing Status & Allowances + * FICA Social Security (with wages cap) + * FICA Medicare + * FICA Additioal Medicare Wages & Tax + * FUTA Federal Unemployment (with wages cap) + * Federal Income Tax Withholdings based on W4 values + """, + + 'auto_install': False, + 'website': 'https://hibou.io/', + 'data': [ + 'l10n_us_hr_payroll_view.xml', + 'data/base.xml', + 'data/rules_2016.xml', + 'data/rules_2017.xml', + 'data/rules_2018.xml', + 'data/final.xml', + ], + 'installable': True +} diff --git a/l10n_us_hr_payroll/data/base.xml b/l10n_us_hr_payroll/data/base.xml new file mode 100755 index 00000000..5a2dabcf --- /dev/null +++ b/l10n_us_hr_payroll/data/base.xml @@ -0,0 +1,136 @@ + + + + + + EFTPS - Form 941 + 1 + + + + EFTPS - Form 940 + 1 + + + + EFTPS - 941 (FICA + Federal Witholding) + Electronic Federal Tax Payment System - Form 941 + + + + EFTPS - 940 (FUTA) + Electronic Federal Tax Payment System - Form 940 + + + + + + + FICA Employee Social Security - Wages + FICA_EMP_SS_WAGES + + + FICA Employee Medicare - Wages + FICA_EMP_M_WAGES + + + FICA Employee Medicare Additional - Wages + FICA_EMP_M_ADD_WAGES + + + FUTA Federal Unemployment - Wages + FUTA_WAGES + + + + FICA Employee Social Security + FICA_EMP_SS + + + + FICA Employee Medicare + FICA_EMP_M + + + + FICA Employee Medicare Additional + FICA_EMP_M_ADD + + + + Federal Income Withholding + FED_INC_WITHHOLD + + + + + FICA Company Social Security + FICA_COMP_SS + + + + FICA Company Medicare + FICA_COMP_M + + + + FUTA Federal Unemployment + FUTA + + + + + + + + + + + FICA Company Social Security + FICA_COMP_SS + none + code + result = categories.FICA_EMP_SS + + + + + + + FICA Company Medicare + FICA_COMP_M + none + code + result = categories.FICA_EMP_M + + + + + + + + + + + FICA Company Social Security + FICA_COMP_SS + none + code + result = categories.FICA_EMP_SS + + + + + + + FICA Company Medicare + FICA_COMP_M + none + code + result = categories.FICA_EMP_M + + + + + + diff --git a/l10n_us_hr_payroll/data/final.xml b/l10n_us_hr_payroll/data/final.xml new file mode 100755 index 00000000..8f355948 --- /dev/null +++ b/l10n_us_hr_payroll/data/final.xml @@ -0,0 +1,51 @@ + + + + + + + US_EMP + USA Employee + + + + + + + diff --git a/l10n_us_hr_payroll/data/rules_2016.xml b/l10n_us_hr_payroll/data/rules_2016.xml new file mode 100755 index 00000000..bf81af1c --- /dev/null +++ b/l10n_us_hr_payroll/data/rules_2016.xml @@ -0,0 +1,604 @@ + + + + + + + + + + FICA Employee Social Security Wages (2016) + FICA_EMP_SS_WAGES_2016 + python + result = (payslip.date_to[:4] == '2016') + code + +### +ytd = payslip.sum('FICA_EMP_SS_WAGES_2016', '2016-01-01', '2017-01-01') +ytd += contract.external_wages +remaining = 118500.0 - ytd +if remaining <= 0.0: + result = 0 +elif remaining < categories.GROSS: + result = remaining +else: + result = categories.GROSS + + + + + + + FICA Employee Medicare Wages (2016) + FICA_EMP_M_WAGES_2016 + python + result = (payslip.date_to[:4] == '2016') + code + result = categories.GROSS + + + + + + FICA Employee Medicare Additional Wages (2016) + FICA_EMP_M_ADD_WAGES_2016 + python + result = (payslip.date_to[:4] == '2016') + code + +### +ADD_M = 200000.0 +norm_med_ytd = payslip.sum('FICA_EMP_M_WAGES_2016', '2016-01-01', '2017-01-01') +norm_med_cur = categories.FICA_EMP_M_WAGES +if ADD_M > norm_med_ytd: + diff = ADD_M - norm_med_ytd + if norm_med_cur > diff: + result = norm_med_cur - diff + else: + result = 0 # normal condition +else: + result = norm_med_cur # after YTD wages have passed the max + + + + + + + + FICA Employee Social Security (2016) + FICA_EMP_SS_2016 + python + result = (payslip.date_to[:4] == '2016') + code + +### this should be "rules.FICA_EMP_SS_WAGES_2016", but it doesn't work +result_rate = -6.2 +result = categories.FICA_EMP_SS_WAGES + + + + + + + FICA Employee Medicare (2016) + FICA_EMP_M_2016 + python + result = (payslip.date_to[:4] == '2016') + code + +### this should be "rules.FICA_EMP_M_WAGES_2016", but it doesn't work +result_rate = -1.45 +result = categories.FICA_EMP_M_WAGES + + + + + + + FICA Employee Medicare Additional (2016) + FICA_EMP_M_ADD_2016 + python + result = (payslip.date_to[:4] == '2016') + code + +### this should be "rules.FICA_EMP_M_ADD_WAGES_2016", but it doesn't work +result_rate = -0.9 +result = categories.FICA_EMP_M_ADD_WAGES + + + + + + + + + Federal Income Withholding - Single (2016) + FED_INC_WITHHOLD_2016_S + python + result = (payslip.date_to[:4] == '2016' and contract.w4_filing_status != 'married' and contract.w4_filing_status) + code + +wages = categories.GROSS +allowances = contract.w4_allowances +is_nra = contract.w4_is_nonresident_alien +schedule_pay = contract.schedule_pay +val = 0.00 +additional = contract.w4_additional_withholding + +### +# Single WEEKLY +### +if 'weekly' == schedule_pay: + wages -= allowances * 77.90 + if is_nra: + wages += 43.30 + + if wages > 43 and wages <= 222: + val = 0.00 + ((wages - 43) * 0.10) + + elif wages > 222 and wages <= 767: + val = 17.90 + ((wages - 222) * 0.15) + + elif wages > 767 and wages <= 1796: + val = 99.65 + ((wages - 767) * 0.25) + + elif wages > 1796 and wages <= 3700: + val = 356.90 + ((wages - 1796) * 0.28) + + elif wages > 3700 and wages <= 7992: + val = 890.02 + ((wages - 3700) * 0.33) + + elif wages > 7992 and wages <= 8025: + val = 2306.38 + ((wages - 7992) * 0.35) + + elif wages > 8025: + val = 2317.93 + ((wages - 8025) * 0.396) + +### +# Single BIWEEKLY +### +elif 'bi-weekly' == schedule_pay: + wages -= allowances * 155.80 + if is_nra: + wages += 86.50 + + if wages > 87 and wages <= 443: + val = 0.00 + ((wages - 87) * 0.10) + + elif wages > 443 and wages <= 1535: + val = 35.60 + ((wages - 443) * 0.15) + + elif wages > 1535 and wages <= 3592: + val = 199.40 + ((wages - 1535) * 0.25) + + elif wages > 3592 and wages <= 7400: + val = 713.65 + ((wages - 3592) * 0.28) + + elif wages > 7400 and wages <= 15985: + val = 1779.89 + ((wages - 7400) * 0.33) + + elif wages > 15985 and wages <= 16050: + val = 4612.94 + ((wages - 15985) * 0.35) + + elif wages > 16050: + val = 4635.69 + ((wages - 16050) * 0.396) + +### +# Single SEMIMONTHLY +### +elif 'semi-monthly' == schedule_pay: + wages -= allowances * 168.80 + if is_nra: + wages += 93.80 + + if wages > 94 and wages <= 480: + val = 0.00 + ((wages - 94) * 0.10) + + elif wages > 480 and wages <= 1663: + val = 38.60 + ((wages - 480) * 0.15) + + elif wages > 1663 and wages <= 3892: + val = 216.05 + ((wages - 1663) * 0.25) + + elif wages > 3892 and wages <= 8017: + val = 773.30 + ((wages - 3892) * 0.28) + + elif wages > 8017 and wages <= 17317: + val = 1928.30 + ((wages - 8017) * 0.33) + + elif wages > 17317 and wages <= 17388: + val = 4997.30 + ((wages - 17317) * 0.35) + + elif wages > 17388: + val = 5022.15 + ((wages - 17388) * 0.396) + +### +# Single MONTHLY +### +elif 'monthly' == schedule_pay: + wages -= allowances * 337.50 + if is_nra: + wages += 187.50 + + if wages > 188 and wages <= 960: + val = 0.00 + ((wages - 188) * 0.10) + + elif wages > 960 and wages <= 3325: + val = 77.20 + ((wages - 960) * 0.15) + + elif wages > 3325 and wages <= 7783: + val = 431.95 + ((wages - 3325) * 0.25) + + elif wages > 7783 and wages <= 16033: + val = 1546.45 + ((wages - 7783) * 0.28) + + elif wages > 16033 and wages <= 34633: + val = 3856.45 + ((wages - 16033) * 0.33) + + elif wages > 34633 and wages <= 34775: + val = 9994.45 + ((wages - 34633) * 0.35) + + elif wages > 34775: + val = 10044.15 + ((wages - 34775) * 0.396) + +### +# Single QUARTERLY +### +elif 'quarterly' == schedule_pay: + wages -= allowances * 1012.50 + if is_nra: + wages += 562.50 + + if wages > 563 and wages <= 2881: + val = 0.00 + ((wages - 563) * 0.10) + + elif wages > 2881 and wages <= 9975: + val = 231.80 + ((wages - 2881) * 0.15) + + elif wages > 9975 and wages <= 23350: + val = 1295.90 + ((wages - 9975) * 0.25) + + elif wages > 23350 and wages <= 48100: + val = 4639.65 + ((wages - 23350) * 0.28) + + elif wages > 48100 and wages <= 103900: + val = 11569.65 + ((wages - 48100) * 0.33) + + elif wages > 103900 and wages <= 104325: + val = 29983.65 + ((wages - 103900) * 0.35) + + elif wages > 104325: + val = 30132.40 + ((wages - 104325) * 0.396) + +### +# Single SEMIANNUAL +### +elif 'semi-annually' == schedule_pay: + wages -= allowances * 2025.00 + if is_nra: + wages += 1125.0 + + if wages > 1125 and wages <= 5763: + val = 0.00 + ((wages - 1125) * 0.10) + + elif wages > 5763 and wages <= 19950: + val = 463.80 + ((wages - 5763) * 0.15) + + elif wages > 19950 and wages <= 46700: + val = 2591.85 + ((wages - 19950) * 0.25) + + elif wages > 46700 and wages <= 96200: + val = 9279.35 + ((wages - 46700) * 0.28) + + elif wages > 96200 and wages <= 207800: + val = 23139.35 + ((wages - 96200) * 0.33) + + elif wages > 207800 and wages <= 208650: + val = 59967.35 + ((wages - 207800) * 0.35) + + elif wages > 208650: + val = 60264.85 + ((wages - 208650) * 0.396) + +### +# Single ANNUAL +### +elif 'annually' == schedule_pay: + wages -= allowances * 4050.00 + if is_nra: + wages += 2250.0 + + if wages > 2250 and wages <= 11525: + val = 0.00 + ((wages - 2250) * 0.10) + + elif wages > 11525 and wages <= 39900: + val = 927.50 + ((wages - 11525) * 0.15) + + elif wages > 39900 and wages <= 93400: + val = 5183.75 + ((wages - 39900) * 0.25) + + elif wages > 93400 and wages <= 192400: + val = 18558.75 + ((wages - 93400) * 0.28) + + elif wages > 192400 and wages <= 415600: + val = 46278.75 + ((wages - 192400) * 0.33) + + elif wages > 415600 and wages <= 417300: + val = 119934.75 + ((wages - 415600) * 0.35) + + elif wages > 417300: + val = 120529.75 + ((wages - 417300) * 0.396) + +else: + raise Exception('Invalid schedule_pay="' + schedule_pay + '" for W4 Allowance calculation') + +result = -(val + additional) + + + + + + + Federal Income Withholding - Married (2016) + FED_INC_WITHHOLD_2016_M + python + result = (payslip.date_to[:4] == '2016' and contract.w4_filing_status == 'married') + code + +wages = categories.GROSS +allowances = contract.w4_allowances +is_nra = contract.w4_is_nonresident_alien +schedule_pay = contract.schedule_pay +val = 0.00 +additional = contract.w4_additional_withholding + +### +# Married WEEKLY +### +if 'weekly' == schedule_pay: + wages -= allowances * 77.90 + if is_nra: + wages += 43.30 + + if wages > 164 and wages <= 521: + val = 0.00 + ((wages - 164) * 0.10) + + elif wages > 521 and wages <= 1613: + val = 35.70 + ((wages - 521) * 0.15) + + elif wages > 1613 and wages <= 3086: + val = 199.50 + ((wages - 1613) * 0.25) + + elif wages > 3086 and wages <= 4615: + val = 567.75 + ((wages - 3086) * 0.28) + + elif wages > 4615 and wages <= 8113: + val = 995.87 + ((wages - 4615) * 0.33) + + elif wages > 8113 and wages <= 9144: + val = 2150.21 + ((wages - 8113) * 0.35) + + elif wages > 9144: + val = 2511.06 + ((wages - 9144) * 0.396) + +### +# Married BIWEEKLY +### +elif 'bi-weekly' == schedule_pay: + wages -= allowances * 155.80 + if is_nra: + wages += 86.50 + + if wages > 329 and wages <= 1042: + val = 0.00 + ((wages - 329) * 0.10) + + elif wages > 1042 and wages <= 3225: + val = 71.30 + ((wages - 1042) * 0.15) + + elif wages > 3225 and wages <= 6171: + val = 398.75 + ((wages - 3225) * 0.25) + + elif wages > 6171 and wages <= 9231: + val = 1135.25 + ((wages - 6171) * 0.28) + + elif wages > 9231 and wages <= 16227: + val = 1992.05 + ((wages - 9231) * 0.33) + + elif wages > 16227 and wages <= 18288: + val = 4300.73 + ((wages - 16227) * 0.35) + + elif wages > 18288: + val = 5022.08 + ((wages - 18288) * 0.396) + +### +# Married SEMIMONTHLY +### +elif 'semi-monthly' == schedule_pay: + wages -= allowances * 168.80 + if is_nra: + wages += 93.80 + + if wages > 356 and wages <= 1129: + val = 0.00 + ((wages - 356) * 0.10) + + elif wages > 1129 and wages <= 3494: + val = 77.30 + ((wages - 1129) * 0.15) + + elif wages > 3494 and wages <= 6685: + val = 432.05 + ((wages - 3494) * 0.25) + + elif wages > 6685 and wages <= 10000: + val = 1229.80 + ((wages - 6685) * 0.28) + + elif wages > 10000 and wages <= 17579: + val = 2158.00 + ((wages - 10000) * 0.33) + + elif wages > 17579 and wages <= 19813: + val = 4659.07 + ((wages - 17579) * 0.35) + + elif wages > 19813: + val = 5440.97 + ((wages - 19813) * 0.396) + +### +# Married MONTHLY +### +elif 'monthly' == schedule_pay: + wages -= allowances * 337.50 + if is_nra: + wages += 187.50 + + if wages > 713 and wages <= 2258: + val = 0.00 + ((wages - 713) * 0.10) + + elif wages > 2258 and wages <= 6988: + val = 154.50 + ((wages - 2258) * 0.15) + + elif wages > 6988 and wages <= 13371: + val = 864.00 + ((wages - 6988) * 0.25) + + elif wages > 13371 and wages <= 20000: + val = 2459.75 + ((wages - 13371) * 0.28) + + elif wages > 20000 and wages <= 35158: + val = 4315.87 + ((wages - 20000) * 0.33) + + elif wages > 35158 and wages <= 39625: + val = 9318.01 + ((wages - 35158) * 0.35) + + elif wages > 39625: + val = 10881.46 + ((wages - 39625) * 0.396) + +### +# Married QUARTERLY +### +elif 'quarterly' == schedule_pay: + wages -= allowances * 1012.50 + if is_nra: + wages += 562.50 + + if wages > 2138 and wages <= 6775: + val = 0.00 + ((wages - 2138) * 0.10) + + elif wages > 6775 and wages <= 20963: + val = 463.70 + ((wages - 6775) * 0.15) + + elif wages > 20963 and wages <= 40113: + val = 2591.90 + ((wages - 20963) * 0.25) + + elif wages > 40113 and wages <= 60000: + val = 7379.40 + ((wages - 40113) * 0.28) + + elif wages > 60000 and wages <= 105475: + val = 12947.76 + ((wages - 60000) * 0.33) + + elif wages > 105475 and wages <= 118875: + val = 27954.51 + ((wages - 105475) * 0.35) + + elif wages > 118875: + val = 32644.51 + ((wages - 118875) * 0.396) + +### +# Married SEMIANNUAL +### +elif 'semi-annually' == schedule_pay: + wages -= allowances * 2025.00 + if is_nra: + wages += 1125.0 + + if wages > 4275 and wages <= 13550: + val = 0.00 + ((wages - 4275) * 0.10) + + elif wages > 13550 and wages <= 41925: + val = 927.50 + ((wages - 13550) * 0.15) + + elif wages > 41925 and wages <= 80225: + val = 5183.75 + ((wages - 41925) * 0.25) + + elif wages > 80225 and wages <= 120000: + val = 14758.75 + ((wages - 80225) * 0.28) + + elif wages > 120000 and wages <= 210950: + val = 25895.75 + ((wages - 120000) * 0.33) + + elif wages > 210950 and wages <= 237750: + val = 55909.25 + ((wages - 210950) * 0.35) + + elif wages > 237750: + val = 65289.25 + ((wages - 237750) * 0.396) + +### +# Married ANNUAL +### +elif 'annually' == schedule_pay: + wages -= allowances * 4050.00 + if is_nra: + wages += 2250.0 + + if wages > 8550 and wages <= 27100: + val = 0.00 + ((wages - 8550) * 0.10) + + elif wages > 27100 and wages <= 83850: + val = 1855.00 + ((wages - 27100) * 0.15) + + elif wages > 83850 and wages <= 160450: + val = 10367.50 + ((wages - 83850) * 0.25) + + elif wages > 160450 and wages <= 240000: + val = 29517.50 + ((wages - 160450) * 0.28) + + elif wages > 240000 and wages <= 421900: + val = 51791.50 + ((wages - 240000) * 0.33) + + elif wages > 421900 and wages <= 475500: + val = 111818.50 + ((wages - 421900) * 0.35) + + elif wages > 475500: + val = 130578.50 + ((wages - 475500) * 0.396) + +else: + raise Exception('Invalid schedule_pay="' + schedule_pay + '" for W4 Allowance calculation') + +result = -(val + additional) + + + + + + + + FUTA Federal Unemployment - Wages (2016) + FUTA_WAGES_2016 + python + result = (payslip.date_to[:4] == '2016' and contract.futa_type != contract.FUTA_TYPE_EXEMPT) + code + +### +ytd = payslip.sum('FUTA_WAGES_2016', '2016-01-01', '2017-01-01') +ytd += contract.external_wages +remaining = 7000.0 - ytd +if remaining <= 0.0: + result = 0 +elif remaining < categories.GROSS: + result = remaining +else: + result = categories.GROSS + + + + + + + FUTA Federal Unemployment (2016) + FUTA_2016 + python + result = (payslip.date_to[:4] == '2016' and contract.futa_type != contract.FUTA_TYPE_EXEMPT) + code + +result_rate = -(contract.futa_rate(2016)) +result = categories.FUTA_WAGES + + + + + + + diff --git a/l10n_us_hr_payroll/data/rules_2017.xml b/l10n_us_hr_payroll/data/rules_2017.xml new file mode 100755 index 00000000..d128e7d0 --- /dev/null +++ b/l10n_us_hr_payroll/data/rules_2017.xml @@ -0,0 +1,604 @@ + + + + + + + + + + FICA Employee Social Security Wages (2017) + FICA_EMP_SS_WAGES_2017 + python + result = (payslip.date_to[:4] == '2017') + code + +### +ytd = payslip.sum('FICA_EMP_SS_WAGES_2017', '2017-01-01', '2018-01-01') +ytd += contract.external_wages +remaining = 127200.0 - ytd +if remaining <= 0.0: + result = 0 +elif remaining < categories.GROSS: + result = remaining +else: + result = categories.GROSS + + + + + + + FICA Employee Medicare Wages (2017) + FICA_EMP_M_WAGES_2017 + python + result = (payslip.date_to[:4] == '2017') + code + result = categories.GROSS + + + + + + FICA Employee Medicare Additional Wages (2017) + FICA_EMP_M_ADD_WAGES_2017 + python + result = (payslip.date_to[:4] == '2017') + code + +### +ADD_M = 200000.0 +norm_med_ytd = payslip.sum('FICA_EMP_M_WAGES_2017', '2017-01-01', '2018-01-01') +norm_med_cur = categories.FICA_EMP_M_WAGES +if ADD_M > norm_med_ytd: + diff = ADD_M - norm_med_ytd + if norm_med_cur > diff: + result = norm_med_cur - diff + else: + result = 0 # normal condition +else: + result = norm_med_cur # after YTD wages have passed the max + + + + + + + + FICA Employee Social Security (2017) + FICA_EMP_SS_2017 + python + result = (payslip.date_to[:4] == '2017') + code + +### this should be "rules.FICA_EMP_SS_WAGES_2017", but it doesn't work +result_rate = -6.2 +result = categories.FICA_EMP_SS_WAGES + + + + + + + FICA Employee Medicare (2017) + FICA_EMP_M_2017 + python + result = (payslip.date_to[:4] == '2017') + code + +### this should be "rules.FICA_EMP_M_WAGES_2017", but it doesn't work +result_rate = -1.45 +result = categories.FICA_EMP_M_WAGES + + + + + + + FICA Employee Medicare Additional (2017) + FICA_EMP_M_ADD_2017 + python + result = (payslip.date_to[:4] == '2017') + code + +### this should be "rules.FICA_EMP_M_ADD_WAGES_2017", but it doesn't work +result_rate = -0.9 +result = categories.FICA_EMP_M_ADD_WAGES + + + + + + + + + Federal Income Withholding - Single (2017) + FED_INC_WITHHOLD_2017_S + python + result = (payslip.date_to[:4] == '2017' and contract.w4_filing_status != 'married' and contract.w4_filing_status) + code + +wages = categories.GROSS +allowances = contract.w4_allowances +is_nra = contract.w4_is_nonresident_alien +schedule_pay = contract.schedule_pay +val = 0.00 +additional = contract.w4_additional_withholding + +### +# Single WEEKLY +### +if 'weekly' == schedule_pay: + wages -= allowances * 77.90 + if is_nra: + wages += 44.20 + + if wages > 44 and wages <= 224: + val = 0.00 + ((wages - 44) * 0.10) + + elif wages > 224 and wages <= 774: + val = 18.00 + ((wages - 224) * 0.15) + + elif wages > 774 and wages <= 1812: + val = 100.50 + ((wages - 774) * 0.25) + + elif wages > 1812 and wages <= 3730: + val = 360.00 + ((wages - 1812) * 0.28) + + elif wages > 3730 and wages <= 8058: + val = 897.04 + ((wages - 3730) * 0.33) + + elif wages > 8058 and wages <= 8090: + val = 2325.28 + ((wages - 8058) * 0.35) + + elif wages > 8090: + val = 2336.48 + ((wages - 8090) * 0.396) + +### +# Single BIWEEKLY +### +elif 'bi-weekly' == schedule_pay: + wages -= allowances * 155.80 + if is_nra: + wages += 88.50 + + if wages > 88 and wages <= 447: + val = 0.00 + ((wages - 88) * 0.10) + + elif wages > 447 and wages <= 1548: + val = 35.90 + ((wages - 447) * 0.15) + + elif wages > 1548 and wages <= 3623: + val = 201.05 + ((wages - 1548) * 0.25) + + elif wages > 3623 and wages <= 7460: + val = 719.80 + ((wages - 3623) * 0.28) + + elif wages > 7460 and wages <= 16115: + val = 1794.16 + ((wages - 7460) * 0.33) + + elif wages > 16115 and wages <= 16181: + val = 4650.31 + ((wages - 16115) * 0.35) + + elif wages > 16181: + val = 4673.41 + ((wages - 16181) * 0.396) + +### +# Single SEMIMONTHLY +### +elif 'semi-monthly' == schedule_pay: + wages -= allowances * 168.80 + if is_nra: + wages += 95.80 + + if wages > 96 and wages <= 484: + val = 0.00 + ((wages - 96) * 0.10) + + elif wages > 484 and wages <= 1677: + val = 38.80 + ((wages - 484) * 0.15) + + elif wages > 1677 and wages <= 3925: + val = 217.75 + ((wages - 1677) * 0.25) + + elif wages > 3925 and wages <= 8081: + val = 779.75 + ((wages - 3925) * 0.28) + + elif wages > 8081 and wages <= 17458: + val = 1943.43 + ((wages - 8081) * 0.33) + + elif wages > 17458 and wages <= 17529: + val = 5037.84 + ((wages - 17458) * 0.35) + + elif wages > 17529: + val = 5062.69 + ((wages - 17529) * 0.396) + +### +# Single MONTHLY +### +elif 'monthly' == schedule_pay: + wages -= allowances * 337.50 + if is_nra: + wages += 191.70 + + if wages > 192 and wages <= 969: + val = 0.00 + ((wages - 192) * 0.10) + + elif wages > 969 and wages <= 3354: + val = 77.70 + ((wages - 969) * 0.15) + + elif wages > 3354 and wages <= 7850: + val = 435.45 + ((wages - 3354) * 0.25) + + elif wages > 7850 and wages <= 16163: + val = 1559.45 + ((wages - 7850) * 0.28) + + elif wages > 16163 and wages <= 34917: + val = 3887.09 + ((wages - 16163) * 0.33) + + elif wages > 34917 and wages <= 35058: + val = 10075.91 + ((wages - 34917) * 0.35) + + elif wages > 35058: + val = 10125.26 + ((wages - 35058) * 0.396) + +### +# Single QUARTERLY +### +elif 'quarterly' == schedule_pay: + wages -= allowances * 1012.50 + if is_nra: + wages += 575.00 + + if wages > 575 and wages <= 2906: + val = 0.00 + ((wages - 575) * 0.10) + + elif wages > 2906 and wages <= 10063: + val = 233.10 + ((wages - 2906) * 0.15) + + elif wages > 10063 and wages <= 23550: + val = 1306.65 + ((wages - 10063) * 0.25) + + elif wages > 23550 and wages <= 48488: + val = 4678.40 + ((wages - 23550) * 0.28) + + elif wages > 48488 and wages <= 104750: + val = 11661.04 + ((wages - 48488) * 0.33) + + elif wages > 104750 and wages <= 105175: + val = 30227.50 + ((wages - 104750) * 0.35) + + elif wages > 105175: + val = 30376.25 + ((wages - 105175) * 0.396) + +### +# Single SEMIANNUAL +### +elif 'semi-annually' == schedule_pay: + wages -= allowances * 2025.00 + if is_nra: + wages += 1150.0 + + if wages > 1150 and wages <= 5813: + val = 0.00 + ((wages - 1150) * 0.10) + + elif wages > 5813 and wages <= 20125: + val = 466.30 + ((wages - 5813) * 0.15) + + elif wages > 20125 and wages <= 47100: + val = 2613.10 + ((wages - 20125) * 0.25) + + elif wages > 47100 and wages <= 96975: + val = 9356.85 + ((wages - 47100) * 0.28) + + elif wages > 96975 and wages <= 209500: + val = 23321.85 + ((wages - 96975) * 0.33) + + elif wages > 209500 and wages <= 210350: + val = 60455.10 + ((wages - 209500) * 0.35) + + elif wages > 210350: + val = 60752.60 + ((wages - 210350) * 0.396) + +### +# Single ANNUAL +### +elif 'annually' == schedule_pay: + wages -= allowances * 4050.00 + if is_nra: + wages += 2300.0 + + if wages > 2300 and wages <= 11625: + val = 0.00 + ((wages - 2300) * 0.10) + + elif wages > 11625 and wages <= 40250: + val = 932.50 + ((wages - 11625) * 0.15) + + elif wages > 40250 and wages <= 94200: + val = 5226.25 + ((wages - 40250) * 0.25) + + elif wages > 94200 and wages <= 193950: + val = 18713.75 + ((wages - 94200) * 0.28) + + elif wages > 193950 and wages <= 419000: + val = 46643.75 + ((wages - 193950) * 0.33) + + elif wages > 419000 and wages <= 420700: + val = 120910.25 + ((wages - 419000) * 0.35) + + elif wages > 420700: + val = 121505.25 + ((wages - 420700) * 0.396) + +else: + raise Exception('Invalid schedule_pay="' + schedule_pay + '" for W4 Allowance calculation') + +result = -(val + additional) + + + + + + + Federal Income Withholding - Married (2017) + FED_INC_WITHHOLD_2017_M + python + result = (payslip.date_to[:4] == '2017' and contract.w4_filing_status == 'married') + code + +wages = categories.GROSS +allowances = contract.w4_allowances +is_nra = contract.w4_is_nonresident_alien +schedule_pay = contract.schedule_pay +val = 0.00 +additional = contract.w4_additional_withholding + +### +# Married WEEKLY +### +if 'weekly' == schedule_pay: + wages -= allowances * 77.90 + if is_nra: + wages += 44.20 + + if wages > 166 and wages <= 525: + val = 0.00 + ((wages - 166) * 0.10) + + elif wages > 525 and wages <= 1626: + val = 35.90 + ((wages - 525) * 0.15) + + elif wages > 1626 and wages <= 3111: + val = 201.05 + ((wages - 1626) * 0.25) + + elif wages > 3111 and wages <= 4654: + val = 572.30 + ((wages - 3111) * 0.28) + + elif wages > 4654 and wages <= 8180: + val = 1004.34 + ((wages - 4654) * 0.33) + + elif wages > 8180 and wages <= 9218: + val = 2167.92 + ((wages - 8180) * 0.35) + + elif wages > 9218: + val = 2531.22 + ((wages - 9218) * 0.396) + +### +# Married BIWEEKLY +### +elif 'bi-weekly' == schedule_pay: + wages -= allowances * 155.80 + if is_nra: + wages += 88.50 + + if wages > 333 and wages <= 1050: + val = 0.00 + ((wages - 333) * 0.10) + + elif wages > 1050 and wages <= 3252: + val = 71.70 + ((wages - 1050) * 0.15) + + elif wages > 3252 and wages <= 6221: + val = 402.00 + ((wages - 3252) * 0.25) + + elif wages > 6221 and wages <= 9308: + val = 1144.25 + ((wages - 6221) * 0.28) + + elif wages > 9308 and wages <= 16360: + val = 2008.61 + ((wages - 9308) * 0.33) + + elif wages > 16360 and wages <= 18437: + val = 4335.77 + ((wages - 16360) * 0.35) + + elif wages > 18437: + val = 5062.72 + ((wages - 18437) * 0.396) + +### +# Married SEMIMONTHLY +### +elif 'semi-monthly' == schedule_pay: + wages -= allowances * 168.80 + if is_nra: + wages += 95.80 + + if wages > 360 and wages <= 1138: + val = 0.00 + ((wages - 360) * 0.10) + + elif wages > 1138 and wages <= 3523: + val = 77.80 + ((wages - 1138) * 0.15) + + elif wages > 3523 and wages <= 6740: + val = 435.55 + ((wages - 3523) * 0.25) + + elif wages > 6740 and wages <= 10083: + val = 1239.80 + ((wages - 6740) * 0.28) + + elif wages > 10083 and wages <= 17723: + val = 2175.84 + ((wages - 10083) * 0.33) + + elif wages > 17723 and wages <= 19973: + val = 4697.04 + ((wages - 17723) * 0.35) + + elif wages > 19973: + val = 5484.54 + ((wages - 19973) * 0.396) + +### +# Married MONTHLY +### +elif 'monthly' == schedule_pay: + wages -= allowances * 337.50 + if is_nra: + wages += 191.70 + + if wages > 721 and wages <= 2275: + val = 0.00 + ((wages - 721) * 0.10) + + elif wages > 2275 and wages <= 7046: + val = 155.40 + ((wages - 2275) * 0.15) + + elif wages > 7046 and wages <= 13479: + val = 871.05 + ((wages - 7046) * 0.25) + + elif wages > 13479 and wages <= 20167: + val = 2479.30 + ((wages - 13479) * 0.28) + + elif wages > 20167 and wages <= 35446: + val = 4351.94 + ((wages - 20167) * 0.33) + + elif wages > 35446 and wages <= 39946: + val = 9394.01 + ((wages - 35446) * 0.35) + + elif wages > 39946: + val = 10969.01 + ((wages - 39946) * 0.396) + +### +# Married QUARTERLY +### +elif 'quarterly' == schedule_pay: + wages -= allowances * 1012.50 + if is_nra: + wages += 575.00 + + if wages > 2163 and wages <= 6825: + val = 0.00 + ((wages - 2163) * 0.10) + + elif wages > 6825 and wages <= 21138: + val = 466.20 + ((wages - 6825) * 0.15) + + elif wages > 21138 and wages <= 40438: + val = 2613.15 + ((wages - 21138) * 0.25) + + elif wages > 40438 and wages <= 60500: + val = 7438.15 + ((wages - 40438) * 0.28) + + elif wages > 60500 and wages <= 106338: + val = 13055.51 + ((wages - 60500) * 0.33) + + elif wages > 106338 and wages <= 119838: + val = 28182.05 + ((wages - 106338) * 0.35) + + elif wages > 119838: + val = 32907.05 + ((wages - 119838) * 0.396) + +### +# Married SEMIANNUAL +### +elif 'semi-annually' == schedule_pay: + wages -= allowances * 2025.00 + if is_nra: + wages += 1150.0 + + if wages > 4325 and wages <= 13650: + val = 0.00 + ((wages - 4325) * 0.10) + + elif wages > 13650 and wages <= 42275: + val = 932.50 + ((wages - 13650) * 0.15) + + elif wages > 42275 and wages <= 80875: + val = 5226.25 + ((wages - 42275) * 0.25) + + elif wages > 80875 and wages <= 121000: + val = 14876.25 + ((wages - 80875) * 0.28) + + elif wages > 121000 and wages <= 212675: + val = 26111.25 + ((wages - 121000) * 0.33) + + elif wages > 212675 and wages <= 239675: + val = 56364.00 + ((wages - 212675) * 0.35) + + elif wages > 239675: + val = 65814.00 + ((wages - 239675) * 0.396) + +### +# Married ANNUAL +### +elif 'annually' == schedule_pay: + wages -= allowances * 4050.00 + if is_nra: + wages += 2300.0 + + if wages > 8650 and wages <= 27300: + val = 0.00 + ((wages - 8650) * 0.10) + + elif wages > 27300 and wages <= 84550: + val = 1865.00 + ((wages - 27300) * 0.15) + + elif wages > 84550 and wages <= 161750: + val = 10452.50 + ((wages - 84550) * 0.25) + + elif wages > 161750 and wages <= 242000: + val = 29752.50 + ((wages - 161750) * 0.28) + + elif wages > 242000 and wages <= 425350: + val = 52222.50 + ((wages - 242000) * 0.33) + + elif wages > 425350 and wages <= 479350: + val = 112728.00 + ((wages - 425350) * 0.35) + + elif wages > 479350: + val = 131628.00 + ((wages - 479350) * 0.396) + +else: + raise Exception('Invalid schedule_pay="' + schedule_pay + '" for W4 Allowance calculation') + +result = -(val + additional) + + + + + + + + FUTA Federal Unemployment - Wages (2017) + FUTA_WAGES_2017 + python + result = (payslip.date_to[:4] == '2017' and contract.futa_type != contract.FUTA_TYPE_EXEMPT) + code + +### +ytd = payslip.sum('FUTA_WAGES_2017', '2017-01-01', '2018-01-01') +ytd += contract.external_wages +remaining = 7000.0 - ytd +if remaining <= 0.0: + result = 0 +elif remaining < categories.GROSS: + result = remaining +else: + result = categories.GROSS + + + + + + + FUTA Federal Unemployment (2017) + FUTA_2017 + python + result = (payslip.date_to[:4] == '2017' and contract.futa_type != contract.FUTA_TYPE_EXEMPT) + code + +result_rate = -(contract.futa_rate(2017)) +result = categories.FUTA_WAGES + + + + + + + diff --git a/l10n_us_hr_payroll/data/rules_2018.xml b/l10n_us_hr_payroll/data/rules_2018.xml new file mode 100755 index 00000000..a774ae09 --- /dev/null +++ b/l10n_us_hr_payroll/data/rules_2018.xml @@ -0,0 +1,604 @@ + + + + + + + + + + FICA Employee Social Security Wages (2018) + FICA_EMP_SS_WAGES_2018 + python + result = (payslip.date_to[:4] == '2018') + code + +### +ytd = payslip.sum('FICA_EMP_SS_WAGES_2018', '2018-01-01', '2019-01-01') +ytd += contract.external_wages +remaining = 128400.0 - ytd +if remaining <= 0.0: + result = 0 +elif remaining < categories.GROSS: + result = remaining +else: + result = categories.GROSS + + + + + + + FICA Employee Medicare Wages (2018) + FICA_EMP_M_WAGES_2018 + python + result = (payslip.date_to[:4] == '2018') + code + result = categories.GROSS + + + + + + FICA Employee Medicare Additional Wages (2018) + FICA_EMP_M_ADD_WAGES_2018 + python + result = (payslip.date_to[:4] == '2018') + code + +### +ADD_M = 200000.0 +norm_med_ytd = payslip.sum('FICA_EMP_M_WAGES_2018', '2018-01-01', '2019-01-01') +norm_med_cur = categories.FICA_EMP_M_WAGES +if ADD_M > norm_med_ytd: + diff = ADD_M - norm_med_ytd + if norm_med_cur > diff: + result = norm_med_cur - diff + else: + result = 0 # normal condition +else: + result = norm_med_cur # after YTD wages have passed the max + + + + + + + + FICA Employee Social Security (2018) + FICA_EMP_SS_2018 + python + result = (payslip.date_to[:4] == '2018') + code + +### this should be "rules.FICA_EMP_SS_WAGES_2018", but it doesn't work +result_rate = -6.2 +result = categories.FICA_EMP_SS_WAGES + + + + + + + FICA Employee Medicare (2018) + FICA_EMP_M_2018 + python + result = (payslip.date_to[:4] == '2018') + code + +### this should be "rules.FICA_EMP_M_WAGES_2018", but it doesn't work +result_rate = -1.45 +result = categories.FICA_EMP_M_WAGES + + + + + + + FICA Employee Medicare Additional (2018) + FICA_EMP_M_ADD_2018 + python + result = (payslip.date_to[:4] == '2018') + code + +### this should be "rules.FICA_EMP_M_ADD_WAGES_2018", but it doesn't work +result_rate = -0.9 +result = categories.FICA_EMP_M_ADD_WAGES + + + + + + + + + Federal Income Withholding - Single (2018) + FED_INC_WITHHOLD_2018_S + python + result = (payslip.date_to[:4] == '2018' and contract.w4_filing_status != 'married' and contract.w4_filing_status) + code + +wages = categories.GROSS +allowances = contract.w4_allowances +is_nra = contract.w4_is_nonresident_alien +schedule_pay = contract.schedule_pay +val = 0.00 +additional = contract.w4_additional_withholding + +### +# Single WEEKLY +### +if 'weekly' == schedule_pay: + wages -= allowances * 79.80 + if is_nra: + wages += 151.00 + + if wages > 71 and wages <= 254: + val = 0.00 + ((wages - 71) * 0.10) + + elif wages > 254 and wages <= 815: + val = 18.30 + ((wages - 254) * 0.12) + + elif wages > 815 and wages <= 1658: + val = 85.62 + ((wages - 815) * 0.22) + + elif wages > 1658 and wages <= 3100: + val = 271.08 + ((wages - 1658) * 0.24) + + elif wages > 3100 and wages <= 3917: + val = 617.16 + ((wages - 3100) * 0.32) + + elif wages > 3917 and wages <= 9687: + val = 878.60 + ((wages - 3917) * 0.35) + + elif wages > 9687: + val = 2898.10 + ((wages - 9687) * 0.37) + +### +# Single BIWEEKLY +### +elif 'bi-weekly' == schedule_pay: + wages -= allowances * 159.60 + if is_nra: + wages += 301.90 + + if wages > 142 and wages <= 509: + val = 0.00 + ((wages - 142) * 0.10) + + elif wages > 509 and wages <= 1631: + val = 36.70 + ((wages - 509) * 0.12) + + elif wages > 1631 and wages <= 3315: + val = 171.34 + ((wages - 1631) * 0.22) + + elif wages > 3315 and wages <= 6200: + val = 541.82 + ((wages - 3315) * 0.24) + + elif wages > 6200 and wages <= 7835: + val = 1234.22 + ((wages - 6200) * 0.32) + + elif wages > 7835 and wages <= 19373: + val = 1757.42 + ((wages - 7835) * 0.35) + + elif wages > 19373: + val = 5795.72 + ((wages - 19373) * 0.37) + +### +# Single SEMIMONTHLY +### +elif 'semi-monthly' == schedule_pay: + wages -= allowances * 172.90 + if is_nra: + wages += 327.10 + + if wages > 154 and wages <= 551: + val = 0.00 + ((wages - 154) * 0.10) + + elif wages > 551 and wages <= 1767: + val = 39.70 + ((wages - 551) * 0.12) + + elif wages > 1767 and wages <= 3592: + val = 185.62 + ((wages - 1767) * 0.22) + + elif wages > 3592 and wages <= 6717: + val = 587.12 + ((wages - 3592) * 0.24) + + elif wages > 6717 and wages <= 8488: + val = 1337.12 + ((wages - 6717) * 0.32) + + elif wages > 8488 and wages <= 20988: + val = 1903.84 + ((wages - 8488) * 0.35) + + elif wages > 20988: + val = 6278.84 + ((wages - 20988) * 0.37) + +### +# Single MONTHLY +### +elif 'monthly' == schedule_pay: + wages -= allowances * 345.80 + if is_nra: + wages += 654.20 + + if wages > 308 and wages <= 1102: + val = 0.00 + ((wages - 308) * 0.10) + + elif wages > 1102 and wages <= 3533: + val = 79.40 + ((wages - 1102) * 0.12) + + elif wages > 3533 and wages <= 7183: + val = 371.12 + ((wages - 3533) * 0.22) + + elif wages > 7183 and wages <= 13433: + val = 1174.12 + ((wages - 7183) * 0.24) + + elif wages > 13433 and wages <= 16975: + val = 2674.12 + ((wages - 13433) * 0.32) + + elif wages > 16975 and wages <= 41975: + val = 3807.56 + ((wages - 16975) * 0.35) + + elif wages > 41975: + val = 12557.56 + ((wages - 41975) * 0.37) + +### +# Single QUARTERLY +### +elif 'quarterly' == schedule_pay: + wages -= allowances * 1037.50 + if is_nra: + wages += 1962.50 + + if wages > 925 and wages <= 3306: + val = 0.00 + ((wages - 925) * 0.10) + + elif wages > 3306 and wages <= 10600: + val = 238.10 + ((wages - 3306) * 0.12) + + elif wages > 10600 and wages <= 21550: + val = 1113.38 + ((wages - 10600) * 0.22) + + elif wages > 21550 and wages <= 40300: + val = 3522.38 + ((wages - 21550) * 0.24) + + elif wages > 40300 and wages <= 50925: + val = 8022.38 + ((wages - 40300) * 0.32) + + elif wages > 50925 and wages <= 125925: + val = 11422.38 + ((wages - 50925) * 0.35) + + elif wages > 125925: + val = 37672.38 + ((wages - 125925) * 0.37) + +### +# Single SEMIANNUAL +### +elif 'semi-annually' == schedule_pay: + wages -= allowances * 2075.00 + if is_nra: + wages += 3925.00 + + if wages > 1850 and wages <= 6613: + val = 0.00 + ((wages - 1850) * 0.10) + + elif wages > 6613 and wages <= 21200: + val = 476.30 + ((wages - 6613) * 0.12) + + elif wages > 21200 and wages <= 43100: + val = 2226.74 + ((wages - 21200) * 0.22) + + elif wages > 43100 and wages <= 80600: + val = 7044.74 + ((wages - 43100) * 0.24) + + elif wages > 80600 and wages <= 101850: + val = 16044.74 + ((wages - 80600) * 0.32) + + elif wages > 101850 and wages <= 251850: + val = 22844.74 + ((wages - 101850) * 0.35) + + elif wages > 251850: + val = 75344.74 + ((wages - 251850) * 0.37) + +### +# Single ANNUAL +### +elif 'annually' == schedule_pay: + wages -= allowances * 4150.00 + if is_nra: + wages += 7850.00 + + if wages > 3700 and wages <= 13225: + val = 0.00 + ((wages - 3700) * 0.10) + + elif wages > 13225 and wages <= 42400: + val = 952.50 + ((wages - 13225) * 0.12) + + elif wages > 42400 and wages <= 86200: + val = 4453.50 + ((wages - 42400) * 0.22) + + elif wages > 86200 and wages <= 161200: + val = 14089.50 + ((wages - 86200) * 0.24) + + elif wages > 161200 and wages <= 203700: + val = 32089.50 + ((wages - 161200) * 0.32) + + elif wages > 203700 and wages <= 503700: + val = 45689.50 + ((wages - 203700) * 0.35) + + elif wages > 503700: + val = 150689.50 + ((wages - 503700) * 0.37) + +else: + raise Exception('Invalid schedule_pay="' + schedule_pay + '" for W4 Allowance calculation') + +result = -(val + additional) + + + + + + + Federal Income Withholding - Married (2018) + FED_INC_WITHHOLD_2018_M + python + result = (payslip.date_to[:4] == '2018' and contract.w4_filing_status == 'married') + code + +wages = categories.GROSS +allowances = contract.w4_allowances +is_nra = contract.w4_is_nonresident_alien +schedule_pay = contract.schedule_pay +val = 0.00 +additional = contract.w4_additional_withholding + +### +# Married WEEKLY +### +if 'weekly' == schedule_pay: + wages -= allowances * 79.80 + if is_nra: + wages += 151.00 + + if wages > 222 and wages <= 588: + val = 0.00 + ((wages - 222) * 0.10) + + elif wages > 588 and wages <= 1711: + val = 36.60 + ((wages - 588) * 0.12) + + elif wages > 1711 and wages <= 3395: + val = 171.36 + ((wages - 1711) * 0.22) + + elif wages > 3395 and wages <= 6280: + val = 541.84 + ((wages - 3395) * 0.24) + + elif wages > 6280 and wages <= 7914: + val = 1234.24 + ((wages - 6280) * 0.32) + + elif wages > 7914 and wages <= 11761: + val = 1757.12 + ((wages - 7914) * 0.35) + + elif wages > 11761: + val = 3103.57 + ((wages - 11761) * 0.37) + +### +# Married BIWEEKLY +### +elif 'bi-weekly' == schedule_pay: + wages -= allowances * 159.60 + if is_nra: + wages += 301.90 + + if wages > 444 and wages <= 1177: + val = 0.00 + ((wages - 444) * 0.10) + + elif wages > 1177 and wages <= 3421: + val = 73.30 + ((wages - 1177) * 0.12) + + elif wages > 3421 and wages <= 6790: + val = 342.58 + ((wages - 3421) * 0.22) + + elif wages > 6790 and wages <= 12560: + val = 1083.76 + ((wages - 6790) * 0.24) + + elif wages > 12560 and wages <= 15829: + val = 2468.56 + ((wages - 12560) * 0.32) + + elif wages > 15829 and wages <= 23521: + val = 3514.64 + ((wages - 15829) * 0.35) + + elif wages > 23521: + val = 6206.84 + ((wages - 23521) * 0.37) + +### +# Married SEMIMONTHLY +### +elif 'semi-monthly' == schedule_pay: + wages -= allowances * 172.90 + if is_nra: + wages += 327.10 + + if wages > 481 and wages <= 1275: + val = 0.00 + ((wages - 481) * 0.10) + + elif wages > 1275 and wages <= 3706: + val = 79.40 + ((wages - 1275) * 0.12) + + elif wages > 3706 and wages <= 7356: + val = 371.12 + ((wages - 3706) * 0.22) + + elif wages > 7356 and wages <= 13606: + val = 1174.12 + ((wages - 7356) * 0.24) + + elif wages > 13606 and wages <= 17148: + val = 2674.12 + ((wages - 13606) * 0.32) + + elif wages > 17148 and wages <= 25481: + val = 3807.56 + ((wages - 17148) * 0.35) + + elif wages > 25481: + val = 6724.11 + ((wages - 25481) * 0.37) + +### +# Married MONTHLY +### +elif 'monthly' == schedule_pay: + wages -= allowances * 345.80 + if is_nra: + wages += 654.20 + + if wages > 963 and wages <= 2550: + val = 0.00 + ((wages - 963) * 0.10) + + elif wages > 2550 and wages <= 7413: + val = 158.70 + ((wages - 2550) * 0.12) + + elif wages > 7413 and wages <= 14713: + val = 742.26 + ((wages - 7413) * 0.22) + + elif wages > 14713 and wages <= 27213: + val = 2348.26 + ((wages - 14713) * 0.24) + + elif wages > 27213 and wages <= 34296: + val = 5348.26 + ((wages - 27213) * 0.32) + + elif wages > 34296 and wages <= 50963: + val = 7614.82 + ((wages - 34296) * 0.35) + + elif wages > 50963: + val = 13448.27 + ((wages - 50963) * 0.37) + +### +# Married QUARTERLY +### +elif 'quarterly' == schedule_pay: + wages -= allowances * 1037.50 + if is_nra: + wages += 1962.50 + + if wages > 2888 and wages <= 7650: + val = 0.00 + ((wages - 2888) * 0.10) + + elif wages > 7650 and wages <= 22238: + val = 476.20 + ((wages - 7650) * 0.12) + + elif wages > 22238 and wages <= 44138: + val = 2226.76 + ((wages - 22238) * 0.22) + + elif wages > 44138 and wages <= 81638: + val = 7044.76 + ((wages - 44138) * 0.24) + + elif wages > 81638 and wages <= 102888: + val = 16044.76 + ((wages - 81638) * 0.32) + + elif wages > 102888 and wages <= 152888: + val = 22844.76 + ((wages - 102888) * 0.35) + + elif wages > 152888: + val = 40344.76 + ((wages - 152888) * 0.37) + +### +# Married SEMIANNUAL +### +elif 'semi-annually' == schedule_pay: + wages -= allowances * 2075.00 + if is_nra: + wages += 3925.00 + + if wages > 5775 and wages <= 15300: + val = 0.00 + ((wages - 5775) * 0.10) + + elif wages > 15300 and wages <= 44475: + val = 952.50 + ((wages - 15300) * 0.12) + + elif wages > 44475 and wages <= 88275: + val = 4453.50 + ((wages - 44475) * 0.22) + + elif wages > 88275 and wages <= 163275: + val = 14089.50 + ((wages - 88275) * 0.24) + + elif wages > 163275 and wages <= 205775: + val = 32089.50 + ((wages - 163275) * 0.32) + + elif wages > 205775 and wages <= 305775: + val = 45689.50 + ((wages - 205775) * 0.35) + + elif wages > 305775: + val = 80689.50 + ((wages - 305775) * 0.37) + +### +# Married ANNUAL +### +elif 'annually' == schedule_pay: + wages -= allowances * 4150.00 + if is_nra: + wages += 7850.00 + + if wages > 11550 and wages <= 30600: + val = 0.00 + ((wages - 11550) * 0.10) + + elif wages > 30600 and wages <= 88950: + val = 1905.00 + ((wages - 30600) * 0.12) + + elif wages > 88950 and wages <= 176550: + val = 8907.00 + ((wages - 88950) * 0.22) + + elif wages > 176550 and wages <= 326550: + val = 28179.00 + ((wages - 176550) * 0.24) + + elif wages > 326550 and wages <= 411550: + val = 64179.00 + ((wages - 326550) * 0.32) + + elif wages > 411550 and wages <= 611550: + val = 91379.00 + ((wages - 411550) * 0.35) + + elif wages > 611550: + val = 161379.00 + ((wages - 611550) * 0.37) + +else: + raise Exception('Invalid schedule_pay="' + schedule_pay + '" for W4 Allowance calculation') + +result = -(val + additional) + + + + + + + + FUTA Federal Unemployment - Wages (2018) + FUTA_WAGES_2018 + python + result = (payslip.date_to[:4] == '2018' and contract.futa_type != contract.FUTA_TYPE_EXEMPT) + code + +### +ytd = payslip.sum('FUTA_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.GROSS: + result = remaining +else: + result = categories.GROSS + + + + + + + FUTA Federal Unemployment (2018) + FUTA_2018 + python + result = (payslip.date_to[:4] == '2018' and contract.futa_type != contract.FUTA_TYPE_EXEMPT) + code + +result_rate = -(contract.futa_rate(2018)) +result = categories.FUTA_WAGES + + + + + + + diff --git a/l10n_us_hr_payroll/l10n_us_hr_payroll.py b/l10n_us_hr_payroll/l10n_us_hr_payroll.py new file mode 100755 index 00000000..68099b6e --- /dev/null +++ b/l10n_us_hr_payroll/l10n_us_hr_payroll.py @@ -0,0 +1,47 @@ +from odoo import models, fields, api + + +class USHrContract(models.Model): + FUTA_TYPE_EXEMPT = 'exempt' + FUTA_TYPE_BASIC = 'basic' + FUTA_TYPE_NORMAL = 'normal' + FUTA_YEARS_VALID = ( + 2016, + 2017, + 2018, + ) + + _inherit = 'hr.contract' + + schedule_pay = fields.Selection(selection_add=[('semi-monthly', 'Semi-monthly')]) + w4_allowances = fields.Integer(string='Federal W4 Allowances', default=0) + w4_filing_status = fields.Selection([ + ('', 'Exempt'), + ('single', 'Single'), + ('married', 'Married'), + ('married_as_single', 'Married but at Single Rate'), + ], string='Federal W4 Filing Status', default='single') + w4_is_nonresident_alien = fields.Boolean(string="Federal W4 Is Nonresident Alien", default=False) + w4_additional_withholding = fields.Float(string="Federal W4 Additional Withholding", default=0.0) + + external_wages = fields.Float(string='External Existing Wages', default=0.0) + + futa_type = fields.Selection([ + (FUTA_TYPE_EXEMPT, 'Exempt (0%)'), + (FUTA_TYPE_NORMAL, 'Normal Net Rate (0.6%)'), + (FUTA_TYPE_BASIC, 'Basic Rate (6%)'), + ], string="Federal Unemployment Tax Type (FUTA)", default='normal') + + @api.multi + def futa_rate(self, year): + self.ensure_one() + + if year not in self.FUTA_YEARS_VALID: + raise NotImplemented('FUTA rate for Year: ' + str(year) + ' not known.') + + if self.futa_type == self.FUTA_TYPE_EXEMPT: + return 0.0 + elif self.futa_type == self.FUTA_TYPE_NORMAL: + return 0.6 + else: + return 6.0 diff --git a/l10n_us_hr_payroll/l10n_us_hr_payroll_view.xml b/l10n_us_hr_payroll/l10n_us_hr_payroll_view.xml new file mode 100755 index 00000000..9280da73 --- /dev/null +++ b/l10n_us_hr_payroll/l10n_us_hr_payroll_view.xml @@ -0,0 +1,27 @@ + + + + + hr.contract.form.inherit + hr.contract + 20 + + + + + + + + + + + + + + + + + + + + diff --git a/l10n_us_hr_payroll/tests/__init__.py b/l10n_us_hr_payroll/tests/__init__.py new file mode 100755 index 00000000..774c98d0 --- /dev/null +++ b/l10n_us_hr_payroll/tests/__init__.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- + +from . import test_us_payslip +from . import test_us_payslip_2016 +from . import test_us_payslip_2017 +from . import test_us_payslip_2018 diff --git a/l10n_us_hr_payroll/tests/test_us_payslip.py b/l10n_us_hr_payroll/tests/test_us_payslip.py new file mode 100755 index 00000000..196ec850 --- /dev/null +++ b/l10n_us_hr_payroll/tests/test_us_payslip.py @@ -0,0 +1,113 @@ +# -*- coding: utf-8 -*- + +from logging import getLogger +from sys import float_info as sys_float_info + +from odoo.tests import common +from odoo.tools.float_utils import float_round as odoo_float_round +from odoo.addons.l10n_us_hr_payroll.l10n_us_hr_payroll import USHrContract + + +def process_payslip(payslip): + try: + #v9 + payslip.process_sheet() + except AttributeError: + payslip.action_payslip_done() + + +class TestUsPayslip(common.TransactionCase): + debug = False + _logger = getLogger(__name__) + + float_info = sys_float_info + + def float_round(self, value, digits): + return odoo_float_round(value, digits) + + _payroll_digits = -1 + + @property + def payroll_digits(self): + if self._payroll_digits == -1: + self._payroll_digits = self.env['decimal.precision'].precision_get('Payroll') + return self._payroll_digits + + def _log(self, message): + if self.debug: + self._logger.warn(message) + + def _createEmployee(self): + return self.env['hr.employee'].create({ + 'birthday': '1985-03-14', + 'country_id': self.ref('base.us'), + 'department_id': self.ref('hr.dep_rd'), + 'gender': 'male', + 'name': 'Jared' + }) + + def _createContract(self, employee, salary, + schedule_pay='monthly', + w4_allowances=0, + w4_filing_status='single', + w4_is_nonresident_alien=False, + w4_additional_withholding=0.0, + external_wages=0.0, + struct_id=False, + futa_type=USHrContract.FUTA_TYPE_NORMAL, + ): + if not struct_id: + struct_id = self.ref('l10n_us_hr_payroll.hr_payroll_salary_structure_us_employee') + + return self.env['hr.contract'].create({ + 'date_start': '2016-01-01', + 'date_end': '2030-12-31', + 'name': 'Contract for Jared 2016', + 'wage': salary, + 'type_id': self.ref('hr_contract.hr_contract_type_emp'), + 'employee_id': employee.id, + 'struct_id': struct_id, + 'resource_calendar_id': self.ref('resource.resource_calendar_std'), + 'schedule_pay': schedule_pay, + 'w4_allowances': w4_allowances, + 'w4_filing_status': w4_filing_status, + 'w4_is_nonresident_alien': w4_is_nonresident_alien, + 'w4_additional_withholding': w4_additional_withholding, + 'external_wages': external_wages, + 'futa_type': futa_type, + 'state': 'open', # if not "Running" then no automatic selection when Payslip is created + }) + + def _createPayslip(self, employee, date_from, date_to): + return self.env['hr.payslip'].create({ + 'employee_id': employee.id, + 'date_from': date_from, + 'date_to': date_to + }) + + def _getCategories(self, payslip): + detail_lines = payslip.details_by_salary_rule_category + categories = {} + for line in detail_lines: + self._log(' line code: ' + str(line.code) + + ' category code: ' + line.category_id.code + + ' total: ' + str(line.total) + + ' rate: ' + str(line.rate) + + ' amount: ' + str(line.amount)) + if line.category_id.code not in categories: + categories[line.category_id.code] = line.total + else: + categories[line.category_id.code] += line.total + + return categories + + def assertPayrollEqual(self, first, second): + self.assertAlmostEqual(first, second, self.payroll_digits) + + def test_semi_monthly(self): + salary = 80000.0 + employee = self._createEmployee() + contract = self._createContract(employee, salary, schedule_pay='semi-monthly') + payslip = self._createPayslip(employee, '2016-01-01', '2016-01-14') + + payslip.compute_sheet() diff --git a/l10n_us_hr_payroll/tests/test_us_payslip_2016.py b/l10n_us_hr_payroll/tests/test_us_payslip_2016.py new file mode 100755 index 00000000..0503d0c4 --- /dev/null +++ b/l10n_us_hr_payroll/tests/test_us_payslip_2016.py @@ -0,0 +1,377 @@ +from .test_us_payslip import TestUsPayslip, process_payslip + +from odoo.addons.l10n_us_hr_payroll.l10n_us_hr_payroll import USHrContract + + +class TestUsPayslip2016(TestUsPayslip): + FUTA_RATE_NORMAL_2016 = 0.6 + FUTA_RATE_BASIC_2016 = 6.0 + FUTA_RATE_EXEMPT_2016 = 0.0 + + + ### + # 2016 Taxes and Rates + ### + + def test_2016_taxes(self): + # salary is high so that second payslip runs over max + # social security salary + salary = 80000.0 + + ## tax rates + FICA_SS = -0.062 + FICA_M = -0.0145 + FUTA = -self.FUTA_RATE_NORMAL_2016 / 100.0 + FICA_M_ADD = -0.009 + + ## tax maximums + FICA_SS_MAX_WAGE = 118500.0 + FICA_M_MAX_WAGE = self.float_info.max + FICA_M_ADD_START_WAGE = 200000.0 + FUTA_MAX_WAGE = 7000.0 + + employee = self._createEmployee() + + contract = self._createContract(employee, salary) + + self._log('2016 tax first payslip:') + payslip = self._createPayslip(employee, '2016-01-01', '2016-01-31') + + payslip.compute_sheet() + + cats = self._getCategories(payslip) + + self.assertPayrollEqual(cats['FICA_EMP_SS_WAGES'], salary) + self.assertPayrollEqual(cats['FICA_EMP_SS'], cats['FICA_EMP_SS_WAGES'] * FICA_SS) + self.assertPayrollEqual(cats['FICA_EMP_M_WAGES'], salary) + self.assertPayrollEqual(cats['FICA_EMP_M'], cats['FICA_EMP_M_WAGES'] * FICA_M) + self.assertPayrollEqual(cats['FICA_EMP_M_ADD_WAGES'], 0.0) + self.assertPayrollEqual(cats['FICA_EMP_M_ADD'], 0.0) + self.assertPayrollEqual(cats['FICA_COMP_SS'], cats['FICA_EMP_SS']) + self.assertPayrollEqual(cats['FICA_COMP_M'], cats['FICA_EMP_M']) + self.assertPayrollEqual(cats['FUTA_WAGES'], FUTA_MAX_WAGE) + self.assertPayrollEqual(cats['FUTA'], cats['FUTA_WAGES'] * FUTA) + + process_payslip(payslip) + + # Make a new payslip, this one will have maximums for FICA Social Security Wages + + remaining_ss_wages = FICA_SS_MAX_WAGE - salary if (FICA_SS_MAX_WAGE - 2 * salary < salary) else salary + remaining_m_wages = FICA_M_MAX_WAGE - salary if (FICA_M_MAX_WAGE - 2 * salary < salary) else salary + + self._log('2016 tax second payslip:') + payslip = self._createPayslip(employee, '2016-02-01', '2016-02-29') # 2016 is a leap year + + payslip.compute_sheet() + + cats = self._getCategories(payslip) + + self.assertPayrollEqual(cats['FICA_EMP_SS_WAGES'], remaining_ss_wages) + self.assertPayrollEqual(cats['FICA_EMP_SS'], cats['FICA_EMP_SS_WAGES'] * FICA_SS) + self.assertPayrollEqual(cats['FICA_EMP_M_WAGES'], remaining_m_wages) + self.assertPayrollEqual(cats['FICA_EMP_M'], cats['FICA_EMP_M_WAGES'] * FICA_M) + self.assertPayrollEqual(cats['FICA_EMP_M_ADD_WAGES'], 0.0) + self.assertPayrollEqual(cats['FICA_EMP_M_ADD'], 0.0) + self.assertPayrollEqual(cats['FICA_COMP_SS'], cats['FICA_EMP_SS']) + self.assertPayrollEqual(cats['FICA_COMP_M'], cats['FICA_EMP_M']) + self.assertPayrollEqual(cats['FUTA_WAGES'], 0) + self.assertPayrollEqual(cats['FUTA'], 0) + + process_payslip(payslip) + + # Make a new payslip, this one will have reached Medicare Additional (employee only) + + self._log('2016 tax third payslip:') + payslip = self._createPayslip(employee, '2016-03-01', '2016-03-31') + + payslip.compute_sheet() + + cats = self._getCategories(payslip) + + self.assertPayrollEqual(cats['FICA_EMP_M_WAGES'], salary) + self.assertPayrollEqual(cats['FICA_EMP_M_ADD_WAGES'], FICA_M_ADD_START_WAGE - (salary * 2)) # aka 40k + self.assertPayrollEqual(cats['FICA_EMP_M_ADD'], cats['FICA_EMP_M_ADD_WAGES'] * FICA_M_ADD) + + process_payslip(payslip) + + # Make a new payslip, this one will have all salary as Medicare Additional + + self._log('2016 tax fourth payslip:') + payslip = self._createPayslip(employee, '2016-04-01', '2016-04-30') + + payslip.compute_sheet() + + cats = self._getCategories(payslip) + + self.assertPayrollEqual(cats['FICA_EMP_M_WAGES'], salary) + self.assertPayrollEqual(cats['FICA_EMP_M_ADD_WAGES'], salary) + self.assertPayrollEqual(cats['FICA_EMP_M_ADD'], cats['FICA_EMP_M_ADD_WAGES'] * FICA_M_ADD) + + process_payslip(payslip) + + + def test_2016_fed_income_withholding_single(self): + salary = 6000.00 + schedule_pay = 'monthly' + w4_allowances = 3 + w4_allowance_amt = 337.50 * w4_allowances + adjusted_salary = salary - w4_allowance_amt # should be 4987.50, but would work over a wide value for the rate + ### + # Single MONTHLY form Publication 15 + expected_withholding = self.float_round(-(431.95 + ((adjusted_salary - 3325) * 0.25)), self.payroll_digits) + + employee = self._createEmployee() + contract = self._createContract(employee, salary, schedule_pay, w4_allowances, 'single') + + self._log('2016 fed income single payslip: adjusted_salary: ' + str(adjusted_salary)) + payslip = self._createPayslip(employee, '2016-01-01', '2016-01-31') + + payslip.compute_sheet() + + cats = self._getCategories(payslip) + + self.assertPayrollEqual(cats['FED_INC_WITHHOLD'], expected_withholding) + + + def test_2016_fed_income_withholding_married_as_single(self): + salary = 500.00 + schedule_pay = 'weekly' + w4_allowances = 1 + w4_allowance_amt = 77.90 * w4_allowances + adjusted_salary = salary - w4_allowance_amt # should be 422.10, but would work over a wide value for the rate + ### + # Single MONTHLY form Publication 15 + expected_withholding = self.float_round(-(17.90 + ((adjusted_salary - 222) * 0.15)), self.payroll_digits) + + employee = self._createEmployee() + contract = self._createContract(employee, salary, schedule_pay, w4_allowances, 'married_as_single') + + self._log('2016 fed income married_as_single payslip: adjusted_salary: ' + str(adjusted_salary)) + payslip = self._createPayslip(employee, '2016-01-01', '2016-01-31') + + payslip.compute_sheet() + + cats = self._getCategories(payslip) + + self.assertPayrollEqual(cats['FED_INC_WITHHOLD'], expected_withholding) + + + def test_2016_fed_income_withholding_married(self): + salary = 14000.00 + schedule_pay = 'bi-weekly' + w4_allowances = 2 + w4_allowance_amt = 155.80 * w4_allowances + adjusted_salary = salary - w4_allowance_amt # should be 1368.84, but would work over a wide value for the rate + ### + # Single MONTHLY form Publication 15 + expected_withholding = self.float_round(-(1992.05 + ((adjusted_salary - 9231) * 0.33)), self.payroll_digits) + + employee = self._createEmployee() + contract = self._createContract(employee, salary, schedule_pay, w4_allowances, 'married') + + self._log('2016 fed income married payslip: adjusted_salary: ' + str(adjusted_salary)) + payslip = self._createPayslip(employee, '2016-01-01', '2016-01-31') + + payslip.compute_sheet() + + cats = self._getCategories(payslip) + + self.assertPayrollEqual(cats['FED_INC_WITHHOLD'], expected_withholding) + + + def test_2016_taxes_with_external(self): + ## tax rates + FICA_SS = -0.062 + FICA_M = -0.0145 + FUTA = -self.FUTA_RATE_NORMAL_2016 / 100.0 + FICA_M_ADD = -0.009 + + ## tax maximums + FICA_SS_MAX_WAGE = 118500.0 + FICA_M_MAX_WAGE = self.float_info.max + FICA_M_ADD_START_WAGE = 200000.0 + FUTA_MAX_WAGE = 7000.0 + + # social security salary + salary = FICA_M_ADD_START_WAGE + external_wages = 6000.0 + + employee = self._createEmployee() + + contract = self._createContract(employee, salary, external_wages=external_wages) + + self._log('2016 tax first payslip:') + payslip = self._createPayslip(employee, '2016-01-01', '2016-01-31') + + payslip.compute_sheet() + + cats = self._getCategories(payslip) + + self.assertPayrollEqual(cats['FICA_EMP_SS_WAGES'], FICA_SS_MAX_WAGE - external_wages) + self.assertPayrollEqual(cats['FICA_EMP_SS'], cats['FICA_EMP_SS_WAGES'] * FICA_SS) + self.assertPayrollEqual(cats['FICA_EMP_M_WAGES'], salary) + self.assertPayrollEqual(cats['FICA_EMP_M'], cats['FICA_EMP_M_WAGES'] * FICA_M) + self.assertPayrollEqual(cats['FICA_EMP_M_ADD_WAGES'], 0.0) + self.assertPayrollEqual(cats['FICA_EMP_M_ADD'], cats['FICA_EMP_M_ADD_WAGES'] * FICA_M_ADD) + self.assertPayrollEqual(cats['FICA_COMP_SS'], cats['FICA_EMP_SS']) + self.assertPayrollEqual(cats['FICA_COMP_M'], cats['FICA_EMP_M']) + self.assertPayrollEqual(cats['FUTA_WAGES'], FUTA_MAX_WAGE - external_wages) + self.assertPayrollEqual(cats['FUTA'], cats['FUTA_WAGES'] * FUTA) + + + def test_2016_taxes_with_full_futa(self): + ## tax rates + FICA_SS = -0.062 + FICA_M = -0.0145 + FUTA = -self.FUTA_RATE_BASIC_2016 / 100.0 # because of state exemption + FICA_M_ADD = -0.009 + + ## tax maximums + FICA_SS_MAX_WAGE = 118500.0 + FICA_M_MAX_WAGE = self.float_info.max + FICA_M_ADD_START_WAGE = 200000.0 + FUTA_MAX_WAGE = 7000.0 + + # social security salary + salary = FICA_M_ADD_START_WAGE + + employee = self._createEmployee() + + contract = self._createContract(employee, salary, futa_type=USHrContract.FUTA_TYPE_BASIC) + + self._log('2016 tax first payslip:') + payslip = self._createPayslip(employee, '2016-01-01', '2016-01-31') + + payslip.compute_sheet() + + cats = self._getCategories(payslip) + + self.assertPayrollEqual(cats['FICA_EMP_SS_WAGES'], FICA_SS_MAX_WAGE) + self.assertPayrollEqual(cats['FICA_EMP_SS'], cats['FICA_EMP_SS_WAGES'] * FICA_SS) + self.assertPayrollEqual(cats['FICA_EMP_M_WAGES'], salary) + self.assertPayrollEqual(cats['FICA_EMP_M'], cats['FICA_EMP_M_WAGES'] * FICA_M) + self.assertPayrollEqual(cats['FICA_EMP_M_ADD_WAGES'], 0.0) + self.assertPayrollEqual(cats['FICA_EMP_M_ADD'], cats['FICA_EMP_M_ADD_WAGES'] * FICA_M_ADD) + self.assertPayrollEqual(cats['FICA_COMP_SS'], cats['FICA_EMP_SS']) + self.assertPayrollEqual(cats['FICA_COMP_M'], cats['FICA_EMP_M']) + self.assertPayrollEqual(cats['FUTA_WAGES'], FUTA_MAX_WAGE) + self.assertPayrollEqual(cats['FUTA'], cats['FUTA_WAGES'] * FUTA) + + + def test_2016_taxes_with_futa_exempt(self): + ## tax rates + FICA_SS = -0.062 + FICA_M = -0.0145 + FUTA = self.FUTA_RATE_EXEMPT_2016 # because of exemption + FICA_M_ADD = -0.009 + + ## tax maximums + FICA_SS_MAX_WAGE = 118500.0 + FICA_M_MAX_WAGE = self.float_info.max + FICA_M_ADD_START_WAGE = 200000.0 + FUTA_MAX_WAGE = 7000.0 + + # social security salary + salary = FICA_M_ADD_START_WAGE + + employee = self._createEmployee() + + contract = self._createContract(employee, salary, futa_type=USHrContract.FUTA_TYPE_EXEMPT) + + self._log('2016 tax first payslip:') + payslip = self._createPayslip(employee, '2016-01-01', '2016-01-31') + + payslip.compute_sheet() + + cats = self._getCategories(payslip) + + self.assertPayrollEqual(cats['FICA_EMP_SS_WAGES'], FICA_SS_MAX_WAGE) + self.assertPayrollEqual(cats['FICA_EMP_SS'], cats['FICA_EMP_SS_WAGES'] * FICA_SS) + self.assertPayrollEqual(cats['FICA_EMP_M_WAGES'], salary) + self.assertPayrollEqual(cats['FICA_EMP_M'], cats['FICA_EMP_M_WAGES'] * FICA_M) + self.assertPayrollEqual(cats['FICA_EMP_M_ADD_WAGES'], 0.0) + self.assertPayrollEqual(cats['FICA_EMP_M_ADD'], cats['FICA_EMP_M_ADD_WAGES'] * FICA_M_ADD) + self.assertPayrollEqual(cats['FICA_COMP_SS'], cats['FICA_EMP_SS']) + self.assertPayrollEqual(cats['FICA_COMP_M'], cats['FICA_EMP_M']) + + FUTA_WAGES = 0.0 + if 'FUTA_WAGES' in cats: + FUTA_WAGES = cats['FUTA_WAGES'] + FUTA = 0.0 + if 'FUTA' in cats: + FUTA = cats['FUTA'] + self.assertPayrollEqual(FUTA_WAGES, 0.0) + self.assertPayrollEqual(FUTA, FUTA_WAGES * FUTA) + + + def test_2016_fed_income_withholding_nonresident_alien(self): + salary = 3500.00 + schedule_pay = 'quarterly' + w4_allowances = 1 + w4_allowance_amt = 1012.50 * w4_allowances + nra_adjustment = 562.50 # for quarterly + adjusted_salary = salary - w4_allowance_amt + nra_adjustment # 3050 + + ### + # Single QUARTERLY form Publication 15 + expected_withholding = self.float_round(-(231.80 + ((adjusted_salary - 2881) * 0.15)), self.payroll_digits) + + employee = self._createEmployee() + contract = self._createContract(employee, salary, schedule_pay, w4_allowances, 'single', + w4_is_nonresident_alien=True) + + self._log('2016 fed income single payslip nonresident alien: adjusted_salary: ' + str(adjusted_salary)) + payslip = self._createPayslip(employee, '2016-01-01', '2016-01-31') + + payslip.compute_sheet() + + cats = self._getCategories(payslip) + + self.assertPayrollEqual(cats['FED_INC_WITHHOLD'], expected_withholding) + + + def test_2016_fed_income_additional_withholding(self): + salary = 50000.00 + schedule_pay = 'annually' + w4_additional_withholding = 5000.0 + w4_allowances = 2 + w4_allowance_amt = 4050.0 * w4_allowances + adjusted_salary = salary - w4_allowance_amt # 41900 + + ### + # Single ANNUAL form Publication 15 + expected_withholding = self.float_round(-((1855 + ((adjusted_salary - 27100) * 0.15)) + w4_additional_withholding), + self.payroll_digits) + + employee = self._createEmployee() + contract = self._createContract(employee, salary, schedule_pay, w4_allowances, 'married', + w4_additional_withholding=w4_additional_withholding) + + self._log('2016 fed income married payslip additional withholding: adjusted_salary: ' + str(adjusted_salary)) + payslip = self._createPayslip(employee, '2016-01-01', '2016-01-31') + + payslip.compute_sheet() + + cats = self._getCategories(payslip) + + self.assertPayrollEqual(cats['FED_INC_WITHHOLD'], expected_withholding) + + + def test_2016_taxes_with_w4_exempt(self): + salary = 6000.0 + schedule_pay = 'bi-weekly' + w4_allowances = 0 + employee = self._createEmployee() + contract = self._createContract(employee, salary, schedule_pay, w4_allowances, '') + + self._log('2016 tax w4 exempt payslip:') + payslip = self._createPayslip(employee, '2016-01-01', '2016-01-31') + + payslip.compute_sheet() + + cats = self._getCategories(payslip) + + FED_INC_WITHHOLD = 0.0 + if 'FED_INC_WITHHOLD' in cats: + FED_INC_WITHHOLD = cats['FED_INC_WITHHOLD'] + self.assertPayrollEqual(FED_INC_WITHHOLD, 0.0) diff --git a/l10n_us_hr_payroll/tests/test_us_payslip_2017.py b/l10n_us_hr_payroll/tests/test_us_payslip_2017.py new file mode 100755 index 00000000..f6fed47e --- /dev/null +++ b/l10n_us_hr_payroll/tests/test_us_payslip_2017.py @@ -0,0 +1,344 @@ +from .test_us_payslip import TestUsPayslip, process_payslip + +from odoo.addons.l10n_us_hr_payroll.l10n_us_hr_payroll import USHrContract + +from sys import float_info + + +class TestUsPayslip2017(TestUsPayslip): + # FUTA Constants + FUTA_RATE_NORMAL = 0.6 + FUTA_RATE_BASIC = 6.0 + FUTA_RATE_EXEMPT = 0.0 + + # Wage caps + FICA_SS_MAX_WAGE = 127200.0 + FICA_M_MAX_WAGE = float_info.max + FICA_M_ADD_START_WAGE = 200000.0 + FUTA_MAX_WAGE = 7000.0 + + # Rates + FICA_SS = 6.2 / -100.0 + FICA_M = 1.45 / -100.0 + FUTA = FUTA_RATE_NORMAL / -100.0 + FICA_M_ADD = 0.9 / -100.0 + + ### + # 2017 Taxes and Rates + ### + + def test_2017_taxes(self): + # salary is high so that second payslip runs over max + # social security salary + salary = 80000.0 + + employee = self._createEmployee() + + self._createContract(employee, salary) + + self._log('2016 tax last slip') + payslip = self._createPayslip(employee, '2016-12-01', '2016-12-31') + payslip.compute_sheet() + process_payslip(payslip) + + self._log('2017 tax first payslip:') + payslip = self._createPayslip(employee, '2017-01-01', '2017-01-31') + + payslip.compute_sheet() + + cats = self._getCategories(payslip) + + self.assertPayrollEqual(cats['FICA_EMP_SS_WAGES'], salary) + self.assertPayrollEqual(cats['FICA_EMP_SS'], cats['FICA_EMP_SS_WAGES'] * self.FICA_SS) + self.assertPayrollEqual(cats['FICA_EMP_M_WAGES'], salary) + self.assertPayrollEqual(cats['FICA_EMP_M'], cats['FICA_EMP_M_WAGES'] * self.FICA_M) + self.assertPayrollEqual(cats['FICA_EMP_M_ADD_WAGES'], 0.0) + self.assertPayrollEqual(cats['FICA_EMP_M_ADD'], 0.0) + self.assertPayrollEqual(cats['FICA_COMP_SS'], cats['FICA_EMP_SS']) + self.assertPayrollEqual(cats['FICA_COMP_M'], cats['FICA_EMP_M']) + self.assertPayrollEqual(cats['FUTA_WAGES'], self.FUTA_MAX_WAGE) + self.assertPayrollEqual(cats['FUTA'], cats['FUTA_WAGES'] * self.FUTA) + + process_payslip(payslip) + + # Make a new payslip, this one will have maximums for FICA Social Security Wages + + remaining_ss_wages = self.FICA_SS_MAX_WAGE - salary if (self.FICA_SS_MAX_WAGE - 2 * salary < salary) else salary + remaining_m_wages = self.FICA_M_MAX_WAGE - salary if (self.FICA_M_MAX_WAGE - 2 * salary < salary) else salary + + self._log('2017 tax second payslip:') + payslip = self._createPayslip(employee, '2017-02-01', '2017-02-28') + + payslip.compute_sheet() + + cats = self._getCategories(payslip) + + self.assertPayrollEqual(cats['FICA_EMP_SS_WAGES'], remaining_ss_wages) + self.assertPayrollEqual(cats['FICA_EMP_SS'], cats['FICA_EMP_SS_WAGES'] * self.FICA_SS) + self.assertPayrollEqual(cats['FICA_EMP_M_WAGES'], remaining_m_wages) + self.assertPayrollEqual(cats['FICA_EMP_M'], cats['FICA_EMP_M_WAGES'] * self.FICA_M) + self.assertPayrollEqual(cats['FICA_EMP_M_ADD_WAGES'], 0.0) + self.assertPayrollEqual(cats['FICA_EMP_M_ADD'], 0.0) + self.assertPayrollEqual(cats['FICA_COMP_SS'], cats['FICA_EMP_SS']) + self.assertPayrollEqual(cats['FICA_COMP_M'], cats['FICA_EMP_M']) + self.assertPayrollEqual(cats['FUTA_WAGES'], 0) + self.assertPayrollEqual(cats['FUTA'], 0) + + process_payslip(payslip) + + # Make a new payslip, this one will have reached Medicare Additional (employee only) + + self._log('2017 tax third payslip:') + payslip = self._createPayslip(employee, '2017-03-01', '2017-03-31') + + payslip.compute_sheet() + + cats = self._getCategories(payslip) + + self.assertPayrollEqual(cats['FICA_EMP_M_WAGES'], salary) + self.assertPayrollEqual(cats['FICA_EMP_M_ADD_WAGES'], self.FICA_M_ADD_START_WAGE - (salary * 2)) # aka 40k + self.assertPayrollEqual(cats['FICA_EMP_M_ADD'], cats['FICA_EMP_M_ADD_WAGES'] * self.FICA_M_ADD) + + process_payslip(payslip) + + # Make a new payslip, this one will have all salary as Medicare Additional + + self._log('2017 tax fourth payslip:') + payslip = self._createPayslip(employee, '2017-04-01', '2017-04-30') + + payslip.compute_sheet() + + cats = self._getCategories(payslip) + + self.assertPayrollEqual(cats['FICA_EMP_M_WAGES'], salary) + self.assertPayrollEqual(cats['FICA_EMP_M_ADD_WAGES'], salary) + self.assertPayrollEqual(cats['FICA_EMP_M_ADD'], cats['FICA_EMP_M_ADD_WAGES'] * self.FICA_M_ADD) + + process_payslip(payslip) + + def test_2017_fed_income_withholding_single(self): + salary = 6000.00 + schedule_pay = 'monthly' + w4_allowances = 3 + w4_allowance_amt = 337.50 * w4_allowances + adjusted_salary = salary - w4_allowance_amt # should be 4987.50, but would work over a wide value for the rate + ### + # Single MONTHLY form Publication 15 + expected_withholding = self.float_round(-(435.45 + ((adjusted_salary - 3354) * 0.25)), self.payroll_digits) + + employee = self._createEmployee() + self._createContract(employee, salary, schedule_pay, w4_allowances, 'single') + + self._log('2017 fed income single payslip: adjusted_salary: ' + str(adjusted_salary)) + payslip = self._createPayslip(employee, '2017-01-01', '2017-01-31') + + payslip.compute_sheet() + + cats = self._getCategories(payslip) + + self.assertPayrollEqual(cats['FED_INC_WITHHOLD'], expected_withholding) + + def test_2017_fed_income_withholding_married_as_single(self): + salary = 500.00 + schedule_pay = 'weekly' + w4_allowances = 1 + w4_allowance_amt = 77.90 * w4_allowances + adjusted_salary = salary - w4_allowance_amt # should be 422.10, but would work over a wide value for the rate + ### + # Single MONTHLY form Publication 15 + expected_withholding = self.float_round(-(18.00 + ((adjusted_salary - 224) * 0.15)), self.payroll_digits) + + employee = self._createEmployee() + self._createContract(employee, salary, schedule_pay, w4_allowances, 'married_as_single') + + self._log('2017 fed income married_as_single payslip: adjusted_salary: ' + str(adjusted_salary)) + payslip = self._createPayslip(employee, '2017-01-01', '2017-01-31') + + payslip.compute_sheet() + + cats = self._getCategories(payslip) + + self.assertPayrollEqual(cats['FED_INC_WITHHOLD'], expected_withholding) + + def test_2017_fed_income_withholding_married(self): + salary = 14000.00 + schedule_pay = 'bi-weekly' + w4_allowances = 2 + w4_allowance_amt = 155.80 * w4_allowances + adjusted_salary = salary - w4_allowance_amt # should be 1368.84, but would work over a wide value for the rate + ### + # Single MONTHLY form Publication 15 + expected_withholding = self.float_round(-(2008.61 + ((adjusted_salary - 9308) * 0.33)), self.payroll_digits) + + employee = self._createEmployee() + self._createContract(employee, salary, schedule_pay, w4_allowances, 'married') + + self._log('2017 fed income married payslip: adjusted_salary: ' + str(adjusted_salary)) + payslip = self._createPayslip(employee, '2017-01-01', '2017-01-31') + + payslip.compute_sheet() + + cats = self._getCategories(payslip) + + self.assertPayrollEqual(cats['FED_INC_WITHHOLD'], expected_withholding) + + def test_2017_taxes_with_external(self): + + # social security salary + salary = self.FICA_M_ADD_START_WAGE + external_wages = 6000.0 + + employee = self._createEmployee() + + self._createContract(employee, salary, external_wages=external_wages) + + self._log('2017 tax first payslip:') + payslip = self._createPayslip(employee, '2017-01-01', '2017-01-31') + + payslip.compute_sheet() + + cats = self._getCategories(payslip) + + self.assertPayrollEqual(cats['FICA_EMP_SS_WAGES'], self.FICA_SS_MAX_WAGE - external_wages) + self.assertPayrollEqual(cats['FICA_EMP_SS'], cats['FICA_EMP_SS_WAGES'] * self.FICA_SS) + self.assertPayrollEqual(cats['FICA_EMP_M_WAGES'], salary) + self.assertPayrollEqual(cats['FICA_EMP_M'], cats['FICA_EMP_M_WAGES'] * self.FICA_M) + self.assertPayrollEqual(cats['FICA_EMP_M_ADD_WAGES'], 0.0) + self.assertPayrollEqual(cats['FICA_EMP_M_ADD'], cats['FICA_EMP_M_ADD_WAGES'] * self.FICA_M_ADD) + self.assertPayrollEqual(cats['FICA_COMP_SS'], cats['FICA_EMP_SS']) + self.assertPayrollEqual(cats['FICA_COMP_M'], cats['FICA_EMP_M']) + self.assertPayrollEqual(cats['FUTA_WAGES'], self.FUTA_MAX_WAGE - external_wages) + self.assertPayrollEqual(cats['FUTA'], cats['FUTA_WAGES'] * self.FUTA) + + def test_2017_taxes_with_full_futa(self): + futa_rate = self.FUTA_RATE_BASIC / -100.0 + # social security salary + salary = self.FICA_M_ADD_START_WAGE + + employee = self._createEmployee() + + self._createContract(employee, salary, futa_type=USHrContract.FUTA_TYPE_BASIC) + + self._log('2017 tax first payslip:') + payslip = self._createPayslip(employee, '2017-01-01', '2017-01-31') + + payslip.compute_sheet() + + cats = self._getCategories(payslip) + + self.assertPayrollEqual(cats['FICA_EMP_SS_WAGES'], self.FICA_SS_MAX_WAGE) + self.assertPayrollEqual(cats['FICA_EMP_SS'], cats['FICA_EMP_SS_WAGES'] * self.FICA_SS) + self.assertPayrollEqual(cats['FICA_EMP_M_WAGES'], salary) + self.assertPayrollEqual(cats['FICA_EMP_M'], cats['FICA_EMP_M_WAGES'] * self.FICA_M) + self.assertPayrollEqual(cats['FICA_EMP_M_ADD_WAGES'], 0.0) + self.assertPayrollEqual(cats['FICA_EMP_M_ADD'], cats['FICA_EMP_M_ADD_WAGES'] * self.FICA_M_ADD) + self.assertPayrollEqual(cats['FICA_COMP_SS'], cats['FICA_EMP_SS']) + self.assertPayrollEqual(cats['FICA_COMP_M'], cats['FICA_EMP_M']) + self.assertPayrollEqual(cats['FUTA_WAGES'], self.FUTA_MAX_WAGE) + self.assertPayrollEqual(cats['FUTA'], cats['FUTA_WAGES'] * futa_rate) + + def test_2017_taxes_with_futa_exempt(self): + futa_rate = self.FUTA_RATE_EXEMPT / -100.0 # because of exemption + + # social security salary + salary = self.FICA_M_ADD_START_WAGE + + employee = self._createEmployee() + + self._createContract(employee, salary, futa_type=USHrContract.FUTA_TYPE_EXEMPT) + + self._log('2017 tax first payslip:') + payslip = self._createPayslip(employee, '2017-01-01', '2017-01-31') + + payslip.compute_sheet() + + cats = self._getCategories(payslip) + + self.assertPayrollEqual(cats['FICA_EMP_SS_WAGES'], self.FICA_SS_MAX_WAGE) + self.assertPayrollEqual(cats['FICA_EMP_SS'], cats['FICA_EMP_SS_WAGES'] * self.FICA_SS) + self.assertPayrollEqual(cats['FICA_EMP_M_WAGES'], salary) + self.assertPayrollEqual(cats['FICA_EMP_M'], cats['FICA_EMP_M_WAGES'] * self.FICA_M) + self.assertPayrollEqual(cats['FICA_EMP_M_ADD_WAGES'], 0.0) + self.assertPayrollEqual(cats['FICA_EMP_M_ADD'], cats['FICA_EMP_M_ADD_WAGES'] * self.FICA_M_ADD) + self.assertPayrollEqual(cats['FICA_COMP_SS'], cats['FICA_EMP_SS']) + self.assertPayrollEqual(cats['FICA_COMP_M'], cats['FICA_EMP_M']) + + futa_wages = 0.0 + if 'FUTA_WAGES' in cats: + futa_wages = cats['FUTA_WAGES'] + futa = 0.0 + if 'FUTA' in cats: + futa = cats['FUTA'] + self.assertPayrollEqual(futa_wages, 0.0) + self.assertPayrollEqual(futa, futa_wages * futa_rate) + + def test_2017_fed_income_withholding_nonresident_alien(self): + salary = 3500.00 + schedule_pay = 'quarterly' + w4_allowances = 1 + w4_allowance_amt = 1012.50 * w4_allowances + nra_adjustment = 575.00 # for quarterly + adjusted_salary = salary - w4_allowance_amt + nra_adjustment # 3050 + + ### + # Single QUARTERLY form Publication 15 + expected_withholding = self.float_round(-(233.10 + ((adjusted_salary - 2906) * 0.15)), self.payroll_digits) + + employee = self._createEmployee() + self._createContract(employee, salary, schedule_pay, w4_allowances, 'single', + w4_is_nonresident_alien=True) + + self._log('2017 fed income single payslip nonresident alien: adjusted_salary: ' + str(adjusted_salary)) + payslip = self._createPayslip(employee, '2017-01-01', '2017-01-31') + + payslip.compute_sheet() + + cats = self._getCategories(payslip) + + self.assertPayrollEqual(cats['FED_INC_WITHHOLD'], expected_withholding) + + def test_2017_fed_income_additional_withholding(self): + salary = 50000.00 + schedule_pay = 'annually' + w4_additional_withholding = 5000.0 + w4_allowances = 2 + w4_allowance_amt = 4050.0 * w4_allowances + adjusted_salary = salary - w4_allowance_amt # 41900 + + ### + # Single ANNUAL form Publication 15 + expected_withholding = \ + self.float_round(-((1865.00 + ((adjusted_salary - 27300) * 0.15)) + w4_additional_withholding), + self.payroll_digits) + + employee = self._createEmployee() + self._createContract(employee, salary, schedule_pay, w4_allowances, 'married', + w4_additional_withholding=w4_additional_withholding) + + self._log('2017 fed income married payslip additional withholding: adjusted_salary: ' + str(adjusted_salary)) + payslip = self._createPayslip(employee, '2017-01-01', '2017-01-31') + + payslip.compute_sheet() + + cats = self._getCategories(payslip) + + self.assertPayrollEqual(cats['FED_INC_WITHHOLD'], expected_withholding) + + def test_2017_taxes_with_w4_exempt(self): + salary = 6000.0 + schedule_pay = 'bi-weekly' + w4_allowances = 0 + employee = self._createEmployee() + self._createContract(employee, salary, schedule_pay, w4_allowances, '') + + self._log('2017 tax w4 exempt payslip:') + payslip = self._createPayslip(employee, '2017-01-01', '2017-01-31') + + payslip.compute_sheet() + + cats = self._getCategories(payslip) + + fed_inc_withhold = 0.0 + if 'FED_INC_WITHHOLD' in cats: + fed_inc_withhold = cats['FED_INC_WITHHOLD'] + self.assertPayrollEqual(fed_inc_withhold, 0.0) diff --git a/l10n_us_hr_payroll/tests/test_us_payslip_2018.py b/l10n_us_hr_payroll/tests/test_us_payslip_2018.py new file mode 100755 index 00000000..9e676218 --- /dev/null +++ b/l10n_us_hr_payroll/tests/test_us_payslip_2018.py @@ -0,0 +1,344 @@ +from .test_us_payslip import TestUsPayslip, process_payslip + +from odoo.addons.l10n_us_hr_payroll.l10n_us_hr_payroll import USHrContract + +from sys import float_info + + +class TestUsPayslip2018(TestUsPayslip): + # FUTA Constants + FUTA_RATE_NORMAL = 0.6 + FUTA_RATE_BASIC = 6.0 + FUTA_RATE_EXEMPT = 0.0 + + # Wage caps + FICA_SS_MAX_WAGE = 128400.0 + FICA_M_MAX_WAGE = float_info.max + FICA_M_ADD_START_WAGE = 200000.0 + FUTA_MAX_WAGE = 7000.0 + + # Rates + FICA_SS = 6.2 / -100.0 + FICA_M = 1.45 / -100.0 + FUTA = FUTA_RATE_NORMAL / -100.0 + FICA_M_ADD = 0.9 / -100.0 + + ### + # 2018 Taxes and Rates + ### + + def test_2018_taxes(self): + # salary is high so that second payslip runs over max + # social security salary + salary = 80000.0 + + employee = self._createEmployee() + + self._createContract(employee, salary) + + self._log('2017 tax last slip') + payslip = self._createPayslip(employee, '2017-12-01', '2017-12-31') + payslip.compute_sheet() + process_payslip(payslip) + + self._log('2018 tax first payslip:') + payslip = self._createPayslip(employee, '2018-01-01', '2018-01-31') + + payslip.compute_sheet() + + cats = self._getCategories(payslip) + + self.assertPayrollEqual(cats['FICA_EMP_SS_WAGES'], salary) + self.assertPayrollEqual(cats['FICA_EMP_SS'], cats['FICA_EMP_SS_WAGES'] * self.FICA_SS) + self.assertPayrollEqual(cats['FICA_EMP_M_WAGES'], salary) + self.assertPayrollEqual(cats['FICA_EMP_M'], cats['FICA_EMP_M_WAGES'] * self.FICA_M) + self.assertPayrollEqual(cats['FICA_EMP_M_ADD_WAGES'], 0.0) + self.assertPayrollEqual(cats['FICA_EMP_M_ADD'], 0.0) + self.assertPayrollEqual(cats['FICA_COMP_SS'], cats['FICA_EMP_SS']) + self.assertPayrollEqual(cats['FICA_COMP_M'], cats['FICA_EMP_M']) + self.assertPayrollEqual(cats['FUTA_WAGES'], self.FUTA_MAX_WAGE) + self.assertPayrollEqual(cats['FUTA'], cats['FUTA_WAGES'] * self.FUTA) + + process_payslip(payslip) + + # Make a new payslip, this one will have maximums for FICA Social Security Wages + + remaining_ss_wages = self.FICA_SS_MAX_WAGE - salary if (self.FICA_SS_MAX_WAGE - 2 * salary < salary) else salary + remaining_m_wages = self.FICA_M_MAX_WAGE - salary if (self.FICA_M_MAX_WAGE - 2 * salary < salary) else salary + + self._log('2018 tax second payslip:') + payslip = self._createPayslip(employee, '2018-02-01', '2018-02-28') + + payslip.compute_sheet() + + cats = self._getCategories(payslip) + + self.assertPayrollEqual(cats['FICA_EMP_SS_WAGES'], remaining_ss_wages) + self.assertPayrollEqual(cats['FICA_EMP_SS'], cats['FICA_EMP_SS_WAGES'] * self.FICA_SS) + self.assertPayrollEqual(cats['FICA_EMP_M_WAGES'], remaining_m_wages) + self.assertPayrollEqual(cats['FICA_EMP_M'], cats['FICA_EMP_M_WAGES'] * self.FICA_M) + self.assertPayrollEqual(cats['FICA_EMP_M_ADD_WAGES'], 0.0) + self.assertPayrollEqual(cats['FICA_EMP_M_ADD'], 0.0) + self.assertPayrollEqual(cats['FICA_COMP_SS'], cats['FICA_EMP_SS']) + self.assertPayrollEqual(cats['FICA_COMP_M'], cats['FICA_EMP_M']) + self.assertPayrollEqual(cats['FUTA_WAGES'], 0) + self.assertPayrollEqual(cats['FUTA'], 0) + + process_payslip(payslip) + + # Make a new payslip, this one will have reached Medicare Additional (employee only) + + self._log('2018 tax third payslip:') + payslip = self._createPayslip(employee, '2018-03-01', '2018-03-31') + + payslip.compute_sheet() + + cats = self._getCategories(payslip) + + self.assertPayrollEqual(cats['FICA_EMP_M_WAGES'], salary) + self.assertPayrollEqual(cats['FICA_EMP_M_ADD_WAGES'], self.FICA_M_ADD_START_WAGE - (salary * 2)) # aka 40k + self.assertPayrollEqual(cats['FICA_EMP_M_ADD'], cats['FICA_EMP_M_ADD_WAGES'] * self.FICA_M_ADD) + + process_payslip(payslip) + + # Make a new payslip, this one will have all salary as Medicare Additional + + self._log('2018 tax fourth payslip:') + payslip = self._createPayslip(employee, '2018-04-01', '2018-04-30') + + payslip.compute_sheet() + + cats = self._getCategories(payslip) + + self.assertPayrollEqual(cats['FICA_EMP_M_WAGES'], salary) + self.assertPayrollEqual(cats['FICA_EMP_M_ADD_WAGES'], salary) + self.assertPayrollEqual(cats['FICA_EMP_M_ADD'], cats['FICA_EMP_M_ADD_WAGES'] * self.FICA_M_ADD) + + process_payslip(payslip) + + def test_2018_fed_income_withholding_single(self): + salary = 6000.00 + schedule_pay = 'monthly' + w4_allowances = 3 + w4_allowance_amt = 345.80 * w4_allowances + adjusted_salary = salary - w4_allowance_amt # should be 4962.60, but would work over a wide value for the rate + ### + # Single MONTHLY form Publication 15 + expected_withholding = self.float_round(-(371.12 + ((adjusted_salary - 3533) * 0.22)), self.payroll_digits) + + employee = self._createEmployee() + self._createContract(employee, salary, schedule_pay, w4_allowances, 'single') + + self._log('2018 fed income single payslip: adjusted_salary: ' + str(adjusted_salary)) + payslip = self._createPayslip(employee, '2018-01-01', '2018-01-31') + + payslip.compute_sheet() + + cats = self._getCategories(payslip) + + self.assertPayrollEqual(cats['FED_INC_WITHHOLD'], expected_withholding) + + def test_2018_fed_income_withholding_married_as_single(self): + salary = 500.00 + schedule_pay = 'weekly' + w4_allowances = 1 + w4_allowance_amt = 79.80 * w4_allowances + adjusted_salary = salary - w4_allowance_amt # should be 420.50, but would work over a wide value for the rate + ### + # Single MONTHLY form Publication 15 + expected_withholding = self.float_round(-(18.30 + ((adjusted_salary - 254) * 0.12)), self.payroll_digits) + + employee = self._createEmployee() + self._createContract(employee, salary, schedule_pay, w4_allowances, 'married_as_single') + + self._log('2018 fed income married_as_single payslip: adjusted_salary: ' + str(adjusted_salary)) + payslip = self._createPayslip(employee, '2018-01-01', '2018-01-31') + + payslip.compute_sheet() + + cats = self._getCategories(payslip) + + self.assertPayrollEqual(cats['FED_INC_WITHHOLD'], expected_withholding) + + def test_2018_fed_income_withholding_married(self): + salary = 14000.00 + schedule_pay = 'bi-weekly' + w4_allowances = 2 + w4_allowance_amt = 159.60 * w4_allowances + adjusted_salary = salary - w4_allowance_amt # should be 13680.80, but would work over a wide value for the rate + ### + # Single MONTHLY form Publication 15 + expected_withholding = self.float_round(-(2468.56 + ((adjusted_salary - 12560) * 0.32)), self.payroll_digits) + + employee = self._createEmployee() + self._createContract(employee, salary, schedule_pay, w4_allowances, 'married') + + self._log('2018 fed income married payslip: adjusted_salary: ' + str(adjusted_salary)) + payslip = self._createPayslip(employee, '2018-01-01', '2018-01-31') + + payslip.compute_sheet() + + cats = self._getCategories(payslip) + + self.assertPayrollEqual(cats['FED_INC_WITHHOLD'], expected_withholding) + + def test_2018_taxes_with_external(self): + + # social security salary + salary = self.FICA_M_ADD_START_WAGE + external_wages = 6000.0 + + employee = self._createEmployee() + + self._createContract(employee, salary, external_wages=external_wages) + + self._log('2018 tax first payslip:') + payslip = self._createPayslip(employee, '2018-01-01', '2018-01-31') + + payslip.compute_sheet() + + cats = self._getCategories(payslip) + + self.assertPayrollEqual(cats['FICA_EMP_SS_WAGES'], self.FICA_SS_MAX_WAGE - external_wages) + self.assertPayrollEqual(cats['FICA_EMP_SS'], cats['FICA_EMP_SS_WAGES'] * self.FICA_SS) + self.assertPayrollEqual(cats['FICA_EMP_M_WAGES'], salary) + self.assertPayrollEqual(cats['FICA_EMP_M'], cats['FICA_EMP_M_WAGES'] * self.FICA_M) + self.assertPayrollEqual(cats['FICA_EMP_M_ADD_WAGES'], 0.0) + self.assertPayrollEqual(cats['FICA_EMP_M_ADD'], cats['FICA_EMP_M_ADD_WAGES'] * self.FICA_M_ADD) + self.assertPayrollEqual(cats['FICA_COMP_SS'], cats['FICA_EMP_SS']) + self.assertPayrollEqual(cats['FICA_COMP_M'], cats['FICA_EMP_M']) + self.assertPayrollEqual(cats['FUTA_WAGES'], self.FUTA_MAX_WAGE - external_wages) + self.assertPayrollEqual(cats['FUTA'], cats['FUTA_WAGES'] * self.FUTA) + + def test_2018_taxes_with_full_futa(self): + futa_rate = self.FUTA_RATE_BASIC / -100.0 + # social security salary + salary = self.FICA_M_ADD_START_WAGE + + employee = self._createEmployee() + + self._createContract(employee, salary, futa_type=USHrContract.FUTA_TYPE_BASIC) + + self._log('2018 tax first payslip:') + payslip = self._createPayslip(employee, '2018-01-01', '2018-01-31') + + payslip.compute_sheet() + + cats = self._getCategories(payslip) + + self.assertPayrollEqual(cats['FICA_EMP_SS_WAGES'], self.FICA_SS_MAX_WAGE) + self.assertPayrollEqual(cats['FICA_EMP_SS'], cats['FICA_EMP_SS_WAGES'] * self.FICA_SS) + self.assertPayrollEqual(cats['FICA_EMP_M_WAGES'], salary) + self.assertPayrollEqual(cats['FICA_EMP_M'], cats['FICA_EMP_M_WAGES'] * self.FICA_M) + self.assertPayrollEqual(cats['FICA_EMP_M_ADD_WAGES'], 0.0) + self.assertPayrollEqual(cats['FICA_EMP_M_ADD'], cats['FICA_EMP_M_ADD_WAGES'] * self.FICA_M_ADD) + self.assertPayrollEqual(cats['FICA_COMP_SS'], cats['FICA_EMP_SS']) + self.assertPayrollEqual(cats['FICA_COMP_M'], cats['FICA_EMP_M']) + self.assertPayrollEqual(cats['FUTA_WAGES'], self.FUTA_MAX_WAGE) + self.assertPayrollEqual(cats['FUTA'], cats['FUTA_WAGES'] * futa_rate) + + def test_2018_taxes_with_futa_exempt(self): + futa_rate = self.FUTA_RATE_EXEMPT / -100.0 # because of exemption + + # social security salary + salary = self.FICA_M_ADD_START_WAGE + + employee = self._createEmployee() + + self._createContract(employee, salary, futa_type=USHrContract.FUTA_TYPE_EXEMPT) + + self._log('2018 tax first payslip:') + payslip = self._createPayslip(employee, '2018-01-01', '2018-01-31') + + payslip.compute_sheet() + + cats = self._getCategories(payslip) + + self.assertPayrollEqual(cats['FICA_EMP_SS_WAGES'], self.FICA_SS_MAX_WAGE) + self.assertPayrollEqual(cats['FICA_EMP_SS'], cats['FICA_EMP_SS_WAGES'] * self.FICA_SS) + self.assertPayrollEqual(cats['FICA_EMP_M_WAGES'], salary) + self.assertPayrollEqual(cats['FICA_EMP_M'], cats['FICA_EMP_M_WAGES'] * self.FICA_M) + self.assertPayrollEqual(cats['FICA_EMP_M_ADD_WAGES'], 0.0) + self.assertPayrollEqual(cats['FICA_EMP_M_ADD'], cats['FICA_EMP_M_ADD_WAGES'] * self.FICA_M_ADD) + self.assertPayrollEqual(cats['FICA_COMP_SS'], cats['FICA_EMP_SS']) + self.assertPayrollEqual(cats['FICA_COMP_M'], cats['FICA_EMP_M']) + + futa_wages = 0.0 + if 'FUTA_WAGES' in cats: + futa_wages = cats['FUTA_WAGES'] + futa = 0.0 + if 'FUTA' in cats: + futa = cats['FUTA'] + self.assertPayrollEqual(futa_wages, 0.0) + self.assertPayrollEqual(futa, futa_wages * futa_rate) + + def test_2018_fed_income_withholding_nonresident_alien(self): + salary = 3500.00 + schedule_pay = 'quarterly' + w4_allowances = 1 + w4_allowance_amt = 1037.50 * w4_allowances + nra_adjustment = 1962.50 # for quarterly + adjusted_salary = salary - w4_allowance_amt + nra_adjustment # 4425 + + ### + # Single QUARTERLY form Publication 15 + expected_withholding = self.float_round(-(238.10 + ((adjusted_salary - 3306) * 0.12)), self.payroll_digits) + + employee = self._createEmployee() + self._createContract(employee, salary, schedule_pay, w4_allowances, 'single', + w4_is_nonresident_alien=True) + + self._log('2018 fed income single payslip nonresident alien: adjusted_salary: ' + str(adjusted_salary)) + payslip = self._createPayslip(employee, '2018-01-01', '2018-01-31') + + payslip.compute_sheet() + + cats = self._getCategories(payslip) + + self.assertPayrollEqual(cats['FED_INC_WITHHOLD'], expected_withholding) + + def test_2018_fed_income_additional_withholding(self): + salary = 50000.00 + schedule_pay = 'annually' + w4_additional_withholding = 5000.0 + w4_allowances = 2 + w4_allowance_amt = 4150.00 * w4_allowances + adjusted_salary = salary - w4_allowance_amt # 41700 + + ### + # Single ANNUAL form Publication 15 + expected_withholding = \ + self.float_round(-((1905.00 + ((adjusted_salary - 30600) * 0.12)) + w4_additional_withholding), + self.payroll_digits) + + employee = self._createEmployee() + self._createContract(employee, salary, schedule_pay, w4_allowances, 'married', + w4_additional_withholding=w4_additional_withholding) + + self._log('2018 fed income married payslip additional withholding: adjusted_salary: ' + str(adjusted_salary)) + payslip = self._createPayslip(employee, '2018-01-01', '2018-01-31') + + payslip.compute_sheet() + + cats = self._getCategories(payslip) + + self.assertPayrollEqual(cats['FED_INC_WITHHOLD'], expected_withholding) + + def test_2018_taxes_with_w4_exempt(self): + salary = 6000.0 + schedule_pay = 'bi-weekly' + w4_allowances = 0 + employee = self._createEmployee() + self._createContract(employee, salary, schedule_pay, w4_allowances, '') + + self._log('2018 tax w4 exempt payslip:') + payslip = self._createPayslip(employee, '2018-01-01', '2018-01-31') + + payslip.compute_sheet() + + cats = self._getCategories(payslip) + + fed_inc_withhold = 0.0 + if 'FED_INC_WITHHOLD' in cats: + fed_inc_withhold = cats['FED_INC_WITHHOLD'] + self.assertPayrollEqual(fed_inc_withhold, 0.0)