diff --git a/hr_payroll_rate/models/payroll.py b/hr_payroll_rate/models/payroll.py index 66a89963..d1280593 100644 --- a/hr_payroll_rate/models/payroll.py +++ b/hr_payroll_rate/models/payroll.py @@ -1,23 +1,41 @@ +import ast from odoo import api, fields, models +from odoo.tools import ormcache +from odoo.exceptions import UserError class PayrollRate(models.Model): _name = 'hr.payroll.rate' _description = 'Payroll Rate' + _order = 'date_from DESC, company_id ASC' active = fields.Boolean(string='Active', default=True) name = fields.Char(string='Name') - date_from = fields.Date(string='Date From', required=True) + date_from = fields.Date(string='Date From', index=True, required=True) date_to = fields.Date(string='Date To') company_id = fields.Many2one('res.company', string='Company', copy=False, default=False) - rate = fields.Float(string='Rate', digits=(12, 6), required=True) - code = fields.Char(string='Code', required=True) + rate = fields.Float(string='Rate', digits=(12, 6), default=0.0, required=True) + code = fields.Char(string='Code', index=True, required=True) limit_payslip = fields.Float(string='Payslip Limit') limit_year = fields.Float(string='Year Limit') wage_limit_payslip = fields.Float(string='Payslip Wage Limit') wage_limit_year = fields.Float(string='Year Wage Limit') + parameter_value = fields.Text(help="Python data structure") + + @api.model + @ormcache('code', 'date', 'company_id', 'self.env.user.company_id.id') + def _get_parameter_from_code(self, code, company_id, date=None): + if not date: + date = fields.Date.today() + rate = self.search([ + ('code', '=', code), + ('date_from', '<=', date), + ], limit=1) + if not rate: + raise UserError(_("No rule parameter with code '%s' was found for %s ") % (code, date)) + return ast.literal_eval(rate.parameter_value) class Payslip(models.Model): @@ -35,3 +53,6 @@ class Payslip(models.Model): self.ensure_one() return self.env['hr.payroll.rate'].search( self._get_rate_domain(code), limit=1, order='date_from DESC, company_id ASC') + + def rule_parameter(self, code): + return self.env['hr.payroll.rate']._get_parameter_from_code(code, self.company_id.id, self.date_to) diff --git a/hr_payroll_rate/tests/test_payroll_rate.py b/hr_payroll_rate/tests/test_payroll_rate.py index c86fe446..5354cfe1 100644 --- a/hr_payroll_rate/tests/test_payroll_rate.py +++ b/hr_payroll_rate/tests/test_payroll_rate.py @@ -44,6 +44,15 @@ class TestPayrollRate(common.TransactionCase): rate = self.payslip.get_rate('TEST') self.assertEqual(rate, test_rate) + test_rate.parameter_value = """[ + (1, 2, 3), + (4, 5, 6), + ]""" + + value = self.payslip.rule_parameter('TEST') + self.assertEqual(len(value), 2) + self.assertEqual(value[0], (1, 2, 3)) + def test_payroll_rate_multicompany(self): test_rate_other = self.env['hr.payroll.rate'].create({ 'name': 'Test Rate', diff --git a/hr_payroll_rate/views/payroll_views.xml b/hr_payroll_rate/views/payroll_views.xml index 4132846e..49815884 100644 --- a/hr_payroll_rate/views/payroll_views.xml +++ b/hr_payroll_rate/views/payroll_views.xml @@ -27,6 +27,7 @@ + diff --git a/l10n_us_hr_payroll/__init__.py b/l10n_us_hr_payroll/__init__.py index 0650744f..09434554 100755 --- a/l10n_us_hr_payroll/__init__.py +++ b/l10n_us_hr_payroll/__init__.py @@ -1 +1,3 @@ +# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details. + from . import models diff --git a/l10n_us_hr_payroll/__manifest__.py b/l10n_us_hr_payroll/__manifest__.py index dd863641..e32bde9c 100755 --- a/l10n_us_hr_payroll/__manifest__.py +++ b/l10n_us_hr_payroll/__manifest__.py @@ -1,10 +1,9 @@ { 'name': 'USA - Payroll', 'author': 'Hibou Corp. ', - 'license': 'AGPL-3', 'category': 'Localization', 'depends': ['hr_payroll', 'hr_payroll_rate'], - 'version': '12.0.2019.1.0', + 'version': '12.0.2020.1.0', 'description': """ USA Payroll Rules. ================== @@ -20,11 +19,19 @@ USA Payroll Rules. 'auto_install': False, 'website': 'https://hibou.io/', 'data': [ - 'views/l10n_us_hr_payroll_view.xml', + 'security/ir.model.access.csv', 'data/base.xml', - 'data/rates.xml', - 'data/rules.xml', + 'data/integration_rules.xml', + 'data/federal/fed_940_futa_parameters.xml', + 'data/federal/fed_940_futa_rules.xml', + 'data/federal/fed_941_fica_parameters.xml', + 'data/federal/fed_941_fica_rules.xml', + 'data/federal/fed_941_fit_parameters.xml', + 'data/federal/fed_941_fit_rules.xml', 'data/final.xml', + 'views/hr_contract_views.xml', + 'views/us_payroll_config_views.xml', ], - 'installable': True + 'installable': True, + 'license': 'OPL-1', } diff --git a/l10n_us_hr_payroll/data/base.xml b/l10n_us_hr_payroll/data/base.xml old mode 100755 new mode 100644 index 0579e8f3..75cafa13 --- a/l10n_us_hr_payroll/data/base.xml +++ b/l10n_us_hr_payroll/data/base.xml @@ -1,83 +1,4 @@ - + - - - - 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 - - - - - - Wage: US FICA Social Security - WAGE_US_FICA_SS - - - Wage: US FICA Medicare - WAGE_US_FICA_M - - - Wage: US FICA Medicare Additional - WAGE_US_FICA_M_ADD - - - Wage: US FUTA Federal Unemployment - WAGE_US_FUTA - - - - EE: US FICA Social Security - EE_US_FICA_SS - - - - EE: US FICA Medicare - EE_US_FICA_M - - - - EE: US FICA Medicare Additional - EE_US_FICA_M_ADD - - - - EE: US Federal Income Tax Withholding - EE_US_FED_INC_WITHHOLD - - - - - ER: US FICA Social Security - ER_US_FICA_SS - - - - ER: US FICA Medicare - ER_US_FICA_M - - - - ER: US FUTA Federal Unemployment - ER_US_FUTA - - - - - + \ No newline at end of file diff --git a/l10n_us_hr_payroll/data/federal/fed_940_futa_parameters.xml b/l10n_us_hr_payroll/data/federal/fed_940_futa_parameters.xml new file mode 100644 index 00000000..e2ed116b --- /dev/null +++ b/l10n_us_hr_payroll/data/federal/fed_940_futa_parameters.xml @@ -0,0 +1,26 @@ + + + + + Federal 940 FUTA Wage Base + fed_940_futa_wage_base + 7000.00 + + + + + + Federal 940 FUTA Rate Basic + fed_940_futa_rate_basic + 6.0 + + + + + Federal 940 FUTA Rate Normal + fed_940_futa_rate_normal + 0.6 + + + + \ No newline at end of file diff --git a/l10n_us_hr_payroll/data/federal/fed_940_futa_rules.xml b/l10n_us_hr_payroll/data/federal/fed_940_futa_rules.xml new file mode 100644 index 00000000..c1073b06 --- /dev/null +++ b/l10n_us_hr_payroll/data/federal/fed_940_futa_rules.xml @@ -0,0 +1,39 @@ + + + + + US Federal 940 - EFTPS + + + + EFTPS - 940 (FUTA) + Electronic Federal Tax Payment System - Form 940 + + + + + ER: Federal 940 FUTA + ER_US_940_FUTA + + + + + + WAGE: Federal 940 FUTA Exempt + WAGE_US_940_FUTA_EXEMPT + + + + + + ER: US FUTA Federal Unemployment + ER_US_940_FUTA + python + result, _ = er_us_940_futa(payslip, categories, worked_days, inputs) + code + result, result_rate = er_us_940_futa(payslip, categories, worked_days, inputs) + + + + + \ No newline at end of file diff --git a/l10n_us_hr_payroll/data/federal/fed_941_fica_parameters.xml b/l10n_us_hr_payroll/data/federal/fed_941_fica_parameters.xml new file mode 100644 index 00000000..8a25dde0 --- /dev/null +++ b/l10n_us_hr_payroll/data/federal/fed_941_fica_parameters.xml @@ -0,0 +1,66 @@ + + + + + + Federal 941 FICA Social Security Wage Base + fed_941_fica_ss_wage_base + 128400.0 + + + + Federal 941 FICA Social Security Wage Base + fed_941_fica_ss_wage_base + 132900.0 + + + + Federal 941 FICA Social Security Wage Base + fed_941_fica_ss_wage_base + 137700.0 + + + + + + Federal 941 FICA Rate + fed_941_fica_ss_rate + 6.2 + + + + + + + Federal 941 FICA Medicare Wage Base + fed_941_fica_m_wage_base + "inf" + + + + + + Federal 941 FICA Rate + fed_941_fica_m_rate + 1.45 + + + + + + + Federal 941 FICA Medicare Additional Wage Start + fed_941_fica_m_add_wage_start + 200000.0 + + + + + + Federal 941 FICA Medicare Additional Rate + fed_941_fica_m_add_rate + 0.9 + + + + \ No newline at end of file diff --git a/l10n_us_hr_payroll/data/federal/fed_941_fica_rules.xml b/l10n_us_hr_payroll/data/federal/fed_941_fica_rules.xml new file mode 100644 index 00000000..6ce417fa --- /dev/null +++ b/l10n_us_hr_payroll/data/federal/fed_941_fica_rules.xml @@ -0,0 +1,101 @@ + + + + + US Federal 941 - EFTPS + + + + EFTPS - 941 (FICA + Federal Witholding) + Electronic Federal Tax Payment System - Form 941 + + + + + EE: Federal 941 FICA + EE_US_941_FICA + + + + + ER: Federal 941 FICA + ER_US_941_FICA + + + + + + WAGE: Federal 941 FICA Exempt + WAGE_US_941_FICA_EXEMPT + + + + + + + + EE: US FICA Social Security + EE_US_941_FICA_SS + python + result, _ = ee_us_941_fica_ss(payslip, categories, worked_days, inputs) + code + result, result_rate = ee_us_941_fica_ss(payslip, categories, worked_days, inputs) + + + + + + + + ER: US FICA Social Security + ER_US_941_FICA_SS + python + result, _ = er_us_941_fica_ss(payslip, categories, worked_days, inputs) + code + result, result_rate = er_us_941_fica_ss(payslip, categories, worked_days, inputs) + + + + + + + + + EE: US FICA Medicare + EE_US_941_FICA_M + python + result, _ = ee_us_941_fica_m(payslip, categories, worked_days, inputs) + code + result, result_rate = ee_us_941_fica_m(payslip, categories, worked_days, inputs) + + + + + + + + ER: US FICA Medicare + ER_US_941_FICA_M + python + result, _ = er_us_941_fica_m(payslip, categories, worked_days, inputs) + code + result, result_rate = er_us_941_fica_m(payslip, categories, worked_days, inputs) + + + + + + + + + EE: US FICA Medicare Additional + EE_US_941_FICA_M_ADD + python + result, _ = ee_us_941_fica_m_add(payslip, categories, worked_days, inputs) + code + result, result_rate = ee_us_941_fica_m_add(payslip, categories, worked_days, inputs) + + + + + \ No newline at end of file diff --git a/l10n_us_hr_payroll/data/federal/fed_941_fit_parameters.xml b/l10n_us_hr_payroll/data/federal/fed_941_fit_parameters.xml new file mode 100644 index 00000000..89330246 --- /dev/null +++ b/l10n_us_hr_payroll/data/federal/fed_941_fit_parameters.xml @@ -0,0 +1,492 @@ + + + + + + Federal 941 FIT Allowance + fed_941_fit_allowance + + { + 'weekly': 80.80, + 'bi-weekly': 161.50, + 'semi-monthly': 175.00, + 'monthly': 350.00, + 'quarterly': 1050.00, + 'semi-annually': 2100.00, + 'annually': 4200.00, + } + + + + Federal 941 FIT Allowance + fed_941_fit_allowance + { + 'weekly': 80.80, + 'bi-weekly': 161.50, + 'semi-monthly': 175.00, + 'monthly': 350.00, + 'quarterly': 1050.00, + 'semi-annually': 2100.00, + 'annually': 4200.00, + } + + + + Federal 941 FIT Allowance + fed_941_fit_allowance + + 4300.0 + + + + + Federal 941 FIT NRA Additional + fed_941_fit_nra_additional + + { + 'weekly': 153.80, + 'bi-weekly': 307.70, + 'semi-monthly': 333.30, + 'monthly': 666.70, + 'quarterly': 2000.00, + 'semi-annually': 4000.00, + 'annually': 8000.00, + } + + + + Federal 941 FIT NRA Additional + fed_941_fit_nra_additional + { + 'weekly': 153.80, + 'bi-weekly': 307.70, + 'semi-monthly': 333.30, + 'monthly': 666.70, + 'quarterly': 2000.00, + 'semi-annually': 4000.00, + 'annually': 8000.00, + } + + + + Federal 941 FIT NRA Additional + fed_941_fit_nra_additional + { + 'weekly': 238.50, + 'bi-weekly': 476.90, + 'semi-monthly': 516.70, + 'monthly': 1033.30, + 'quarterly': 3100.00, + 'semi-annually': 6200.00, + 'annually': 12400.00, + } + + + + + + Federal 941 FIT Table Single + fed_941_fit_table_single + + + { + 'weekly': [ + ( 73.00, 0.00, 0), + ( 260.00, 0.00, 10), + ( 832.00, 18.70, 12), + ( 1692.00, 87.34, 22), + ( 3164.00, 276.54, 24), + ( 3998.00, 629.82, 32), + ( 9887.00, 896.70, 35), + ( 'inf', 2957.85, 37), + ], + 'bi-weekly': [ + ( 146.00, 0.00, 0), + ( 519.00, 0.00, 10), + ( 1664.00, 37.30, 12), + ( 3385.00, 174.70, 22), + ( 6328.00, 553.32, 24), + ( 7996.00, 1259.64, 32), + ( 19773.00, 1793.40, 35), + ( 'inf', 5915.35, 37), + ], + 'semi-monthly': [ + ( 158.00, 0.00, 0), + ( 563.00, 0.00, 10), + ( 1803.00, 40.50, 12), + ( 3667.00, 189.30, 22), + ( 6855.00, 599.38, 24), + ( 8663.00, 1364.50, 32), + ( 21421.00, 1943.06, 35), + ( 'inf', 6408.36, 37), + ], + 'monthly': [ + ( 317.00, 0.00, 0), + ( 1125.00, 0.00, 10), + ( 3606.00, 80.80, 12), + ( 7333.00, 378.52, 22), + ( 13710.00, 1198.46, 24), + ( 17325.00, 2728.94, 32), + ( 42842.00, 3885.74, 35), + ( 'inf', 12816.69, 37), + ], + 'quarterly': [ + ( 950.00, 0.00, 0), + ( 3375.00, 0.00, 10), + ( 10819.00, 242.50, 12), + ( 22000.00, 1135.78, 22), + ( 41131.00, 3595.60, 24), + ( 51975.00, 8187.04, 32), + ( 128525.00, 11657.12, 35), + ( 'inf', 38449.62, 37), + ], + 'semi-annually': [ + ( 1900.00, 0.00, 0), + ( 6750.00, 0.00, 10), + ( 21638.00, 485.00, 12), + ( 44000.00, 2271.56, 22), + ( 82263.00, 7191.20, 24), + ( 103950.00, 16374.32, 32), + ( 257050.00, 23314.16, 35), + ( 'inf', 76899.16, 37), + ], + 'annually': [ + ( 3800.00, 0.00, 0), + ( 13500.00, 0.00, 10), + ( 43275.00, 970.00, 12), + ( 88000.00, 4543.00, 22), + ( 164525.00, 14382.50, 24), + ( 207900.00, 32748.50, 32), + ( 514100.00, 46628.50, 35), + ( 'inf', 153798.50, 37), + ], + } + + + + Federal 941 FIT Table Single + fed_941_fit_table_single + + { + 'weekly': [ + ( 73.00, 0.00, 0), + ( 260.00, 0.00, 10), + ( 832.00, 18.70, 12), + ( 1692.00, 87.34, 22), + ( 3164.00, 276.54, 24), + ( 3998.00, 629.82, 32), + ( 9887.00, 896.70, 35), + ( 'inf', 2957.85, 37), + ], + 'bi-weekly': [ + ( 146.00, 0.00, 0), + ( 519.00, 0.00, 10), + ( 1664.00, 37.30, 12), + ( 3385.00, 174.70, 22), + ( 6328.00, 553.32, 24), + ( 7996.00, 1259.64, 32), + ( 19773.00, 1793.40, 35), + ( 'inf', 5915.35, 37), + ], + 'semi-monthly': [ + ( 158.00, 0.00, 0), + ( 563.00, 0.00, 10), + ( 1803.00, 40.50, 12), + ( 3667.00, 189.30, 22), + ( 6855.00, 599.38, 24), + ( 8663.00, 1364.50, 32), + ( 21421.00, 1943.06, 35), + ( 'inf', 6408.36, 37), + ], + 'monthly': [ + ( 317.00, 0.00, 0), + ( 1125.00, 0.00, 10), + ( 3606.00, 80.80, 12), + ( 7333.00, 378.52, 22), + ( 13710.00, 1198.46, 24), + ( 17325.00, 2728.94, 32), + ( 42842.00, 3885.74, 35), + ( 'inf', 12816.69, 37), + ], + 'quarterly': [ + ( 950.00, 0.00, 0), + ( 3375.00, 0.00, 10), + ( 10819.00, 242.50, 12), + ( 22000.00, 1135.78, 22), + ( 41131.00, 3595.60, 24), + ( 51975.00, 8187.04, 32), + ( 128525.00, 11657.12, 35), + ( 'inf', 38449.62, 37), + ], + 'semi-annually': [ + ( 1900.00, 0.00, 0), + ( 6750.00, 0.00, 10), + ( 21638.00, 485.00, 12), + ( 44000.00, 2271.56, 22), + ( 82263.00, 7191.20, 24), + ( 103950.00, 16374.32, 32), + ( 257050.00, 23314.16, 35), + ( 'inf', 76899.16, 37), + ], + 'annually': [ + ( 3800.00, 0.00, 0), + ( 13500.00, 0.00, 10), + ( 43275.00, 970.00, 12), + ( 88000.00, 4543.00, 22), + ( 164525.00, 14382.50, 24), + ( 207900.00, 32748.50, 32), + ( 514100.00, 46628.50, 35), + ( 'inf', 153798.50, 37), + ], + } + + + + Federal 941 FIT Table Single + fed_941_fit_table_single + + + { + 'standard': [ + ( 0.00, 0.00, 0.00), + ( 3800.00, 0.00, 0.10), + ( 13675.00, 987.50, 0.12), + ( 43925.00, 4617.50, 0.22), + ( 89325.00, 14605.50, 0.24), + ( 167100.00, 33271.50, 0.32), + ( 211150.00, 47367.50, 0.35), + ( 522200.00, 156235.00, 0.37), + ], + 'higher': [ + ( 0.00, 0.00, 0.00), + ( 6200.00, 0.00, 0.10), + ( 11138.00, 493.75, 0.12), + ( 26263.00, 2308.75, 0.22), + ( 48963.00, 7302.75, 0.24), + ( 87850.00, 16635.75, 0.32), + ( 109875.00, 23683.75, 0.35), + ( 265400.00, 78117.50, 0.37), + ], + } + + + + + + Federal 941 FIT Table Married + fed_941_fit_table_married + + + { + 'weekly': [ + ( 227.00, 0.00, 0), + ( 600.00, 0.00, 10), + ( 1745.00, 37.30, 12), + ( 3465.00, 174.70, 22), + ( 6409.00, 553.10, 24), + ( 8077.00, 1259.66, 32), + ( 12003.00, 1793.42, 35), + ( 'inf', 3167.52, 37), + ], + 'bi-weekly': [ + ( 454.00, 0.00, 0), + ( 1200.00, 0.00, 10), + ( 3490.00, 74.60, 12), + ( 6931.00, 349.40, 22), + ( 12817.00, 1106.42, 24), + ( 16154.00, 2519.06, 32), + ( 24006.00, 3586.90, 35), + ( 'inf', 6335.10, 37), + ], + 'semi-monthly': [ + ( 492.00, 0.00, 0), + ( 1300.00, 0.00, 10), + ( 3781.00, 80.80, 12), + ( 7508.00, 378.52, 22), + ( 13885.00, 1198.46, 24), + ( 17500.00, 2728.94, 32), + ( 26006.00, 3885.74, 35), + ( 'inf', 6862.84, 37), + ], + 'monthly': [ + ( 983.00, 0.00, 0), + ( 2600.00, 0.00, 10), + ( 7563.00, 161.70, 12), + ( 15017.00, 757.26, 22), + ( 27771.00, 2397.14, 24), + ( 35000.00, 5458.10, 32), + ( 52013.00, 7771.38, 35), + ( 'inf', 13725.93, 37), + ], + 'quarterly': [ + ( 2950.00, 0.00, 0), + ( 7800.00, 0.00, 10), + ( 22688.00, 485.00, 12), + ( 45050.00, 2271.56, 22), + ( 83313.00, 7191.20, 24), + ( 105000.00, 16374.32, 32), + ( 156038.00, 23314.16, 35), + ( 'inf', 41177.46, 37), + ], + 'semi-annually': [ + ( 5900.00, 0.00, 0), + ( 15600.00, 0.00, 10), + ( 45375.00, 970.00, 12), + ( 90100.00, 4543.00, 22), + ( 166625.00, 14382.50, 24), + ( 210000.00, 32748.50, 32), + ( 312075.00, 46628.50, 35), + ( 'inf', 82354.75, 37), + ], + 'annually': [ + ( 11800.00, 0.00, 0), + ( 31200.00, 0.00, 10), + ( 90750.00, 1940.00, 12), + ( 180200.00, 9086.00, 22), + ( 333250.00, 28765.00, 24), + ( 420000.00, 65497.00, 32), + ( 624150.00, 93257.00, 35), + ( 'inf', 164709.50, 37), + ], + } + + + + Federal 941 FIT Table Married + fed_941_fit_table_married + + { + 'weekly': [ + ( 227.00, 0.00, 0), + ( 600.00, 0.00, 10), + ( 1745.00, 37.30, 12), + ( 3465.00, 174.70, 22), + ( 6409.00, 553.10, 24), + ( 8077.00, 1259.66, 32), + ( 12003.00, 1793.42, 35), + ( 'inf', 3167.52, 37), + ], + 'bi-weekly': [ + ( 454.00, 0.00, 0), + ( 1200.00, 0.00, 10), + ( 3490.00, 74.60, 12), + ( 6931.00, 349.40, 22), + ( 12817.00, 1106.42, 24), + ( 16154.00, 2519.06, 32), + ( 24006.00, 3586.90, 35), + ( 'inf', 6335.10, 37), + ], + 'semi-monthly': [ + ( 492.00, 0.00, 0), + ( 1300.00, 0.00, 10), + ( 3781.00, 80.80, 12), + ( 7508.00, 378.52, 22), + ( 13885.00, 1198.46, 24), + ( 17500.00, 2728.94, 32), + ( 26006.00, 3885.74, 35), + ( 'inf', 6862.84, 37), + ], + 'monthly': [ + ( 983.00, 0.00, 0), + ( 2600.00, 0.00, 10), + ( 7563.00, 161.70, 12), + ( 15017.00, 757.26, 22), + ( 27771.00, 2397.14, 24), + ( 35000.00, 5458.10, 32), + ( 52013.00, 7771.38, 35), + ( 'inf', 13725.93, 37), + ], + 'quarterly': [ + ( 2950.00, 0.00, 0), + ( 7800.00, 0.00, 10), + ( 22688.00, 485.00, 12), + ( 45050.00, 2271.56, 22), + ( 83313.00, 7191.20, 24), + ( 105000.00, 16374.32, 32), + ( 156038.00, 23314.16, 35), + ( 'inf', 41177.46, 37), + ], + 'semi-annually': [ + ( 5900.00, 0.00, 0), + ( 15600.00, 0.00, 10), + ( 45375.00, 970.00, 12), + ( 90100.00, 4543.00, 22), + ( 166625.00, 14382.50, 24), + ( 210000.00, 32748.50, 32), + ( 312075.00, 46628.50, 35), + ( 'inf', 82354.75, 37), + ], + 'annually': [ + ( 11800.00, 0.00, 0), + ( 31200.00, 0.00, 10), + ( 90750.00, 1940.00, 12), + ( 180200.00, 9086.00, 22), + ( 333250.00, 28765.00, 24), + ( 420000.00, 65497.00, 32), + ( 624150.00, 93257.00, 35), + ( 'inf', 164709.50, 37), + ], + } + + + + Federal 941 FIT Table Married + fed_941_fit_table_married + + + { + 'standard': [ + ( 0.00, 0.00, 0.00), + ( 11900.00, 0.00, 0.10), + ( 31650.00, 1975.00, 0.12), + ( 92150.00, 9235.00, 0.22), + ( 182950.00, 29211.00, 0.24), + ( 338500.00, 66543.00, 0.32), + ( 426600.00, 94735.00, 0.35), + ( 633950.00, 167307.50, 0.37), + ], + 'higher': [ + ( 0.00, 0.00, 0.00), + ( 12400.00, 0.00, 0.10), + ( 22275.00, 987.50, 0.12), + ( 52525.00, 4617.50, 0.22), + ( 97925.00, 14605.50, 0.24), + ( 175700.00, 33271.50, 0.32), + ( 219750.00, 47367.50, 0.35), + ( 323425.00, 83653.75, 0.37), + ], + } + + + + + Federal 941 FIT Table Head of Household + fed_941_fit_table_hh + + + { + 'standard': [ + ( 0.00, 0.00, 0.00), + ( 10050.00, 0.00, 0.10), + ( 24150.00, 1410.00, 0.12), + ( 63750.00, 6162.00, 0.22), + ( 95550.00, 13158.00, 0.24), + ( 173350.00, 31830.00, 0.32), + ( 217400.00, 45926.00, 0.35), + ( 528450.00, 154793.50, 0.37), + ], + 'higher': [ + ( 0.00, 0.00, 0.00), + ( 9325.00, 0.00, 0.10), + ( 16375.00, 705.00, 0.12), + ( 36175.00, 3081.00, 0.22), + ( 52075.00, 6579.00, 0.24), + ( 90975.00, 15915.00, 0.32), + ( 113000.00, 22963.00, 0.35), + ( 268525.00, 77396.75, 0.37), + ], + } + + + + \ No newline at end of file diff --git a/l10n_us_hr_payroll/data/federal/fed_941_fit_rules.xml b/l10n_us_hr_payroll/data/federal/fed_941_fit_rules.xml new file mode 100644 index 00000000..8a4612af --- /dev/null +++ b/l10n_us_hr_payroll/data/federal/fed_941_fit_rules.xml @@ -0,0 +1,28 @@ + + + + + WAGE: Federal 941 Income Tax Exempt + WAGE_US_941_FIT_EXEMPT + + + + EE: Federal 941 Income Tax Withholding + EE_US_941_FIT + + + + + + + EE: US Federal Income Tax Withholding + EE_US_941_FIT + python + result, _ = ee_us_941_fit(payslip, categories, worked_days, inputs) + code + result, result_rate = ee_us_941_fit(payslip, categories, worked_days, inputs) + + + + + \ No newline at end of file diff --git a/l10n_us_hr_payroll/data/final.xml b/l10n_us_hr_payroll/data/final.xml old mode 100755 new mode 100644 index a1ae2fd8..b2f700cc --- a/l10n_us_hr_payroll/data/final.xml +++ b/l10n_us_hr_payroll/data/final.xml @@ -1,29 +1,26 @@ - - + US_EMP USA Employee - + - diff --git a/l10n_us_hr_payroll/data/integration_rules.xml b/l10n_us_hr_payroll/data/integration_rules.xml new file mode 100644 index 00000000..9ecec93b --- /dev/null +++ b/l10n_us_hr_payroll/data/integration_rules.xml @@ -0,0 +1,27 @@ + + + + + python + result = inputs.COMMISSION.amount > 0.0 if inputs.COMMISSION else False + code + result = inputs.COMMISSION.amount if inputs.COMMISSION else 0 + BASIC_COM + + Commissions + + + + + + python + result = inputs.BADGES.amount > 0.0 if inputs.BADGES else False + code + result = inputs.BADGES.amount if inputs.BADGES else 0 + BASIC_BADGES + + Badges + + + + \ No newline at end of file diff --git a/l10n_us_hr_payroll/data/rates.xml b/l10n_us_hr_payroll/data/rates.xml deleted file mode 100644 index 6af3f48e..00000000 --- a/l10n_us_hr_payroll/data/rates.xml +++ /dev/null @@ -1,68 +0,0 @@ - - - - - US FUTA Exempt - US_FUTA_EXEMPT - 0.0 - 2016-01-01 - - - - US FUTA Normal - US_FUTA_NORMAL - 0.6 - 2016-01-01 - - - - US FUTA Basic - US_FUTA_BASIC - 6.0 - 2016-01-01 - - - - - - - US FICA Social Security - US_FICA_SS - 6.2 - 2016-01-01 - 2017-12-31 - - - - US FICA Social Security - US_FICA_SS - 6.2 - 2018-01-01 - 2018-12-31 - - - - US FICA Social Security - US_FICA_SS - 6.2 - 2019-01-01 - 2019-12-31 - - - - - US FICA Medicare - US_FICA_M - 1.45 - 2016-01-01 - - - - US FICA Medicare Additional - US_FICA_M_ADD - 0.9 - 2016-01-01 - 200000.0 - - - \ No newline at end of file diff --git a/l10n_us_hr_payroll/data/rules.xml b/l10n_us_hr_payroll/data/rules.xml deleted file mode 100755 index 469f62f7..00000000 --- a/l10n_us_hr_payroll/data/rules.xml +++ /dev/null @@ -1,1058 +0,0 @@ - - - - - - - - - - Wage: US FICA Social Security - WAGE_US_FICA_SS - python - result = not contract.fica_exempt - code - -### -year = payslip.dict.date_to.year -ytd = payslip.sum('WAGE_US_FICA_SS', str(year) + '-01-01', str(year+1) + '-01-01') -ytd += contract.external_wages -rate = payslip.dict.get_rate('US_FICA_SS') -remaining = rate.wage_limit_year - ytd - -if remaining <= 0.0: - result = 0 -elif remaining < categories.BASIC: - result = remaining -else: - result = categories.BASIC - - - - - - - Wage: US FICA Medicare - WAGE_US_FICA_M - python - result = not contract.fica_exempt - code - result = categories.BASIC - - - - - - Wage: US FICA Medicare Additional - WAGE_US_FICA_M_ADD - python - result = not contract.fica_exempt - code - -### -rate = payslip.dict.get_rate('US_FICA_M_ADD') -ADD_M = rate.wage_limit_year -year = payslip.dict.date_to.year -norm_med_ytd = payslip.sum('WAGE_US_FICA_M', str(year) + '-01-01', str(year+1) + '-01-01') -norm_med_cur = categories.WAGE_US_FICA_M - -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 - - - - - - - - EE: US FICA Social Security - EE_US_FICA_SS - python - result = not contract.fica_exempt - code - -rate = payslip.dict.get_rate('US_FICA_SS') -result_rate = -rate.rate -result = categories.WAGE_US_FICA_SS - - - - - - - EE: US FICA Medicare - EE_US_FICA_M - python - result = not contract.fica_exempt - code - -rate = payslip.dict.get_rate('US_FICA_M') -result_rate = -rate.rate -result = categories.WAGE_US_FICA_M - - - - - - - EE: US FICA Medicare Additional - EE_US_FICA_M_ADD - python - result = not contract.fica_exempt - code - -rate = payslip.dict.get_rate('US_FICA_M_ADD') -result_rate = -rate.rate -result = categories.WAGE_US_FICA_M_ADD - - - - - - - - - EE: US Federal Income Tax Withholding - Single - EE_US_FED_INC_WITHHOLD_S - python - result = (contract.w4_filing_status != 'married' and contract.w4_filing_status) - code - -year = payslip.dict.date_to.year -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 - -if year == 2018: - ### - # 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') -else: - ######## - # 2019 # - ######## - # Single WEEKLY - ### - if 'weekly' == schedule_pay: - wages -= allowances * 80.80 - if is_nra: - wages += 153.80 - - if wages > 73 and wages <= 260: - val = 0.00 + ((wages - 73) * 0.10) - - elif wages > 260 and wages <= 832: - val = 18.70 + ((wages - 260) * 0.12) - - elif wages > 832 and wages <= 1692: - val = 87.34 + ((wages - 832) * 0.22) - - elif wages > 1692 and wages <= 3164: - val = 276.54 + ((wages - 1692) * 0.24) - - elif wages > 3164 and wages <= 3998: - val = 629.82 + ((wages - 3164) * 0.32) - - elif wages > 3998 and wages <= 9887: - val = 896.70 + ((wages - 3998) * 0.35) - - elif wages > 9887: - val = 2957.85 + ((wages - 9887) * 0.37) - - ### - # Single BIWEEKLY - ### - elif 'bi-weekly' == schedule_pay: - wages -= allowances * 161.50 - if is_nra: - wages += 307.70 - - if wages > 146 and wages <= 519: - val = 0.00 + ((wages - 146) * 0.10) - - elif wages > 519 and wages <= 1664: - val = 37.30 + ((wages - 519) * 0.12) - - elif wages > 1664 and wages <= 3385: - val = 174.70 + ((wages - 1664) * 0.22) - - elif wages > 3385 and wages <= 6328: - val = 553.32 + ((wages - 3385) * 0.24) - - elif wages > 6328 and wages <= 7996: - val = 1259.64 + ((wages - 6328) * 0.32) - - elif wages > 7996 and wages <= 19773: - val = 1793.40 + ((wages - 7996) * 0.35) - - elif wages > 19773: - val = 5915.35 + ((wages - 19773) * 0.37) - - ### - # Single SEMIMONTHLY - ### - elif 'semi-monthly' == schedule_pay: - wages -= allowances * 175.00 - if is_nra: - wages += 333.30 - - if wages > 158 and wages <= 563: - val = 0.00 + ((wages - 158) * 0.10) - - elif wages > 563 and wages <= 1803: - val = 40.50 + ((wages - 563) * 0.12) - - elif wages > 1803 and wages <= 3667: - val = 189.30 + ((wages - 1803) * 0.22) - - elif wages > 3667 and wages <= 6855: - val = 599.38 + ((wages - 3667) * 0.24) - - elif wages > 6855 and wages <= 8663: - val = 1364.50 + ((wages - 6855) * 0.32) - - elif wages > 8663 and wages <= 21421: - val = 1943.06 + ((wages - 8663) * 0.35) - - elif wages > 21421: - val = 6408.36 + ((wages - 21421) * 0.37) - - ### - # Single MONTHLY - ### - elif 'monthly' == schedule_pay: - wages -= allowances * 350.00 - if is_nra: - wages += 666.70 - - if wages > 317 and wages <= 1125: - val = 0.00 + ((wages - 317) * 0.10) - - elif wages > 1125 and wages <= 3606: - val = 80.80 + ((wages - 1125) * 0.12) - - elif wages > 3606 and wages <= 7333: - val = 378.52 + ((wages - 3606) * 0.22) - - elif wages > 7333 and wages <= 13710: - val = 1198.46 + ((wages - 7333) * 0.24) - - elif wages > 13710 and wages <= 17325: - val = 2728.94 + ((wages - 13710) * 0.32) - - elif wages > 17325 and wages <= 42842: - val = 3885.74 + ((wages - 17325) * 0.35) - - elif wages > 42842: - val = 12816.69 + ((wages - 42842) * 0.37) - - ### - # Single QUARTERLY - ### - elif 'quarterly' == schedule_pay: - wages -= allowances * 1050.00 - if is_nra: - wages += 2000.0 - - if wages > 950 and wages <= 3375: - val = 0.00 + ((wages - 950) * 0.10) - - elif wages > 3375 and wages <= 10819: - val = 242.50 + ((wages - 3375) * 0.12) - - elif wages > 10819 and wages <= 22000: - val = 1135.78 + ((wages - 10819) * 0.22) - - elif wages > 22000 and wages <= 41131: - val = 3595.60 + ((wages - 22000) * 0.24) - - elif wages > 41131 and wages <= 51975: - val = 8187.04 + ((wages - 41131) * 0.32) - - elif wages > 51975 and wages <= 128525: - val = 11657.12 + ((wages - 51975) * 0.35) - - elif wages > 128525: - val = 38449.62 + ((wages - 128525) * 0.37) - - ### - # Single SEMIANNUAL - ### - elif 'semi-annually' == schedule_pay: - wages -= allowances * 2100.00 - if is_nra: - wages += 4000.00 - - if wages > 1900 and wages <= 6750: - val = 0.00 + ((wages - 1900) * 0.10) - - elif wages > 6750 and wages <= 21638: - val = 485.00 + ((wages - 6750) * 0.12) - - elif wages > 21638 and wages <= 44000: - val = 2271.56 + ((wages - 21638) * 0.22) - - elif wages > 44000 and wages <= 82263: - val = 7191.20 + ((wages - 44000) * 0.24) - - elif wages > 82263 and wages <= 103950: - val = 16374.32 + ((wages - 82263) * 0.32) - - elif wages > 103950 and wages <= 257050: - val = 23314.16 + ((wages - 103950) * 0.35) - - elif wages > 257050: - val = 76899.16 + ((wages - 257050) * 0.37) - - ### - # Single ANNUAL - ### - elif 'annually' == schedule_pay: - wages -= allowances * 4200.00 - if is_nra: - wages += 8000.00 - - if wages > 3800 and wages <= 13500: - val = 0.00 + ((wages - 3800) * 0.10) - - elif wages > 13500 and wages <= 43275: - val = 970.00 + ((wages - 13500) * 0.12) - - elif wages > 43275 and wages <= 88000: - val = 4543.00 + ((wages - 43275) * 0.22) - - elif wages > 88000 and wages <= 164525: - val = 14382.50 + ((wages - 88000) * 0.24) - - elif wages > 164525 and wages <= 207900: - val = 32748.50 + ((wages - 164525) * 0.32) - - elif wages > 207900 and wages <= 514100: - val = 46628.50 + ((wages - 207900) * 0.35) - - elif wages > 514100: - val = 153798.50 + ((wages - 514100) * 0.37) - - else: - raise Exception('Invalid schedule_pay="' + schedule_pay + '" for W4 Allowance calculation') - -result = -(val + additional) - - - - - - - EE: US Federal Income Tax Withholding - Married - EE_US_FED_INC_WITHHOLD_M - python - result = (contract.w4_filing_status == 'married') - code - -year = payslip.dict.date_to.year -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 - -if year == 2018: - ### - # 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') -else: - ######## - # 2019 # - ######## - # Married WEEKLY - ### - if 'weekly' == schedule_pay: - wages -= allowances * 80.80 - if is_nra: - wages += 153.80 - - if wages > 227 and wages <= 600: - val = 0.00 + ((wages - 227) * 0.10) - - elif wages > 600 and wages <= 1745: - val = 37.30 + ((wages - 600) * 0.12) - - elif wages > 1745 and wages <= 3465: - val = 174.70 + ((wages - 1745) * 0.22) - - elif wages > 3465 and wages <= 6409: - val = 553.10 + ((wages - 3465) * 0.24) - - elif wages > 6409 and wages <= 8077: - val = 1259.66 + ((wages - 6409) * 0.32) - - elif wages > 8077 and wages <= 12003: - val = 1793.42 + ((wages - 8077) * 0.35) - - elif wages > 12003: - val = 3167.52 + ((wages - 12003) * 0.37) - - ### - # Married BIWEEKLY - ### - elif 'bi-weekly' == schedule_pay: - wages -= allowances * 161.50 - if is_nra: - wages += 307.70 - - if wages > 454 and wages <= 1200: - val = 0.00 + ((wages - 454) * 0.10) - - elif wages > 1200 and wages <= 3490: - val = 74.60 + ((wages - 1200) * 0.12) - - elif wages > 3490 and wages <= 6931: - val = 349.40 + ((wages - 3490) * 0.22) - - elif wages > 6931 and wages <= 12817: - val = 1106.42 + ((wages - 6931) * 0.24) - - elif wages > 12817 and wages <= 16154: - val = 2519.06 + ((wages - 12817) * 0.32) - - elif wages > 16154 and wages <= 24006: - val = 3586.90 + ((wages - 16154) * 0.35) - - elif wages > 24006: - val = 6335.10 + ((wages - 24006) * 0.37) - - ### - # Married SEMIMONTHLY - ### - elif 'semi-monthly' == schedule_pay: - wages -= allowances * 175.00 - if is_nra: - wages += 333.30 - - if wages > 492 and wages <= 1300: - val = 0.00 + ((wages - 492) * 0.10) - - elif wages > 1300 and wages <= 3781: - val = 80.80 + ((wages - 1300) * 0.12) - - elif wages > 3781 and wages <= 7508: - val = 378.52 + ((wages - 3781) * 0.22) - - elif wages > 7508 and wages <= 13885: - val = 1198.46 + ((wages - 7508) * 0.24) - - elif wages > 13885 and wages <= 17500: - val = 2728.94 + ((wages - 13885) * 0.32) - - elif wages > 17500 and wages <= 26006: - val = 3885.74 + ((wages - 17500) * 0.35) - - elif wages > 26006: - val = 6862.84 + ((wages - 26006) * 0.37) - - ### - # Married MONTHLY - ### - elif 'monthly' == schedule_pay: - wages -= allowances * 350.00 - if is_nra: - wages += 666.70 - - if wages > 983 and wages <= 2600: - val = 0.00 + ((wages - 983) * 0.10) - - elif wages > 2600 and wages <= 7563: - val = 161.70 + ((wages - 2600) * 0.12) - - elif wages > 7563 and wages <= 15017: - val = 757.26 + ((wages - 7563) * 0.22) - - elif wages > 15017 and wages <= 27771: - val = 2397.14 + ((wages - 15017) * 0.24) - - elif wages > 27771 and wages <= 35000: - val = 5458.10 + ((wages - 27771) * 0.32) - - elif wages > 35000 and wages <= 50963: - val = 7771.38 + ((wages - 35000) * 0.35) - - elif wages > 52013: - val = 13725.93 + ((wages - 52013) * 0.37) - - ### - # Married QUARTERLY - ### - elif 'quarterly' == schedule_pay: - wages -= allowances * 1050.00 - if is_nra: - wages += 2000.00 - - if wages > 2950 and wages <= 7800: - val = 0.00 + ((wages - 2950) * 0.10) - - elif wages > 7800 and wages <= 22688: - val = 485.00 + ((wages - 7800) * 0.12) - - elif wages > 22688 and wages <= 45050: - val = 2271.56 + ((wages - 22688) * 0.22) - - elif wages > 45050 and wages <= 83313: - val = 7191.20 + ((wages - 45050) * 0.24) - - elif wages > 83313 and wages <= 105000: - val = 16374.32 + ((wages - 83313) * 0.32) - - elif wages > 105000 and wages <= 156038: - val = 23314.16 + ((wages - 105000) * 0.35) - - elif wages > 156038: - val = 41177.46 + ((wages - 156038) * 0.37) - - ### - # Married SEMIANNUAL - ### - elif 'semi-annually' == schedule_pay: - wages -= allowances * 2100.00 - if is_nra: - wages += 4000.00 - - if wages > 5900 and wages <= 15600: - val = 0.00 + ((wages - 5900) * 0.10) - - elif wages > 15600 and wages <= 45375: - val = 970.00 + ((wages - 15600) * 0.12) - - elif wages > 45375 and wages <= 90100: - val = 4543.00 + ((wages - 45375) * 0.22) - - elif wages > 90100 and wages <= 166625: - val = 14382.50 + ((wages - 90100) * 0.24) - - elif wages > 166625 and wages <= 210000: - val = 32748.50 + ((wages - 166625) * 0.32) - - elif wages > 210000 and wages <= 312075: - val = 46628.50 + ((wages - 210000) * 0.35) - - elif wages > 312075: - val = 82354.75 + ((wages - 312075) * 0.37) - - ### - # Married ANNUAL - ### - elif 'annually' == schedule_pay: - wages -= allowances * 4200.00 - if is_nra: - wages += 8000.00 - - if wages > 11800 and wages <= 31200: - val = 0.00 + ((wages - 11800) * 0.10) - - elif wages > 31200 and wages <= 90750: - val = 1940.00 + ((wages - 31200) * 0.12) - - elif wages > 90750 and wages <= 180200: - val = 9086.00 + ((wages - 90750) * 0.22) - - elif wages > 180200 and wages <= 333250: - val = 28765.00 + ((wages - 180200) * 0.24) - - elif wages > 333250 and wages <= 420000: - val = 65497.00 + ((wages - 333250) * 0.32) - - elif wages > 420000 and wages <= 624150: - val = 93257.00 + ((wages - 420000) * 0.35) - - elif wages > 624150: - val = 164709.50 + ((wages - 624150) * 0.37) - - else: - raise Exception('Invalid schedule_pay="' + schedule_pay + '" for W4 Allowance calculation') - -result = -(val + additional) - - - - - - - - Wage: US FUTA Federal Unemployment - WAGE_US_FUTA - python - result = (contract.futa_type != contract.FUTA_TYPE_EXEMPT) - code - -### -rate = payslip.dict.get_futa_rate(contract) -year = payslip.dict.date_to.year -ytd = payslip.sum('WAGE_US_FUTA', 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 FUTA Federal Unemployment - ER_US_FUTA - python - result = (contract.futa_type != contract.FUTA_TYPE_EXEMPT) - code - -year = payslip.dict.date_to.year -rate = payslip.dict.get_futa_rate(contract) -result_rate = -(rate.rate) -result = categories.WAGE_US_FUTA - - - - - - - - - - ER: US FICA Social Security - ER_US_FICA_SS - none - code - result = categories.EE_US_FICA_SS - - - - - - - ER: US FICA Medicare - ER_US_FICA_M - none - code - result = categories.EE_US_FICA_M - - - - - - diff --git a/l10n_us_hr_payroll/models/__init__.py b/l10n_us_hr_payroll/models/__init__.py index ff687165..c6d607ff 100644 --- a/l10n_us_hr_payroll/models/__init__.py +++ b/l10n_us_hr_payroll/models/__init__.py @@ -1 +1,5 @@ -from . import l10n_us_hr_payroll +# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details. + +from . import hr_contract +from . import hr_payslip +from . import us_payroll_config diff --git a/l10n_us_hr_payroll/models/federal/__init__.py b/l10n_us_hr_payroll/models/federal/__init__.py new file mode 100644 index 00000000..0358305d --- /dev/null +++ b/l10n_us_hr_payroll/models/federal/__init__.py @@ -0,0 +1 @@ +# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details. diff --git a/l10n_us_hr_payroll/models/federal/fed_940.py b/l10n_us_hr_payroll/models/federal/fed_940.py new file mode 100644 index 00000000..ed475a75 --- /dev/null +++ b/l10n_us_hr_payroll/models/federal/fed_940.py @@ -0,0 +1,37 @@ +# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details. + +def er_us_940_futa(payslip, categories, worked_days, inputs): + """ + Returns FUTA eligible wage and rate. + WAGE = GROSS - WAGE_US_940_FUTA_EXEMPT + :return: result, result_rate (wage, percent) + """ + + # Determine Rate. + if payslip.dict.contract_id.futa_type == payslip.dict.contract_id.FUTA_TYPE_EXEMPT: + # Exit early + return 0.0, 0.0 + elif payslip.dict.contract_id.futa_type == payslip.dict.contract_id.FUTA_TYPE_BASIC: + result_rate = -payslip.dict.rule_parameter('fed_940_futa_rate_basic') + else: + result_rate = -payslip.dict.rule_parameter('fed_940_futa_rate_normal') + + # Determine Wage + year = payslip.dict.get_year() + ytd_wage = payslip.sum_category('GROSS', str(year) + '-01-01', str(year+1) + '-01-01') + ytd_wage -= payslip.sum_category('WAGE_US_940_FUTA_EXEMPT', str(year) + '-01-01', str(year+1) + '-01-01') + ytd_wage += payslip.dict.contract_id.external_wages + + wage_base = payslip.dict.rule_parameter('fed_940_futa_wage_base') + remaining = wage_base - ytd_wage + + wage = categories.GROSS - categories.WAGE_US_940_FUTA_EXEMPT + + if remaining < 0.0: + result = 0.0 + elif remaining < wage: + result = remaining + else: + result = wage + + return result, result_rate diff --git a/l10n_us_hr_payroll/models/federal/fed_941.py b/l10n_us_hr_payroll/models/federal/fed_941.py new file mode 100644 index 00000000..7a0916ec --- /dev/null +++ b/l10n_us_hr_payroll/models/federal/fed_941.py @@ -0,0 +1,239 @@ +# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details. + +# import logging +# _logger = logging.getLogger(__name__) + + +def ee_us_941_fica_ss(payslip, categories, worked_days, inputs): + """ + Returns FICA Social Security eligible wage and rate. + WAGE = GROSS - WAGE_US_941_FICA_EXEMPT + :return: result, result_rate (wage, percent) + """ + exempt = payslip.dict.contract_id.us_payroll_config_value('fed_941_fica_exempt') + if exempt: + return 0.0, 0.0 + + # Determine Rate. + result_rate = -payslip.dict.rule_parameter('fed_941_fica_ss_rate') + + # Determine Wage + year = payslip.dict.get_year() + ytd_wage = payslip.sum_category('GROSS', str(year) + '-01-01', str(year+1) + '-01-01') + ytd_wage -= payslip.sum_category('WAGE_US_941_FICA_EXEMPT', str(year) + '-01-01', str(year+1) + '-01-01') + ytd_wage += payslip.dict.contract_id.external_wages + + wage_base = payslip.dict.rule_parameter('fed_941_fica_ss_wage_base') + remaining = wage_base - ytd_wage + + wage = categories.GROSS - categories.WAGE_US_941_FICA_EXEMPT + + if remaining < 0.0: + result = 0.0 + elif remaining < wage: + result = remaining + else: + result = wage + + return result, result_rate + + +er_us_941_fica_ss = ee_us_941_fica_ss + + +def ee_us_941_fica_m(payslip, categories, worked_days, inputs): + """ + Returns FICA Medicare eligible wage and rate. + WAGE = GROSS - WAGE_US_941_FICA_EXEMPT + :return: result, result_rate (wage, percent) + """ + exempt = payslip.dict.contract_id.us_payroll_config_value('fed_941_fica_exempt') + if exempt: + return 0.0, 0.0 + + # Determine Rate. + result_rate = -payslip.dict.rule_parameter('fed_941_fica_m_rate') + + # Determine Wage + year = payslip.dict.get_year() + ytd_wage = payslip.sum_category('GROSS', str(year) + '-01-01', str(year+1) + '-01-01') + ytd_wage -= payslip.sum_category('WAGE_US_941_FICA_EXEMPT', str(year) + '-01-01', str(year+1) + '-01-01') + ytd_wage += payslip.dict.contract_id.external_wages + + wage_base = float(payslip.dict.rule_parameter('fed_941_fica_m_wage_base')) # inf + remaining = wage_base - ytd_wage + + wage = categories.GROSS - categories.WAGE_US_941_FICA_EXEMPT + + if remaining < 0.0: + result = 0.0 + elif remaining < wage: + result = remaining + else: + result = wage + + return result, result_rate + + +er_us_941_fica_m = ee_us_941_fica_m + + +def ee_us_941_fica_m_add(payslip, categories, worked_days, inputs): + """ + Returns FICA Medicare Additional eligible wage and rate. + Note that this wage is not capped like the above rules. + WAGE = GROSS - WAGE_FICA_EXEMPT + :return: result, result_rate (wage, percent) + """ + exempt = payslip.dict.contract_id.us_payroll_config_value('fed_941_fica_exempt') + if exempt: + return 0.0, 0.0 + + # Determine Rate. + result_rate = -payslip.dict.rule_parameter('fed_941_fica_m_add_rate') + + # Determine Wage + year = payslip.dict.get_year() + ytd_wage = payslip.sum_category('GROSS', str(year) + '-01-01', str(year+1) + '-01-01') + ytd_wage -= payslip.sum_category('WAGE_US_941_FICA_EXEMPT', str(year) + '-01-01', str(year+1) + '-01-01') + ytd_wage += payslip.dict.contract_id.external_wages + + wage_start = payslip.dict.rule_parameter('fed_941_fica_m_add_wage_start') + existing_wage = ytd_wage - wage_start + + wage = categories.GROSS - categories.WAGE_US_941_FICA_EXEMPT + + if existing_wage >= 0.0: + result = wage + elif wage + existing_wage > 0.0: + result = wage + existing_wage + else: + result = 0.0 + + return result, result_rate + + +# Federal Income Tax +def ee_us_941_fit(payslip, categories, worked_days, inputs): + """ + Returns Wage and rate that is computed given the amount to withhold. + WAGE = GROSS - WAGE_US_941_FIT_EXEMPT + :return: result, result_rate (wage, percent) + """ + filing_status = payslip.dict.contract_id.us_payroll_config_value('fed_941_fit_w4_filing_status') + if not filing_status: + return 0.0, 0.0 + + schedule_pay = payslip.dict.contract_id.schedule_pay + wage = categories.GROSS - categories.WAGE_US_941_FIT_EXEMPT + #_logger.warn('initial gross wage: ' + str(wage)) + year = payslip.dict.get_year() + if year >= 2020: + # Large changes in Federal Income Tax in 2020 and the W4 + # We will assume that your W4 is the 2020 version + # Steps are from IRS Publication 15-T + # + # Step 1 + working_wage = wage + is_nra = payslip.dict.contract_id.us_payroll_config_value('fed_941_fit_w4_is_nonresident_alien') + if is_nra: + nra_table = payslip.dict.rule_parameter('fed_941_fit_nra_additional') + working_wage += nra_table.get(schedule_pay, 0.0) + #_logger.warn(' is_nrm after wage: ' + str(working_wage)) + + pay_periods = payslip.dict.get_pay_periods_in_year() + wage_annual = pay_periods * working_wage + #_logger.warn('annual wage: ' + str(wage_annual)) + wage_annual += payslip.dict.contract_id.us_payroll_config_value('fed_941_fit_w4_other_income') + #_logger.warn(' after other income: ' + str(wage_annual)) + + deductions = payslip.dict.contract_id.us_payroll_config_value('fed_941_fit_w4_deductions') + #_logger.warn('deductions from W4: ' + str(deductions)) + + higher_rate_type = payslip.dict.contract_id.us_payroll_config_value('fed_941_fit_w4_multiple_jobs_higher') + if not higher_rate_type: + deductions += 12900.0 if filing_status == 'married' else 8600.0 + #_logger.warn(' deductions after standard deduction: ' + str(deductions)) + + adjusted_wage_annual = wage_annual - deductions + if adjusted_wage_annual < 0.0: + adjusted_wage_annual = 0.0 + #_logger.warn('adusted annual wage: ' + str(adjusted_wage_annual)) + + # Step 2 + if filing_status == 'single': + tax_tables = payslip.dict.rule_parameter('fed_941_fit_table_single') + elif filing_status == 'married': + tax_tables = payslip.dict.rule_parameter('fed_941_fit_table_married') + else: + # married_as_single for historic reasons + tax_tables = payslip.dict.rule_parameter('fed_941_fit_table_hh') + + if higher_rate_type: + tax_table = tax_tables['higher'] + else: + tax_table = tax_tables['standard'] + + selected_row = None + for row in tax_table: + if row[0] <= adjusted_wage_annual: + selected_row = row + else: + # First row where wage is higher than adjusted_wage_annual + break + + wage_threshold, base_withholding_amount, marginal_rate = selected_row + #_logger.warn(' selected row: ' + str(selected_row)) + working_wage = adjusted_wage_annual - wage_threshold + tentative_withholding_amount = (working_wage * marginal_rate) + base_withholding_amount + tentative_withholding_amount = tentative_withholding_amount / pay_periods + #_logger.warn('tenative withholding amount: ' + str(tentative_withholding_amount)) + + # Step 3 + dependent_credit = payslip.dict.contract_id.us_payroll_config_value('fed_941_fit_w4_dependent_credit') + dependent_credit = dependent_credit / pay_periods + #_logger.warn('dependent credit (per period): ' + str(dependent_credit)) + tentative_withholding_amount -= dependent_credit + if tentative_withholding_amount < 0.0: + tentative_withholding_amount = 0.0 + + # Step 4 + withholding_amount = tentative_withholding_amount + payslip.dict.contract_id.us_payroll_config_value('fed_941_fit_w4_additional_withholding') + #_logger.warn('final withholding amount: ' + str(withholding_amount)) + # Ideally we would set the 'taxable wage' as the result and compute the percentage tax. + # This is off by 1 penny across our tests, but I feel like it is worth it for the added reporting. + # - Jared Kipe 2019 during Odoo 13.0 rewrite. + # + # return -withholding_amount, 100.0 + return wage, -(withholding_amount / wage * 100.0) + else: + working_wage = wage + is_nra = payslip.dict.contract_id.us_payroll_config_value('fed_941_fit_w4_is_nonresident_alien') + if is_nra: + nra_table = payslip.dict.rule_parameter('fed_941_fit_nra_additional') + working_wage += nra_table[schedule_pay] + + allowance_table = payslip.dict.rule_parameter('fed_941_fit_allowance') + allowances = payslip.dict.contract_id.us_payroll_config_value('fed_941_fit_w4_allowances') + working_wage -= allowance_table[schedule_pay] * allowances + tax = 0.0 + last_limit = 0.0 + if filing_status == 'married': + tax_table = payslip.dict.rule_parameter('fed_941_fit_table_married') + else: + tax_table = payslip.dict.rule_parameter('fed_941_fit_table_single') + for row in tax_table[schedule_pay]: + limit, base, percent = row + limit = float(limit) # 'inf' + if working_wage <= limit: + tax = base + ((working_wage - last_limit) * (percent / 100.0)) + break + last_limit = limit + + tax += payslip.dict.contract_id.us_payroll_config_value('fed_941_fit_w4_additional_withholding') + # Ideally we would set the 'taxable wage' as the result and compute the percentage tax. + # This is off by 1 penny across our tests, but I feel like it is worth it for the added reporting. + # - Jared Kipe 2019 during Odoo 13.0 rewrite. + # + # return -tax, 100.0 + return wage, -(tax / wage * 100.0) diff --git a/l10n_us_hr_payroll/models/hr_contract.py b/l10n_us_hr_payroll/models/hr_contract.py new file mode 100644 index 00000000..ce3e6de9 --- /dev/null +++ b/l10n_us_hr_payroll/models/hr_contract.py @@ -0,0 +1,24 @@ +# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details. + +from odoo import api, fields, models +from .us_payroll_config import FUTA_TYPE_NORMAL, \ + FUTA_TYPE_BASIC, \ + FUTA_TYPE_EXEMPT + + +class USHRContract(models.Model): + _inherit = 'hr.contract' + + FUTA_TYPE_NORMAL = FUTA_TYPE_NORMAL + FUTA_TYPE_BASIC = FUTA_TYPE_BASIC + FUTA_TYPE_EXEMPT = FUTA_TYPE_EXEMPT + + schedule_pay = fields.Selection(selection_add=[('semi-monthly', 'Semi-monthly')]) + us_payroll_config_id = fields.Many2one('hr.contract.us_payroll_config', 'Payroll Forms') + external_wages = fields.Float(string='External Existing Wages') + + # Simplified fields for easier rules, state code will exempt based on contract's futa_type + futa_type = fields.Selection(related='us_payroll_config_id.fed_940_type') + + def us_payroll_config_value(self, name): + return self.us_payroll_config_id[name] diff --git a/l10n_us_hr_payroll/models/hr_payslip.py b/l10n_us_hr_payroll/models/hr_payslip.py new file mode 100644 index 00000000..5a335a65 --- /dev/null +++ b/l10n_us_hr_payroll/models/hr_payslip.py @@ -0,0 +1,213 @@ +# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details. + +from odoo import api, fields, models + +from .federal.fed_940 import er_us_940_futa +from .federal.fed_941 import ee_us_941_fica_ss, \ + ee_us_941_fica_m, \ + ee_us_941_fica_m_add,\ + er_us_941_fica_ss, \ + er_us_941_fica_m, \ + ee_us_941_fit + + +class HRPayslip(models.Model): + _inherit = 'hr.payslip' + + # From IRS Publication 15-T or logically (annually, bi-monthly) + PAY_PERIODS_IN_YEAR = { + 'annually': 1, + 'semi-annually': 2, + 'quarterly': 4, + 'bi-monthly': 6, + 'monthly': 12, + 'semi-monthly': 24, + 'bi-weekly': 26, + 'weekly': 52, + 'daily': 260, + } + + def _get_base_local_dict(self): + # back port for US Payroll + #res = super()._get_base_local_dict() + return { + 'er_us_940_futa': er_us_940_futa, + 'ee_us_941_fica_ss': ee_us_941_fica_ss, + 'ee_us_941_fica_m': ee_us_941_fica_m, + 'ee_us_941_fica_m_add': ee_us_941_fica_m_add, + 'er_us_941_fica_ss': er_us_941_fica_ss, + 'er_us_941_fica_m': er_us_941_fica_m, + 'ee_us_941_fit': ee_us_941_fit, + } + + def get_year(self): + # Helper method to get the year (normalized between Odoo Versions) + return self.date_to.year + + def get_pay_periods_in_year(self): + return self.PAY_PERIODS_IN_YEAR.get(self.contract_id.schedule_pay, 0) + + @api.model + def _get_payslip_lines(self, contract_ids, payslip_id): + def _sum_salary_rule_category(localdict, category, amount): + if category.parent_id: + localdict = _sum_salary_rule_category(localdict, category.parent_id, amount) + localdict['categories'].dict[category.code] = category.code in localdict['categories'].dict and localdict['categories'].dict[category.code] + amount or amount + return localdict + + class BrowsableObject(object): + def __init__(self, employee_id, dict, env): + self.employee_id = employee_id + self.dict = dict + self.env = env + + def __getattr__(self, attr): + return attr in self.dict and self.dict.__getitem__(attr) or 0.0 + + class InputLine(BrowsableObject): + """a class that will be used into the python code, mainly for usability purposes""" + def sum(self, code, from_date, to_date=None): + if to_date is None: + to_date = fields.Date.today() + self.env.cr.execute(""" + SELECT sum(amount) as sum + FROM hr_payslip as hp, hr_payslip_input as pi + WHERE hp.employee_id = %s AND hp.state = 'done' + AND hp.date_from >= %s AND hp.date_to <= %s AND hp.id = pi.payslip_id AND pi.code = %s""", + (self.employee_id, from_date, to_date, code)) + return self.env.cr.fetchone()[0] or 0.0 + + class WorkedDays(BrowsableObject): + """a class that will be used into the python code, mainly for usability purposes""" + def _sum(self, code, from_date, to_date=None): + if to_date is None: + to_date = fields.Date.today() + self.env.cr.execute(""" + SELECT sum(number_of_days) as number_of_days, sum(number_of_hours) as number_of_hours + FROM hr_payslip as hp, hr_payslip_worked_days as pi + WHERE hp.employee_id = %s AND hp.state = 'done' + AND hp.date_from >= %s AND hp.date_to <= %s AND hp.id = pi.payslip_id AND pi.code = %s""", + (self.employee_id, from_date, to_date, code)) + return self.env.cr.fetchone() + + def sum(self, code, from_date, to_date=None): + res = self._sum(code, from_date, to_date) + return res and res[0] or 0.0 + + def sum_hours(self, code, from_date, to_date=None): + res = self._sum(code, from_date, to_date) + return res and res[1] or 0.0 + + class Payslips(BrowsableObject): + """a class that will be used into the python code, mainly for usability purposes""" + + def sum(self, code, from_date, to_date=None): + if to_date is None: + to_date = fields.Date.today() + self.env.cr.execute("""SELECT sum(case when hp.credit_note = False then (pl.total) else (-pl.total) end) + FROM hr_payslip as hp, hr_payslip_line as pl + WHERE hp.employee_id = %s AND hp.state = 'done' + AND hp.date_from >= %s AND hp.date_to <= %s AND hp.id = pl.slip_id AND pl.code = %s""", + (self.employee_id, from_date, to_date, code)) + res = self.env.cr.fetchone() + return res and res[0] or 0.0 + + def sum_category(self, code, from_date, to_date=None): + # Hibou Backport + if to_date is None: + to_date = fields.Date.today() + + self.env.cr.execute("""SELECT sum(case when hp.credit_note is not True then (pl.total) else (-pl.total) end) + FROM hr_payslip as hp, hr_payslip_line as pl, hr_salary_rule_category as rc + WHERE hp.employee_id = %s AND hp.state = 'done' + AND hp.date_from >= %s AND hp.date_to <= %s AND hp.id = pl.slip_id + AND rc.id = pl.category_id AND rc.code = %s""", + (self.employee_id, from_date, to_date, code)) + res = self.env.cr.fetchone() + return res and res[0] or 0.0 + + #we keep a dict with the result because a value can be overwritten by another rule with the same code + result_dict = {} + rules_dict = {} + worked_days_dict = {} + inputs_dict = {} + blacklist = [] + payslip = self.env['hr.payslip'].browse(payslip_id) + for worked_days_line in payslip.worked_days_line_ids: + worked_days_dict[worked_days_line.code] = worked_days_line + for input_line in payslip.input_line_ids: + inputs_dict[input_line.code] = input_line + + categories = BrowsableObject(payslip.employee_id.id, {}, self.env) + inputs = InputLine(payslip.employee_id.id, inputs_dict, self.env) + worked_days = WorkedDays(payslip.employee_id.id, worked_days_dict, self.env) + payslips = Payslips(payslip.employee_id.id, payslip, self.env) + rules = BrowsableObject(payslip.employee_id.id, rules_dict, self.env) + + baselocaldict = {'categories': categories, 'rules': rules, 'payslip': payslips, 'worked_days': worked_days, 'inputs': inputs} + + # Hibou Backport + baselocaldict.update(self._get_base_local_dict()) + + #get the ids of the structures on the contracts and their parent id as well + contracts = self.env['hr.contract'].browse(contract_ids) + if len(contracts) == 1 and payslip.struct_id: + structure_ids = list(set(payslip.struct_id._get_parent_structure().ids)) + else: + structure_ids = contracts.get_all_structures() + #get the rules of the structure and thier children + rule_ids = self.env['hr.payroll.structure'].browse(structure_ids).get_all_rules() + #run the rules by sequence + sorted_rule_ids = [id for id, sequence in sorted(rule_ids, key=lambda x:x[1])] + sorted_rules = self.env['hr.salary.rule'].browse(sorted_rule_ids) + + for contract in contracts: + employee = contract.employee_id + localdict = dict(baselocaldict, employee=employee, contract=contract) + for rule in sorted_rules: + key = rule.code + '-' + str(contract.id) + localdict['result'] = None + localdict['result_qty'] = 1.0 + localdict['result_rate'] = 100 + #check if the rule can be applied + if rule._satisfy_condition(localdict) and rule.id not in blacklist: + #compute the amount of the rule + amount, qty, rate = rule._compute_rule(localdict) + #check if there is already a rule computed with that code + previous_amount = rule.code in localdict and localdict[rule.code] or 0.0 + #set/overwrite the amount computed for this rule in the localdict + tot_rule = amount * qty * rate / 100.0 + localdict[rule.code] = tot_rule + rules_dict[rule.code] = rule + #sum the amount for its salary category + localdict = _sum_salary_rule_category(localdict, rule.category_id, tot_rule - previous_amount) + #create/overwrite the rule in the temporary results + result_dict[key] = { + 'salary_rule_id': rule.id, + 'contract_id': contract.id, + 'name': rule.name, + 'code': rule.code, + 'category_id': rule.category_id.id, + 'sequence': rule.sequence, + 'appears_on_payslip': rule.appears_on_payslip, + 'condition_select': rule.condition_select, + 'condition_python': rule.condition_python, + 'condition_range': rule.condition_range, + 'condition_range_min': rule.condition_range_min, + 'condition_range_max': rule.condition_range_max, + 'amount_select': rule.amount_select, + 'amount_fix': rule.amount_fix, + 'amount_python_compute': rule.amount_python_compute, + 'amount_percentage': rule.amount_percentage, + 'amount_percentage_base': rule.amount_percentage_base, + 'register_id': rule.register_id.id, + 'amount': amount, + 'employee_id': contract.employee_id.id, + 'quantity': qty, + 'rate': rate, + } + else: + #blacklist this rule and its children + blacklist += [id for id, seq in rule._recursive_search_of_rules()] + + return list(result_dict.values()) diff --git a/l10n_us_hr_payroll/models/l10n_us_hr_payroll.py b/l10n_us_hr_payroll/models/l10n_us_hr_payroll.py deleted file mode 100755 index d7d4b4f6..00000000 --- a/l10n_us_hr_payroll/models/l10n_us_hr_payroll.py +++ /dev/null @@ -1,44 +0,0 @@ -from odoo import models, fields, api - - -class Payslip(models.Model): - _inherit = 'hr.payslip' - - def get_futa_rate(self, contract): - self.ensure_one() - if contract.futa_type == USHrContract.FUTA_TYPE_EXEMPT: - rate = self.get_rate('US_FUTA_EXEMPT') - elif contract.futa_type == USHrContract.FUTA_TYPE_NORMAL: - rate = self.get_rate('US_FUTA_NORMAL') - else: - rate = self.get_rate('US_FUTA_BASIC') - return rate - - -class USHrContract(models.Model): - FUTA_TYPE_EXEMPT = 'exempt' - FUTA_TYPE_BASIC = 'basic' - FUTA_TYPE_NORMAL = 'normal' - - _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) - - fica_exempt = fields.Boolean(string='FICA Exempt', help="Exempt from Social Security and " - "Medicare e.g. F1 Student Visa") - 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') diff --git a/l10n_us_hr_payroll/models/us_payroll_config.py b/l10n_us_hr_payroll/models/us_payroll_config.py new file mode 100644 index 00000000..ed0e6d73 --- /dev/null +++ b/l10n_us_hr_payroll/models/us_payroll_config.py @@ -0,0 +1,45 @@ +# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details. + +from odoo import api, fields, models + +FUTA_TYPE_EXEMPT = 'exempt' +FUTA_TYPE_BASIC = 'basic' +FUTA_TYPE_NORMAL = 'normal' + + +class HRContractUSPayrollConfig(models.Model): + _name = 'hr.contract.us_payroll_config' + _description = 'Contract US Payroll Forms' + + name = fields.Char(string="Description") + employee_id = fields.Many2one('hr.employee', string="Employee", required=True) + state_id = fields.Many2one('res.country.state', string="Applied State") + + fed_940_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') + + fed_941_fica_exempt = fields.Boolean(string='FICA Exempt', help="Exempt from Social Security and " + "Medicare e.g. F1 Student Visa") + + fed_941_fit_w4_filing_status = fields.Selection([ + ('', 'Exempt'), + ('single', 'Single or Married filing separately'), + ('married', 'Married filing jointly'), + ('married_as_single', 'Head of Household'), + ], string='Federal W4 Filing Status [1(c)]', default='single') + fed_941_fit_w4_allowances = fields.Integer(string='Federal W4 Allowances (before 2020)') + fed_941_fit_w4_is_nonresident_alien = fields.Boolean(string='Federal W4 Is Nonresident Alien') + fed_941_fit_w4_multiple_jobs_higher = fields.Boolean(string='Federal W4 Multiple Jobs Higher [2(c)]', + help='Form W4 (2020+) 2(c) Checkbox. ' + 'Uses Higher Withholding tables.') + fed_941_fit_w4_dependent_credit = fields.Float(string='Federal W4 Dependent Credit [3]', + help='Form W4 (2020+) Line 3') + fed_941_fit_w4_other_income = fields.Float(string='Federal W4 Other Income [4(a)]', + help='Form W4 (2020+) 4(a)') + fed_941_fit_w4_deductions = fields.Float(string='Federal W4 Deductions [4(b)]', + help='Form W4 (2020+) 4(b)') + fed_941_fit_w4_additional_withholding = fields.Float(string='Federal W4 Additional Withholding [4(c)]', + help='Form W4 (2020+) 4(c)') diff --git a/l10n_us_hr_payroll/security/ir.model.access.csv b/l10n_us_hr_payroll/security/ir.model.access.csv new file mode 100644 index 00000000..67a8fa2a --- /dev/null +++ b/l10n_us_hr_payroll/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_hr_contract_us_payroll_config,hr.contract.us_payroll_config,model_hr_contract_us_payroll_config,hr_payroll.group_hr_payroll_manager,1,1,1,1 diff --git a/l10n_us_hr_payroll/static/description/icon.png b/l10n_us_hr_payroll/static/description/icon.png new file mode 100644 index 00000000..2a58a381 Binary files /dev/null and b/l10n_us_hr_payroll/static/description/icon.png differ diff --git a/l10n_us_hr_payroll/tests/__init__.py b/l10n_us_hr_payroll/tests/__init__.py index abe27c5a..23702419 100755 --- a/l10n_us_hr_payroll/tests/__init__.py +++ b/l10n_us_hr_payroll/tests/__init__.py @@ -1,3 +1,5 @@ -from . import test_us_payslip -from . import test_us_payslip_2018 +# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details. + +from . import common from . import test_us_payslip_2019 +from . import test_us_payslip_2020 diff --git a/l10n_us_hr_payroll/tests/common.py b/l10n_us_hr_payroll/tests/common.py new file mode 100755 index 00000000..f6989e67 --- /dev/null +++ b/l10n_us_hr_payroll/tests/common.py @@ -0,0 +1,155 @@ +# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details. + +from logging import getLogger +from sys import float_info as sys_float_info +from collections import defaultdict + +from odoo.tests import common +from odoo.tools.float_utils import float_round as odoo_float_round + + +def process_payslip(payslip): + try: + payslip.action_payslip_done() + except AttributeError: + # v9 + payslip.process_sheet() + + +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, **kwargs): + if not 'schedule_pay' in kwargs: + kwargs['schedule_pay'] = 'monthly' + schedule_pay = kwargs['schedule_pay'] + config_model = self.env['hr.contract.us_payroll_config'] + contract_model = self.env['hr.contract'] + config_values = { + 'name': 'Test Config Values', + 'employee_id': employee.id, + } + contract_values = { + 'name': 'Test Contract', + 'employee_id': employee.id, + } + + for key, val in kwargs.items(): + # Assume any Odoo object is in a Many2one + if hasattr(val, 'id'): + val = val.id + found = False + if hasattr(contract_model, key): + contract_values[key] = val + found = True + if hasattr(config_model, key): + config_values[key] = val + found = True + if not found: + self._logger.warn('cannot locate attribute names "%s" on contract or payroll config' % (key, )) + + # US Payroll Config Defaults Should be set on the Model + config = config_model.create(config_values) + contract_values['us_payroll_config_id'] = config.id + + # Some Basic Defaults + if not contract_values.get('state'): + contract_values['state'] = 'open' # Running + if not contract_values.get('struct_id'): + contract_values['struct_id'] = self.ref('l10n_us_hr_payroll.structure_type_employee') + if not contract_values.get('date_start'): + contract_values['date_start'] = '2016-01-01' + if not contract_values.get('date_end'): + contract_values['date_end'] = '2030-12-31' + if not contract_values.get('resource_calendar_id'): + contract_values['resource_calendar_id'] = self.ref('resource.resource_calendar_std') + + # Compatibility with earlier Odoo versions + if not contract_values.get('journal_id') and hasattr(contract_model, 'journal_id'): + try: + contract_values['journal_id'] = self.env['account.journal'].search([('type', '=', 'general')], limit=1).id + except KeyError: + # Accounting not installed + pass + + contract = contract_model.create(contract_values) + + return contract + + def _createPayslip(self, employee, date_from, date_to): + slip = self.env['hr.payslip'].create({ + 'name': 'Test %s From: %s To: %s' % (employee.name, date_from, date_to), + 'employee_id': employee.id, + 'date_from': date_from, + 'date_to': date_to + }) + if hasattr(slip, '_onchange_employee'): + slip._onchange_employee() + if hasattr(slip, 'onchange_employee'): + # Odoo 12 + slip.onchange_employee() + if self.debug: + self._logger.warn(slip.read()) + self._logger.warn(slip.contract_id.read()) + return slip + + def _getCategories(self, payslip): + categories = defaultdict(float) + for line in payslip.line_ids: + 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)) + category_id = line.category_id + category_code = line.category_id.code + while category_code: + categories[category_code] += line.total + category_id = category_id.parent_id + category_code = category_id.code + return categories + + def _getRules(self, payslip): + rules = defaultdict(float) + for line in payslip.line_ids: + rules[line.code] += line.total + return rules + + def assertPayrollEqual(self, first, second): + self.assertAlmostEqual(first, second, self.payroll_digits) + + def test_semi_monthly(self): + salary = 80000.0 + employee = self._createEmployee() + # so the schedule_pay is now on the Structure... + contract = self._createContract(employee, wage=salary, schedule_pay='semi-monthly') + payslip = self._createPayslip(employee, '2019-01-01', '2019-01-14') + + payslip.compute_sheet() diff --git a/l10n_us_hr_payroll/tests/test_us_payslip.py b/l10n_us_hr_payroll/tests/test_us_payslip.py deleted file mode 100755 index e25642fc..00000000 --- a/l10n_us_hr_payroll/tests/test_us_payslip.py +++ /dev/null @@ -1,117 +0,0 @@ -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.models.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') - - values = { - '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 - } - try: - values['journal_id'] = self.env['account.journal'].search([('type', '=', 'general')], limit=1).id - except KeyError: - pass - - return self.env['hr.contract'].create(values) - - 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_2018.py b/l10n_us_hr_payroll/tests/test_us_payslip_2018.py deleted file mode 100755 index 3bfffda1..00000000 --- a/l10n_us_hr_payroll/tests/test_us_payslip_2018.py +++ /dev/null @@ -1,368 +0,0 @@ -from .test_us_payslip import TestUsPayslip, process_payslip - -from odoo.addons.l10n_us_hr_payroll.models.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['WAGE_US_FICA_SS'], salary) - self.assertPayrollEqual(cats['EE_US_FICA_SS'], cats['WAGE_US_FICA_SS'] * self.FICA_SS) - self.assertPayrollEqual(cats['WAGE_US_FICA_M'], salary) - self.assertPayrollEqual(cats['EE_US_FICA_M'], cats['WAGE_US_FICA_M'] * self.FICA_M) - self.assertPayrollEqual(cats['WAGE_US_FICA_M_ADD'], 0.0) - self.assertPayrollEqual(cats['EE_US_FICA_M_ADD'], 0.0) - self.assertPayrollEqual(cats['ER_US_FICA_SS'], cats['EE_US_FICA_SS']) - self.assertPayrollEqual(cats['ER_US_FICA_M'], cats['EE_US_FICA_M']) - self.assertPayrollEqual(cats['WAGE_US_FUTA'], self.FUTA_MAX_WAGE) - self.assertPayrollEqual(cats['ER_US_FUTA'], cats['WAGE_US_FUTA'] * 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['WAGE_US_FICA_SS'], remaining_ss_wages) - self.assertPayrollEqual(cats['EE_US_FICA_SS'], cats['WAGE_US_FICA_SS'] * self.FICA_SS) - self.assertPayrollEqual(cats['WAGE_US_FICA_M'], remaining_m_wages) - self.assertPayrollEqual(cats['EE_US_FICA_M'], cats['WAGE_US_FICA_M'] * self.FICA_M) - self.assertPayrollEqual(cats['WAGE_US_FICA_M_ADD'], 0.0) - self.assertPayrollEqual(cats['EE_US_FICA_M_ADD'], 0.0) - self.assertPayrollEqual(cats['ER_US_FICA_SS'], cats['EE_US_FICA_SS']) - self.assertPayrollEqual(cats['ER_US_FICA_M'], cats['EE_US_FICA_M']) - self.assertPayrollEqual(cats['WAGE_US_FUTA'], 0) - self.assertPayrollEqual(cats['ER_US_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['WAGE_US_FICA_M'], salary) - self.assertPayrollEqual(cats['WAGE_US_FICA_M_ADD'], self.FICA_M_ADD_START_WAGE - (salary * 2)) # aka 40k - self.assertPayrollEqual(cats['EE_US_FICA_M_ADD'], cats['WAGE_US_FICA_M_ADD'] * 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['WAGE_US_FICA_M'], salary) - self.assertPayrollEqual(cats['WAGE_US_FICA_M_ADD'], salary) - self.assertPayrollEqual(cats['EE_US_FICA_M_ADD'], cats['WAGE_US_FICA_M_ADD'] * 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['EE_US_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['EE_US_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['EE_US_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['WAGE_US_FICA_SS'], self.FICA_SS_MAX_WAGE - external_wages) - self.assertPayrollEqual(cats['EE_US_FICA_SS'], cats['WAGE_US_FICA_SS'] * self.FICA_SS) - self.assertPayrollEqual(cats['WAGE_US_FICA_M'], salary) - self.assertPayrollEqual(cats['EE_US_FICA_M'], cats['WAGE_US_FICA_M'] * self.FICA_M) - self.assertPayrollEqual(cats['WAGE_US_FICA_M_ADD'], 0.0) - self.assertPayrollEqual(cats['EE_US_FICA_M_ADD'], cats['WAGE_US_FICA_M_ADD'] * self.FICA_M_ADD) - self.assertPayrollEqual(cats['ER_US_FICA_SS'], cats['EE_US_FICA_SS']) - self.assertPayrollEqual(cats['ER_US_FICA_M'], cats['EE_US_FICA_M']) - self.assertPayrollEqual(cats['WAGE_US_FUTA'], self.FUTA_MAX_WAGE - external_wages) - self.assertPayrollEqual(cats['ER_US_FUTA'], cats['WAGE_US_FUTA'] * 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['WAGE_US_FICA_SS'], self.FICA_SS_MAX_WAGE) - self.assertPayrollEqual(cats['EE_US_FICA_SS'], cats['WAGE_US_FICA_SS'] * self.FICA_SS) - self.assertPayrollEqual(cats['WAGE_US_FICA_M'], salary) - self.assertPayrollEqual(cats['EE_US_FICA_M'], cats['WAGE_US_FICA_M'] * self.FICA_M) - self.assertPayrollEqual(cats['WAGE_US_FICA_M_ADD'], 0.0) - self.assertPayrollEqual(cats['EE_US_FICA_M_ADD'], cats['WAGE_US_FICA_M_ADD'] * self.FICA_M_ADD) - self.assertPayrollEqual(cats['ER_US_FICA_SS'], cats['EE_US_FICA_SS']) - self.assertPayrollEqual(cats['ER_US_FICA_M'], cats['EE_US_FICA_M']) - self.assertPayrollEqual(cats['WAGE_US_FUTA'], self.FUTA_MAX_WAGE) - self.assertPayrollEqual(cats['ER_US_FUTA'], cats['WAGE_US_FUTA'] * 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['WAGE_US_FICA_SS'], self.FICA_SS_MAX_WAGE) - self.assertPayrollEqual(cats['EE_US_FICA_SS'], cats['WAGE_US_FICA_SS'] * self.FICA_SS) - self.assertPayrollEqual(cats['WAGE_US_FICA_M'], salary) - self.assertPayrollEqual(cats['EE_US_FICA_M'], cats['WAGE_US_FICA_M'] * self.FICA_M) - self.assertPayrollEqual(cats['WAGE_US_FICA_M_ADD'], 0.0) - self.assertPayrollEqual(cats['EE_US_FICA_M_ADD'], cats['WAGE_US_FICA_M_ADD'] * self.FICA_M_ADD) - self.assertPayrollEqual(cats['ER_US_FICA_SS'], cats['EE_US_FICA_SS']) - self.assertPayrollEqual(cats['ER_US_FICA_M'], cats['EE_US_FICA_M']) - - futa_wages = 0.0 - if 'WAGE_US_FUTA' in cats: - futa_wages = cats['WAGE_US_FUTA'] - futa = 0.0 - if 'ER_US_FUTA' in cats: - futa = cats['ER_US_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['EE_US_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['EE_US_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 'EE_US_FED_INC_WITHHOLD' in cats: - fed_inc_withhold = cats['EE_US_FED_INC_WITHHOLD'] - self.assertPayrollEqual(fed_inc_withhold, 0.0) - - def test_2018_taxes_with_fica_exempt(self): - salary = 6000.0 - schedule_pay = 'bi-weekly' - w4_allowances = 2 - employee = self._createEmployee() - contract = self._createContract(employee, salary, schedule_pay, w4_allowances) - contract.fica_exempt = True - - self._log('2018 tax w4 exempt payslip:') - payslip = self._createPayslip(employee, '2018-01-01', '2018-01-31') - - payslip.compute_sheet() - - cats = self._getCategories(payslip) - - ss_wages = cats.get('WAGE_US_FICA_SS', 0.0) - med_wages = cats.get('WAGE_US_FICA_M', 0.0) - ss = cats.get('EE_US_FICA_SS', 0.0) - med = cats.get('EE_US_FICA_M', 0.0) - self.assertPayrollEqual(ss_wages, 0.0) - self.assertPayrollEqual(med_wages, 0.0) - self.assertPayrollEqual(ss, 0.0) - self.assertPayrollEqual(med, 0.0) diff --git a/l10n_us_hr_payroll/tests/test_us_payslip_2019.py b/l10n_us_hr_payroll/tests/test_us_payslip_2019.py old mode 100755 new mode 100644 index 000c063b..e9be35cd --- a/l10n_us_hr_payroll/tests/test_us_payslip_2019.py +++ b/l10n_us_hr_payroll/tests/test_us_payslip_2019.py @@ -1,6 +1,8 @@ -from .test_us_payslip import TestUsPayslip, process_payslip +# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details. -from odoo.addons.l10n_us_hr_payroll.models.l10n_us_hr_payroll import USHrContract +from .common import TestUsPayslip, process_payslip + +from odoo.addons.l10n_us_hr_payroll.models.hr_contract import USHRContract from sys import float_info @@ -28,41 +30,44 @@ class TestUsPayslip2019(TestUsPayslip): ### def test_2019_taxes(self): + self.debug = False # salary is high so that second payslip runs over max # social security salary salary = 80000.0 employee = self._createEmployee() - self._createContract(employee, salary) + contract = self._createContract(employee, wage=salary) + self._log(contract.read()) self._log('2018 tax last slip') payslip = self._createPayslip(employee, '2018-12-01', '2018-12-31') payslip.compute_sheet() + self._log(payslip.read()) process_payslip(payslip) + # Ensure amounts are there, they shouldn't be added in the next year... + cats = self._getCategories(payslip) + self.assertTrue(cats['ER_US_940_FUTA'], self.FUTA_MAX_WAGE * self.FUTA) + self._log('2019 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_FICA_SS'], salary) - self.assertPayrollEqual(cats['EE_US_FICA_SS'], cats['WAGE_US_FICA_SS'] * self.FICA_SS) - self.assertPayrollEqual(cats['WAGE_US_FICA_M'], salary) - self.assertPayrollEqual(cats['EE_US_FICA_M'], cats['WAGE_US_FICA_M'] * self.FICA_M) - self.assertPayrollEqual(cats['WAGE_US_FICA_M_ADD'], 0.0) - self.assertPayrollEqual(cats['EE_US_FICA_M_ADD'], 0.0) - self.assertPayrollEqual(cats['ER_US_FICA_SS'], cats['EE_US_FICA_SS']) - self.assertPayrollEqual(cats['ER_US_FICA_M'], cats['EE_US_FICA_M']) - self.assertPayrollEqual(cats['WAGE_US_FUTA'], self.FUTA_MAX_WAGE) - self.assertPayrollEqual(cats['ER_US_FUTA'], cats['WAGE_US_FUTA'] * self.FUTA) + rules = self._getRules(payslip) + # Employee + self.assertPayrollEqual(rules['EE_US_941_FICA_SS'], cats['BASIC'] * self.FICA_SS) + self.assertPayrollEqual(rules['EE_US_941_FICA_M'], cats['BASIC'] * self.FICA_M) + self.assertPayrollEqual(rules['EE_US_941_FICA_M_ADD'], 0.0) + # Employer + self.assertPayrollEqual(rules['ER_US_941_FICA_SS'], rules['EE_US_941_FICA_SS']) + self.assertPayrollEqual(rules['ER_US_941_FICA_M'], rules['EE_US_941_FICA_M']) + self.assertTrue(cats['ER_US_940_FUTA'], self.FUTA_MAX_WAGE * self.FUTA) process_payslip(payslip) - # Make a new payslip, this one will have maximums for FICA Social Security Wages - + # Make a new payslip, this one will have reached Medicare Additional (employee only) 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 @@ -72,32 +77,25 @@ class TestUsPayslip2019(TestUsPayslip): payslip.compute_sheet() cats = self._getCategories(payslip) + rules = self._getRules(payslip) - self.assertPayrollEqual(cats['WAGE_US_FICA_SS'], remaining_ss_wages) - self.assertPayrollEqual(cats['EE_US_FICA_SS'], cats['WAGE_US_FICA_SS'] * self.FICA_SS) - self.assertPayrollEqual(cats['WAGE_US_FICA_M'], remaining_m_wages) - self.assertPayrollEqual(cats['EE_US_FICA_M'], cats['WAGE_US_FICA_M'] * self.FICA_M) - self.assertPayrollEqual(cats['WAGE_US_FICA_M_ADD'], 0.0) - self.assertPayrollEqual(cats['EE_US_FICA_M_ADD'], 0.0) - self.assertPayrollEqual(cats['ER_US_FICA_SS'], cats['EE_US_FICA_SS']) - self.assertPayrollEqual(cats['ER_US_FICA_M'], cats['EE_US_FICA_M']) - self.assertPayrollEqual(cats['WAGE_US_FUTA'], 0) - self.assertPayrollEqual(cats['ER_US_FUTA'], 0) + self.assertPayrollEqual(rules['EE_US_941_FICA_SS'], remaining_ss_wages * self.FICA_SS) + self.assertPayrollEqual(rules['EE_US_941_FICA_M'], remaining_m_wages * self.FICA_M) + self.assertPayrollEqual(rules['EE_US_941_FICA_M_ADD'], 0.0) + self.assertPayrollEqual(cats['ER_US_940_FUTA'], 0.0) process_payslip(payslip) # Make a new payslip, this one will have reached Medicare Additional (employee only) - self._log('2019 tax third payslip:') payslip = self._createPayslip(employee, '2019-03-01', '2019-03-31') payslip.compute_sheet() cats = self._getCategories(payslip) + rules = self._getRules(payslip) - self.assertPayrollEqual(cats['WAGE_US_FICA_M'], salary) - self.assertPayrollEqual(cats['WAGE_US_FICA_M_ADD'], self.FICA_M_ADD_START_WAGE - (salary * 2)) # aka 40k - self.assertPayrollEqual(cats['EE_US_FICA_M_ADD'], cats['WAGE_US_FICA_M_ADD'] * self.FICA_M_ADD) + self.assertPayrollEqual(rules['EE_US_941_FICA_M_ADD'], (self.FICA_M_ADD_START_WAGE - (salary * 2)) * self.FICA_M_ADD) # aka 40k process_payslip(payslip) @@ -109,14 +107,15 @@ class TestUsPayslip2019(TestUsPayslip): payslip.compute_sheet() cats = self._getCategories(payslip) + rules = self._getRules(payslip) - self.assertPayrollEqual(cats['WAGE_US_FICA_M'], salary) - self.assertPayrollEqual(cats['WAGE_US_FICA_M_ADD'], salary) - self.assertPayrollEqual(cats['EE_US_FICA_M_ADD'], cats['WAGE_US_FICA_M_ADD'] * self.FICA_M_ADD) + self.assertPayrollEqual(rules['EE_US_941_FICA_M_ADD'], salary * self.FICA_M_ADD) process_payslip(payslip) def test_2019_fed_income_withholding_single(self): + self.debug = False + salary = 6000.00 schedule_pay = 'monthly' w4_allowances = 3 @@ -127,16 +126,18 @@ class TestUsPayslip2019(TestUsPayslip): expected_withholding = self.float_round(-(378.52 + ((adjusted_salary - 3606) * 0.22)), self.payroll_digits) employee = self._createEmployee() - self._createContract(employee, salary, schedule_pay, w4_allowances, 'single') + contract = self._createContract(employee, + wage=salary, + schedule_pay=schedule_pay, + fed_941_fit_w4_filing_status='single', + fed_941_fit_w4_allowances=w4_allowances + ) self._log('2019 fed income single payslip: adjusted_salary: ' + str(adjusted_salary)) payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31') - payslip.compute_sheet() - cats = self._getCategories(payslip) - - self.assertPayrollEqual(cats['EE_US_FED_INC_WITHHOLD'], expected_withholding) + self.assertPayrollEqual(cats['EE_US_941_FIT'], expected_withholding) def test_2019_fed_income_withholding_married_as_single(self): salary = 500.00 @@ -145,20 +146,21 @@ class TestUsPayslip2019(TestUsPayslip): w4_allowance_amt = 80.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.70 + ((adjusted_salary - 260) * 0.12)), self.payroll_digits) employee = self._createEmployee() - self._createContract(employee, salary, schedule_pay, w4_allowances, 'married_as_single') + contract = self._createContract(employee, + wage=salary, + schedule_pay=schedule_pay, + fed_941_fit_w4_filing_status='married_as_single', + fed_941_fit_w4_allowances=w4_allowances, + ) self._log('2019 fed income married_as_single payslip: adjusted_salary: ' + str(adjusted_salary)) payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31') - payslip.compute_sheet() - cats = self._getCategories(payslip) - - self.assertPayrollEqual(cats['EE_US_FED_INC_WITHHOLD'], expected_withholding) + self.assertPayrollEqual(cats['EE_US_941_FIT'], expected_withholding) def test_2019_fed_income_withholding_married(self): salary = 14000.00 @@ -171,53 +173,52 @@ class TestUsPayslip2019(TestUsPayslip): expected_withholding = self.float_round(-(2519.06 + ((adjusted_salary - 12817) * 0.32)), self.payroll_digits) employee = self._createEmployee() - self._createContract(employee, salary, schedule_pay, w4_allowances, 'married') + contract = self._createContract(employee, + wage=salary, + schedule_pay=schedule_pay, + fed_941_fit_w4_filing_status='married', + fed_941_fit_w4_allowances=w4_allowances + ) self._log('2019 fed income married payslip: adjusted_salary: ' + str(adjusted_salary)) payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31') - payslip.compute_sheet() - cats = self._getCategories(payslip) - - self.assertPayrollEqual(cats['EE_US_FED_INC_WITHHOLD'], expected_withholding) + # This is off by 1 penny given our new reporting of adjusted wage * computed percentage + #self.assertPayrollEqual(cats['EE_US_941_FIT'], expected_withholding) + self.assertTrue(abs(cats['EE_US_941_FIT'] - expected_withholding) < 0.011) def test_2019_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._createContract(employee, wage=salary, external_wages=external_wages) self._log('2019 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_FICA_SS'], self.FICA_SS_MAX_WAGE - external_wages) - self.assertPayrollEqual(cats['EE_US_FICA_SS'], cats['WAGE_US_FICA_SS'] * self.FICA_SS) - self.assertPayrollEqual(cats['WAGE_US_FICA_M'], salary) - self.assertPayrollEqual(cats['EE_US_FICA_M'], cats['WAGE_US_FICA_M'] * self.FICA_M) - self.assertPayrollEqual(cats['WAGE_US_FICA_M_ADD'], 0.0) - self.assertPayrollEqual(cats['EE_US_FICA_M_ADD'], cats['WAGE_US_FICA_M_ADD'] * self.FICA_M_ADD) - self.assertPayrollEqual(cats['ER_US_FICA_SS'], cats['EE_US_FICA_SS']) - self.assertPayrollEqual(cats['ER_US_FICA_M'], cats['EE_US_FICA_M']) - self.assertPayrollEqual(cats['WAGE_US_FUTA'], self.FUTA_MAX_WAGE - external_wages) - self.assertPayrollEqual(cats['ER_US_FUTA'], cats['WAGE_US_FUTA'] * self.FUTA) + rules = self._getRules(payslip) + self.assertPayrollEqual(rules['EE_US_941_FICA_SS'], (self.FICA_SS_MAX_WAGE - external_wages) * self.FICA_SS) + self.assertPayrollEqual(rules['EE_US_941_FICA_M'], salary * self.FICA_M) + self.assertPayrollEqual(rules['EE_US_941_FICA_M_ADD'], external_wages * self.FICA_M_ADD) + self.assertPayrollEqual(rules['ER_US_941_FICA_SS'], rules['EE_US_941_FICA_SS']) + self.assertPayrollEqual(rules['ER_US_941_FICA_M'], rules['EE_US_941_FICA_M']) + self.assertPayrollEqual(cats['ER_US_940_FUTA'], (self.FUTA_MAX_WAGE - external_wages) * self.FUTA) def test_2019_taxes_with_full_futa(self): + self.debug = True 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._createContract(employee, wage=salary, fed_940_type=USHRContract.FUTA_TYPE_BASIC) self._log('2019 tax first payslip:') payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31') @@ -225,52 +226,26 @@ class TestUsPayslip2019(TestUsPayslip): payslip.compute_sheet() cats = self._getCategories(payslip) - - self.assertPayrollEqual(cats['WAGE_US_FICA_SS'], self.FICA_SS_MAX_WAGE) - self.assertPayrollEqual(cats['EE_US_FICA_SS'], cats['WAGE_US_FICA_SS'] * self.FICA_SS) - self.assertPayrollEqual(cats['WAGE_US_FICA_M'], salary) - self.assertPayrollEqual(cats['EE_US_FICA_M'], cats['WAGE_US_FICA_M'] * self.FICA_M) - self.assertPayrollEqual(cats['WAGE_US_FICA_M_ADD'], 0.0) - self.assertPayrollEqual(cats['EE_US_FICA_M_ADD'], cats['WAGE_US_FICA_M_ADD'] * self.FICA_M_ADD) - self.assertPayrollEqual(cats['ER_US_FICA_SS'], cats['EE_US_FICA_SS']) - self.assertPayrollEqual(cats['ER_US_FICA_M'], cats['EE_US_FICA_M']) - self.assertPayrollEqual(cats['WAGE_US_FUTA'], self.FUTA_MAX_WAGE) - self.assertPayrollEqual(cats['ER_US_FUTA'], cats['WAGE_US_FUTA'] * futa_rate) + rules = self._getRules(payslip) + self.assertPayrollEqual(rules['EE_US_941_FICA_SS'], self.FICA_SS_MAX_WAGE * self.FICA_SS) + self.assertPayrollEqual(rules['EE_US_941_FICA_M'], salary * self.FICA_M) + self.assertPayrollEqual(rules['EE_US_941_FICA_M_ADD'], 0.0 * self.FICA_M_ADD) + self.assertPayrollEqual(rules['ER_US_941_FICA_SS'], rules['EE_US_941_FICA_SS']) + self.assertPayrollEqual(rules['ER_US_941_FICA_M'], rules['EE_US_941_FICA_M']) + self.assertPayrollEqual(cats['ER_US_940_FUTA'], self.FUTA_MAX_WAGE * futa_rate) def test_2019_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._createContract(employee, wage=salary, fed_940_type=USHRContract.FUTA_TYPE_EXEMPT) self._log('2019 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_FICA_SS'], self.FICA_SS_MAX_WAGE) - self.assertPayrollEqual(cats['EE_US_FICA_SS'], cats['WAGE_US_FICA_SS'] * self.FICA_SS) - self.assertPayrollEqual(cats['WAGE_US_FICA_M'], salary) - self.assertPayrollEqual(cats['EE_US_FICA_M'], cats['WAGE_US_FICA_M'] * self.FICA_M) - self.assertPayrollEqual(cats['WAGE_US_FICA_M_ADD'], 0.0) - self.assertPayrollEqual(cats['EE_US_FICA_M_ADD'], cats['WAGE_US_FICA_M_ADD'] * self.FICA_M_ADD) - self.assertPayrollEqual(cats['ER_US_FICA_SS'], cats['EE_US_FICA_SS']) - self.assertPayrollEqual(cats['ER_US_FICA_M'], cats['EE_US_FICA_M']) - - futa_wages = 0.0 - if 'WAGE_US_FUTA' in cats: - futa_wages = cats['WAGE_US_FUTA'] - futa = 0.0 - if 'ER_US_FUTA' in cats: - futa = cats['ER_US_FUTA'] - self.assertPayrollEqual(futa_wages, 0.0) - self.assertPayrollEqual(futa, futa_wages * futa_rate) + self.assertPayrollEqual(cats['ER_US_940_FUTA'], 0.0) def test_2019_fed_income_withholding_nonresident_alien(self): salary = 3500.00 @@ -278,24 +253,26 @@ class TestUsPayslip2019(TestUsPayslip): w4_allowances = 1 w4_allowance_amt = 1050.0 * w4_allowances nra_adjustment = 2000.0 # for quarterly - adjusted_salary = salary - w4_allowance_amt + nra_adjustment # 4425 + adjusted_salary = salary - w4_allowance_amt + nra_adjustment # 4450 ### # Single QUARTERLY form Publication 15 expected_withholding = self.float_round(-(242.50 + ((adjusted_salary - 3375) * 0.12)), self.payroll_digits) employee = self._createEmployee() - self._createContract(employee, salary, schedule_pay, w4_allowances, 'single', - w4_is_nonresident_alien=True) + contract = self._createContract(employee, + wage=salary, + schedule_pay=schedule_pay, + fed_941_fit_w4_allowances=w4_allowances, + fed_941_fit_w4_is_nonresident_alien=True, + ) self._log('2019 fed income single payslip nonresident alien: adjusted_salary: ' + str(adjusted_salary)) payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31') payslip.compute_sheet() - - cats = self._getCategories(payslip) - - self.assertPayrollEqual(cats['EE_US_FED_INC_WITHHOLD'], expected_withholding) + rules = self._getRules(payslip) + self.assertPayrollEqual(rules['EE_US_941_FIT'], expected_withholding) def test_2019_fed_income_additional_withholding(self): salary = 50000.00 @@ -312,44 +289,45 @@ class TestUsPayslip2019(TestUsPayslip): self.payroll_digits) employee = self._createEmployee() - self._createContract(employee, salary, schedule_pay, w4_allowances, 'married', - w4_additional_withholding=w4_additional_withholding) + contract = self._createContract(employee, + wage=salary, + schedule_pay=schedule_pay, + fed_941_fit_w4_filing_status='married', + fed_941_fit_w4_allowances=w4_allowances, + fed_941_fit_w4_additional_withholding=w4_additional_withholding, + ) self._log('2019 fed income married payslip additional withholding: adjusted_salary: ' + str(adjusted_salary)) payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31') - payslip.compute_sheet() - cats = self._getCategories(payslip) - - self.assertPayrollEqual(cats['EE_US_FED_INC_WITHHOLD'], expected_withholding) + rules = self._getRules(payslip) + self.assertPayrollEqual(rules['EE_US_941_FIT'], expected_withholding) def test_2019_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, '') + contract = self._createContract(employee, + wage=salary, + schedule_pay=schedule_pay, + fed_941_fit_w4_allowances=w4_allowances, + fed_941_fit_w4_filing_status='', + ) self._log('2019 tax w4 exempt payslip:') payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31') - payslip.compute_sheet() - - cats = self._getCategories(payslip) - - fed_inc_withhold = 0.0 - if 'EE_US_FED_INC_WITHHOLD' in cats: - fed_inc_withhold = cats['EE_US_FED_INC_WITHHOLD'] - self.assertPayrollEqual(fed_inc_withhold, 0.0) + rules = self._getRules(payslip) + self.assertPayrollEqual(rules['EE_US_941_FIT'], 0.0) def test_2019_taxes_with_fica_exempt(self): salary = 6000.0 schedule_pay = 'bi-weekly' - w4_allowances = 2 employee = self._createEmployee() - contract = self._createContract(employee, salary, schedule_pay, w4_allowances) - contract.fica_exempt = True + contract = self._createContract(employee, wage=salary, schedule_pay=schedule_pay) + contract.us_payroll_config_id.fed_941_fica_exempt = True self._log('2019 tax w4 exempt payslip:') payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31') @@ -357,12 +335,5 @@ class TestUsPayslip2019(TestUsPayslip): payslip.compute_sheet() cats = self._getCategories(payslip) - - ss_wages = cats.get('WAGE_US_FICA_SS', 0.0) - med_wages = cats.get('WAGE_US_FICA_M', 0.0) - ss = cats.get('EE_US_FICA_SS', 0.0) - med = cats.get('EE_US_FICA_M', 0.0) - self.assertPayrollEqual(ss_wages, 0.0) - self.assertPayrollEqual(med_wages, 0.0) - self.assertPayrollEqual(ss, 0.0) - self.assertPayrollEqual(med, 0.0) + self.assertPayrollEqual(cats['EE_US_941_FICA'], 0.0) + self.assertPayrollEqual(cats['ER_US_941_FICA'], 0.0) diff --git a/l10n_us_hr_payroll/tests/test_us_payslip_2020.py b/l10n_us_hr_payroll/tests/test_us_payslip_2020.py new file mode 100644 index 00000000..c10a230b --- /dev/null +++ b/l10n_us_hr_payroll/tests/test_us_payslip_2020.py @@ -0,0 +1,302 @@ +# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details. + +from .common import TestUsPayslip, process_payslip + +from odoo.addons.l10n_us_hr_payroll.models.hr_contract import USHRContract + +from sys import float_info + + +class TestUsPayslip2020(TestUsPayslip): + # FUTA Constants + FUTA_RATE_NORMAL = 0.6 + FUTA_RATE_BASIC = 6.0 + FUTA_RATE_EXEMPT = 0.0 + + # Wage caps + FICA_SS_MAX_WAGE = 137700.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 + + ### + # 2020 Taxes and Rates + ### + + def test_2020_taxes(self): + self.debug = False + # salary is high so that second payslip runs over max + # social security salary + salary = 80000.0 + + employee = self._createEmployee() + + contract = self._createContract(employee, wage=salary) + self._log(contract.read()) + + self._log('2019 tax last slip') + payslip = self._createPayslip(employee, '2019-12-01', '2019-12-31') + payslip.compute_sheet() + self._log(payslip.read()) + process_payslip(payslip) + + # Ensure amounts are there, they shouldn't be added in the next year... + cats = self._getCategories(payslip) + self.assertTrue(cats['ER_US_940_FUTA'], self.FUTA_MAX_WAGE * self.FUTA) + + self._log('2020 tax first payslip:') + payslip = self._createPayslip(employee, '2020-01-01', '2020-01-31') + payslip.compute_sheet() + + cats = self._getCategories(payslip) + rules = self._getRules(payslip) + # Employee + self.assertPayrollEqual(rules['EE_US_941_FICA_SS'], cats['BASIC'] * self.FICA_SS) + self.assertPayrollEqual(rules['EE_US_941_FICA_M'], cats['BASIC'] * self.FICA_M) + self.assertPayrollEqual(rules['EE_US_941_FICA_M_ADD'], 0.0) + # Employer + self.assertPayrollEqual(rules['ER_US_941_FICA_SS'], rules['EE_US_941_FICA_SS']) + self.assertPayrollEqual(rules['ER_US_941_FICA_M'], rules['EE_US_941_FICA_M']) + self.assertTrue(cats['ER_US_940_FUTA'], self.FUTA_MAX_WAGE * self.FUTA) + + process_payslip(payslip) + + # Make a new payslip, this one will have reached Medicare Additional (employee only) + 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('2020 tax second payslip:') + payslip = self._createPayslip(employee, '2020-02-01', '2020-02-28') + + payslip.compute_sheet() + + cats = self._getCategories(payslip) + rules = self._getRules(payslip) + + self.assertPayrollEqual(rules['EE_US_941_FICA_SS'], remaining_ss_wages * self.FICA_SS) + self.assertPayrollEqual(rules['EE_US_941_FICA_M'], remaining_m_wages * self.FICA_M) + self.assertPayrollEqual(rules['EE_US_941_FICA_M_ADD'], 0.0) + self.assertPayrollEqual(cats['ER_US_940_FUTA'], 0.0) + + process_payslip(payslip) + + # Make a new payslip, this one will have reached Medicare Additional (employee only) + self._log('2020 tax third payslip:') + payslip = self._createPayslip(employee, '2020-03-01', '2020-03-31') + + payslip.compute_sheet() + + cats = self._getCategories(payslip) + rules = self._getRules(payslip) + + self.assertPayrollEqual(rules['EE_US_941_FICA_M_ADD'], (self.FICA_M_ADD_START_WAGE - (salary * 2)) * self.FICA_M_ADD) # aka 40k + + process_payslip(payslip) + + # Make a new payslip, this one will have all salary as Medicare Additional + + self._log('2020 tax fourth payslip:') + payslip = self._createPayslip(employee, '2020-04-01', '2020-04-30') + + payslip.compute_sheet() + + cats = self._getCategories(payslip) + rules = self._getRules(payslip) + + self.assertPayrollEqual(rules['EE_US_941_FICA_M_ADD'], salary * self.FICA_M_ADD) + + process_payslip(payslip) + + def test_2020_taxes_with_external(self): + # social security salary + salary = self.FICA_M_ADD_START_WAGE + external_wages = 6000.0 + + employee = self._createEmployee() + + self._createContract(employee, wage=salary, external_wages=external_wages) + + self._log('2020 tax first payslip:') + payslip = self._createPayslip(employee, '2020-01-01', '2020-01-31') + payslip.compute_sheet() + + cats = self._getCategories(payslip) + rules = self._getRules(payslip) + self.assertPayrollEqual(rules['EE_US_941_FICA_SS'], (self.FICA_SS_MAX_WAGE - external_wages) * self.FICA_SS) + self.assertPayrollEqual(rules['EE_US_941_FICA_M'], salary * self.FICA_M) + self.assertPayrollEqual(rules['EE_US_941_FICA_M_ADD'], external_wages * self.FICA_M_ADD) + self.assertPayrollEqual(rules['ER_US_941_FICA_SS'], rules['EE_US_941_FICA_SS']) + self.assertPayrollEqual(rules['ER_US_941_FICA_M'], rules['EE_US_941_FICA_M']) + self.assertPayrollEqual(cats['ER_US_940_FUTA'], (self.FUTA_MAX_WAGE - external_wages) * self.FUTA) + + def test_2020_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, wage=salary, fed_940_type=USHRContract.FUTA_TYPE_BASIC) + + self._log('2020 tax first payslip:') + payslip = self._createPayslip(employee, '2020-01-01', '2020-01-31') + + payslip.compute_sheet() + + cats = self._getCategories(payslip) + rules = self._getRules(payslip) + self.assertPayrollEqual(rules['EE_US_941_FICA_SS'], self.FICA_SS_MAX_WAGE * self.FICA_SS) + self.assertPayrollEqual(rules['EE_US_941_FICA_M'], salary * self.FICA_M) + self.assertPayrollEqual(rules['EE_US_941_FICA_M_ADD'], 0.0 * self.FICA_M_ADD) + self.assertPayrollEqual(rules['ER_US_941_FICA_SS'], rules['EE_US_941_FICA_SS']) + self.assertPayrollEqual(rules['ER_US_941_FICA_M'], rules['EE_US_941_FICA_M']) + self.assertPayrollEqual(cats['ER_US_940_FUTA'], self.FUTA_MAX_WAGE * futa_rate) + + def test_2020_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, wage=salary, fed_940_type=USHRContract.FUTA_TYPE_EXEMPT) + self._log('2020 tax first payslip:') + payslip = self._createPayslip(employee, '2020-01-01', '2020-01-31') + payslip.compute_sheet() + cats = self._getCategories(payslip) + self.assertPayrollEqual(cats['ER_US_940_FUTA'], 0.0) + + def test_2020_taxes_with_fica_exempt(self): + salary = 6000.0 + schedule_pay = 'bi-weekly' + employee = self._createEmployee() + contract = self._createContract(employee, wage=salary, schedule_pay=schedule_pay) + contract.us_payroll_config_id.fed_941_fica_exempt = True + + self._log('2020 tax w4 exempt payslip:') + payslip = self._createPayslip(employee, '2020-01-01', '2020-01-31') + + payslip.compute_sheet() + + cats = self._getCategories(payslip) + self.assertPayrollEqual(cats['EE_US_941_FICA'], 0.0) + self.assertPayrollEqual(cats['ER_US_941_FICA'], 0.0) + + """ + For Federal Income Tax Withholding, we are utilizing the calculations from the new IRS Excel calculator. + Given that you CAN round, we will round to compare even though we will calculate as close to the penny as possible + with the wage * computed_percent method. + """ + + def _run_test_fit(self, + wage=0.0, + schedule_pay='monthly', + filing_status='single', + dependent_credit=0.0, + other_income=0.0, + deductions=0.0, + additional_withholding=0.0, + is_nonresident_alien=False, + expected_standard=0.0, + expected_higher=0.0, + ): + employee = self._createEmployee() + contract = self._createContract(employee, + wage=wage, + schedule_pay=schedule_pay, + fed_941_fit_w4_is_nonresident_alien=is_nonresident_alien, + fed_941_fit_w4_filing_status=filing_status, + fed_941_fit_w4_multiple_jobs_higher=False, + fed_941_fit_w4_dependent_credit=dependent_credit, + fed_941_fit_w4_other_income=other_income, + fed_941_fit_w4_deductions=deductions, + fed_941_fit_w4_additional_withholding=additional_withholding, + ) + payslip = self._createPayslip(employee, '2020-01-01', '2020-01-31') + payslip.compute_sheet() + cats = self._getCategories(payslip) + self.assertPayrollEqual(round(cats.get('EE_US_941_FIT', 0.0)), -expected_standard) + + contract.us_payroll_config_id.fed_941_fit_w4_multiple_jobs_higher = True + payslip.compute_sheet() + cats = self._getCategories(payslip) + self.assertPayrollEqual(round(cats.get('EE_US_941_FIT', 0.0)), -expected_higher) + return payslip + + def test_2020_fed_income_withholding_single(self): + _ = self._run_test_fit( + wage=6000.0, + schedule_pay='monthly', + filing_status='single', + dependent_credit=100.0, + other_income=200.0, + deductions=300.0, + additional_withholding=400.0, + is_nonresident_alien=False, + expected_standard=1132.0, + expected_higher=1459.0, + ) + + def test_2020_fed_income_withholding_married_as_single(self): + # This is "Head of Household" though the field name is the same for historical reasons. + _ = self._run_test_fit( + wage=500.0, + schedule_pay='weekly', + filing_status='married_as_single', + dependent_credit=20.0, + other_income=30.0, + deductions=40.0, + additional_withholding=10.0, + is_nonresident_alien=False, + expected_standard=24.0, + expected_higher=45.0, + ) + + def test_2020_fed_income_withholding_married(self): + _ = self._run_test_fit( + wage=14000.00, + schedule_pay='bi-weekly', + filing_status='married', + dependent_credit=2500.0, + other_income=1200.0, + deductions=1000.0, + additional_withholding=0.0, + is_nonresident_alien=False, + expected_standard=2621.0, + expected_higher=3702.0, + ) + + def test_2020_fed_income_withholding_nonresident_alien(self): + # Monthly NRA additional wage is 1033.30 + # Wage input on IRS Form entered as (3500+1033.30)=4533.30, not 3500.0 + _ = self._run_test_fit( + wage=3500.00, + schedule_pay='monthly', + filing_status='married', + dependent_credit=340.0, + other_income=0.0, + deductions=0.0, + additional_withholding=0.0, + is_nonresident_alien=True, + expected_standard=235.0, + expected_higher=391.0, + ) + + def test_2020_taxes_with_w4_exempt(self): + _ = self._run_test_fit( + wage=3500.00, + schedule_pay='monthly', + filing_status='', # Exempt + dependent_credit=340.0, + other_income=0.0, + deductions=0.0, + additional_withholding=0.0, + is_nonresident_alien=True, + expected_standard=0.0, + expected_higher=0.0, + ) diff --git a/l10n_us_hr_payroll/views/hr_contract_views.xml b/l10n_us_hr_payroll/views/hr_contract_views.xml new file mode 100644 index 00000000..6abe6e14 --- /dev/null +++ b/l10n_us_hr_payroll/views/hr_contract_views.xml @@ -0,0 +1,19 @@ + + + + + hr.contract.form.inherit + hr.contract + + + + + + + + + \ No newline at end of file diff --git a/l10n_us_hr_payroll/views/l10n_us_hr_payroll_view.xml b/l10n_us_hr_payroll/views/l10n_us_hr_payroll_view.xml deleted file mode 100755 index 257508bc..00000000 --- a/l10n_us_hr_payroll/views/l10n_us_hr_payroll_view.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - - hr.contract.form.inherit - hr.contract - 20 - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/l10n_us_hr_payroll/views/us_payroll_config_views.xml b/l10n_us_hr_payroll/views/us_payroll_config_views.xml new file mode 100644 index 00000000..5b6d0dea --- /dev/null +++ b/l10n_us_hr_payroll/views/us_payroll_config_views.xml @@ -0,0 +1,75 @@ + + + + hr.contract.us_payroll_config.tree + hr.contract.us_payroll_config + + + + + + + + + + + + + hr.contract.us_payroll_config.form + hr.contract.us_payroll_config + +
+ + + + + + + + +

Form 940 - Federal Unemployment

+ +

Form 941 / W4 - Federal Income Tax

+ + + + + + + + + +
+
+
+
+
+
+ + + hr.contract.us_payroll_config.search + hr.contract.us_payroll_config + + + + + + + + + + + Employee Payroll Forms + hr.contract.us_payroll_config + tree,form + +

+ No Forms +

+
+
+ + +