diff --git a/l10n_us_ca_hr_payroll/tests/test_us_ca_payslip_2018.py b/l10n_us_ca_hr_payroll/tests/test_us_ca_payslip_2018.py
new file mode 100755
index 00000000..d0835917
--- /dev/null
+++ b/l10n_us_ca_hr_payroll/tests/test_us_ca_payslip_2018.py
@@ -0,0 +1,461 @@
+from odoo.addons.l10n_us_hr_payroll.tests.test_us_payslip import TestUsPayslip, process_payslip
+from odoo.addons.l10n_us_hr_payroll.models.l10n_us_hr_payroll import USHrContract
+
+
+class TestUsCAPayslip(TestUsPayslip):
+ ###
+ # Taxes and Rates
+ ###
+ CA_UIT_MAX_WAGE = 7000
+ CA_UIT_MAX_WAGE = 7000
+ CA_SDI_MAX_WAGE = 114967
+
+ # Examples from http://www.edd.ca.gov/pdf_pub_ctr/18methb.pdf
+ def test_example_a(self):
+ salary = 210
+ schedule_pay = 'weekly'
+ allowances = 1
+ additional_allowances = 0
+
+ wh = 0.00
+
+ employee = self._createEmployee()
+
+ contract = self._createContract(employee,
+ salary,
+ struct_id=self.ref(
+ 'l10n_us_ca_hr_payroll.hr_payroll_salary_structure_us_ca_employee'),
+ schedule_pay=schedule_pay)
+ contract.ca_c4_exemptions = allowances
+ contract.ca_additional_allowances = additional_allowances
+ contract.ca_de4_filing_status = 'single'
+
+ self.assertEqual(contract.schedule_pay, 'weekly')
+
+ # tax rates
+ ca_uit = contract.ca_uit_rate(2018) / -100.0
+ ca_ett = contract.ca_ett_rate(2018) / -100.0
+ ca_sdi = contract.ca_sdi_rate(2018) / -100.0
+
+ self._log('2017 California tax last payslip:')
+ payslip = self._createPayslip(employee, '2017-12-01', '2017-12-31')
+ payslip.compute_sheet()
+ process_payslip(payslip)
+
+ self._log('2018 California tax first payslip:')
+ payslip = self._createPayslip(employee, '2018-01-01', '2018-01-31')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['CA_UIT_WAGES'], salary)
+ self.assertPayrollEqual(cats['CA_UIT'], cats['CA_UIT_WAGES'] * ca_uit)
+ self.assertPayrollEqual(cats['CA_ETT_WAGES'], salary)
+ self.assertPayrollEqual(cats['CA_ETT'], cats['CA_ETT_WAGES'] * ca_ett)
+ self.assertPayrollEqual(cats['CA_SDI_WAGES'], salary)
+ self.assertPayrollEqual(cats['CA_SDI'], cats['CA_SDI_WAGES'] * ca_sdi)
+ self.assertPayrollEqual(cats['CA_WITHHOLD'], wh)
+
+ process_payslip(payslip)
+
+ # Make a new payslip, this one will have maximums
+
+ remaining_ca_uit_wages = self.CA_UIT_MAX_WAGE - salary if (self.CA_UIT_MAX_WAGE - 2 * salary < salary) \
+ else salary
+
+ self._log('2018 California tax second payslip:')
+ payslip = self._createPayslip(employee, '2018-02-01', '2018-02-28')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['CA_UIT_WAGES'], remaining_ca_uit_wages)
+ self.assertPayrollEqual(cats['CA_UIT'], remaining_ca_uit_wages * ca_uit)
+
+ def test_example_b(self):
+ salary = 1250
+ schedule_pay = 'bi-weekly'
+ allowances = 2
+ additional_allowances = 1
+
+ wh = -2.89
+ # tax rates
+ ca_uit = 2.6
+ ca_ett = 0.1
+ ca_sdi = 1.0
+
+ employee = self._createEmployee()
+ employee.company_id.ca_uit_rate_2018 = ca_uit
+ employee.company_id.ca_ett_rate_2018 = ca_ett
+ employee.company_id.ca_sdi_rate_2018 = ca_sdi
+
+ contract = self._createContract(employee,
+ salary,
+ struct_id=self.ref(
+ 'l10n_us_ca_hr_payroll.hr_payroll_salary_structure_us_ca_employee'),
+ schedule_pay=schedule_pay)
+ contract.ca_de4_allowances = allowances
+ contract.ca_additional_allowances = additional_allowances
+ contract.ca_de4_filing_status = 'married'
+
+ self.assertEqual(contract.schedule_pay, 'bi-weekly')
+ self.assertEqual(contract.ca_de4_filing_status, 'married')
+ self.assertEqual(contract.ca_de4_allowances, 2)
+ self.assertEqual(contract.ca_additional_allowances, 1)
+
+ self._log('2017 California tax last payslip:')
+ payslip = self._createPayslip(employee, '2017-12-01', '2017-12-31')
+ payslip.compute_sheet()
+ process_payslip(payslip)
+
+ self._log('2018 California tax first payslip:')
+ payslip = self._createPayslip(employee, '2018-01-01', '2018-01-31')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['CA_UIT_WAGES'], salary)
+ self.assertPayrollEqual(cats['CA_UIT'], round((cats['CA_UIT_WAGES'] * ca_uit)/-100, 2))
+ self.assertPayrollEqual(cats['CA_ETT_WAGES'], salary)
+ self.assertPayrollEqual(cats['CA_ETT'], round((cats['CA_ETT_WAGES'] * ca_ett)/-100, 2))
+ self.assertPayrollEqual(cats['CA_SDI_WAGES'], salary)
+ self.assertPayrollEqual(cats['CA_SDI'], round((cats['CA_SDI_WAGES'] * ca_sdi)/-100, 2))
+ self.assertPayrollEqual(cats['CA_WITHHOLD'], wh)
+
+ process_payslip(payslip)
+
+ # Make a new payslip, this one will have maximums
+
+ remaining_ca_uit_wages = self.CA_UIT_MAX_WAGE - salary if (self.CA_UIT_MAX_WAGE - 2 * salary < salary) \
+ else salary
+
+ self._log('2018 California tax second payslip:')
+ payslip = self._createPayslip(employee, '2018-02-01', '2018-02-28')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['CA_UIT_WAGES'], remaining_ca_uit_wages)
+ self.assertPayrollEqual(cats['CA_UIT'], round((remaining_ca_uit_wages * ca_uit)/-100, 2))
+
+ def test_example_c(self):
+ salary = 3800
+ schedule_pay = 'monthly'
+ allowances = 5
+ additional_allowances = 0.72
+
+ wh = -0.72
+ # tax rates
+ ca_uit = 2.6
+ ca_ett = 0.1
+ ca_sdi = 1.0
+
+ employee = self._createEmployee()
+ employee.company_id.ca_uit_rate_2018 = ca_uit
+ employee.company_id.ca_ett_rate_2018 = ca_ett
+ employee.company_id.ca_sdi_rate_2018 = ca_sdi
+
+ contract = self._createContract(employee,
+ salary,
+ struct_id=self.ref(
+ 'l10n_us_ca_hr_payroll.hr_payroll_salary_structure_us_ca_employee'),
+ schedule_pay=schedule_pay)
+ contract.ca_de4_allowances = allowances
+ contract.ca_additional_allowances = additional_allowances
+ contract.ca_de4_filing_status = 'married'
+
+ self.assertEqual(contract.schedule_pay, 'monthly')
+ self.assertEqual(contract.ca_de4_filing_status, 'married')
+ self.assertEqual(contract.ca_de4_allowances, 5)
+ self.assertEqual(contract.ca_additional_allowances, 0)
+
+ self._log('2017 California tax last payslip:')
+ payslip = self._createPayslip(employee, '2017-12-01', '2017-12-31')
+ payslip.compute_sheet()
+ process_payslip(payslip)
+
+ self._log('2018 California tax first payslip:')
+ payslip = self._createPayslip(employee, '2018-01-01', '2018-01-31')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['CA_UIT_WAGES'], salary)
+ self.assertPayrollEqual(cats['CA_UIT'], round((cats['CA_UIT_WAGES'] * ca_uit)/-100, 2))
+ self.assertPayrollEqual(cats['CA_ETT_WAGES'], salary)
+ self.assertPayrollEqual(cats['CA_ETT'], round((cats['CA_ETT_WAGES'] * ca_ett)/-100, 2))
+ self.assertPayrollEqual(cats['CA_SDI_WAGES'], salary)
+ self.assertPayrollEqual(cats['CA_SDI'], round((cats['CA_SDI_WAGES'] * ca_sdi)/-100, 2))
+ self.assertPayrollEqual(cats['CA_WITHHOLD'], wh)
+
+ process_payslip(payslip)
+
+ # Make a new payslip, this one will have maximums
+
+ remaining_ca_uit_wages = self.CA_UIT_MAX_WAGE - salary if (self.CA_UIT_MAX_WAGE - 2 * salary < salary) \
+ else salary
+
+ self._log('2018 California tax second payslip:')
+ payslip = self._createPayslip(employee, '2018-02-01', '2018-02-28')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['CA_UIT_WAGES'], remaining_ca_uit_wages)
+ self.assertPayrollEqual(cats['CA_UIT'], round((remaining_ca_uit_wages * ca_uit)/-100, 2))
+
+ def test_example_d(self):
+ salary = 800
+ schedule_pay = 'weekly'
+ allowances = 3
+ additional_allowances = 0
+
+ wh = -3.31
+ # tax rates
+ ca_uit = 2.6
+ ca_ett = 0.1
+ ca_sdi = 1.0
+
+ employee = self._createEmployee()
+ employee.company_id.ca_uit_rate_2018 = ca_uit
+ employee.company_id.ca_ett_rate_2018 = ca_ett
+ employee.company_id.ca_sdi_rate_2018 = ca_sdi
+
+ contract = self._createContract(employee,
+ salary,
+ struct_id=self.ref(
+ 'l10n_us_ca_hr_payroll.hr_payroll_salary_structure_us_ca_employee'),
+ schedule_pay=schedule_pay)
+ contract.ca_de4_allowances = allowances
+ contract.ca_additional_allowances = additional_allowances
+ contract.ca_de4_filing_status = 'head_household'
+
+ self.assertEqual(contract.schedule_pay, 'weekly')
+ self.assertEqual(contract.ca_de4_filing_status, 'head_household')
+ self.assertEqual(contract.ca_de4_allowances, 3)
+ self.assertEqual(contract.ca_additional_allowances, 0)
+
+ self._log('2017 California tax last payslip:')
+ payslip = self._createPayslip(employee, '2017-12-01', '2017-12-31')
+ payslip.compute_sheet()
+ process_payslip(payslip)
+
+ self._log('2018 California tax first payslip:')
+ payslip = self._createPayslip(employee, '2018-01-01', '2018-01-31')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['CA_UIT_WAGES'], salary)
+ self.assertPayrollEqual(cats['CA_UIT'], round((cats['CA_UIT_WAGES'] * ca_uit)/-100, 2))
+ self.assertPayrollEqual(cats['CA_ETT_WAGES'], salary)
+ self.assertPayrollEqual(cats['CA_ETT'], round((cats['CA_ETT_WAGES'] * ca_ett)/-100, 2))
+ self.assertPayrollEqual(cats['CA_SDI_WAGES'], salary)
+ self.assertPayrollEqual(cats['CA_SDI'], round((cats['CA_SDI_WAGES'] * ca_sdi)/-100, 2))
+ self.assertPayrollEqual(cats['CA_WITHHOLD'], wh)
+
+ process_payslip(payslip)
+
+ # Make a new payslip, this one will have maximums
+
+ remaining_ca_uit_wages = self.CA_UIT_MAX_WAGE - salary if (self.CA_UIT_MAX_WAGE - 2 * salary < salary) \
+ else salary
+
+ self._log('2018 California tax second payslip:')
+ payslip = self._createPayslip(employee, '2018-02-01', '2018-02-28')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['CA_UIT_WAGES'], remaining_ca_uit_wages)
+ self.assertPayrollEqual(cats['CA_UIT'], round((remaining_ca_uit_wages * ca_uit)/-100, 2))
+
+ def test_example_e(self):
+ salary = 1800
+ schedule_pay = 'semi-monthly'
+ allowances = 4
+ additional_allowances = 0
+
+ wh = -3.39
+ # tax rates
+ ca_uit = 2.6
+ ca_ett = 0.1
+ ca_sdi = 1.0
+
+ employee = self._createEmployee()
+ employee.company_id.ca_uit_rate_2018 = ca_uit
+ employee.company_id.ca_ett_rate_2018 = ca_ett
+ employee.company_id.ca_sdi_rate_2018 = ca_sdi
+
+ contract = self._createContract(employee,
+ salary,
+ struct_id=self.ref(
+ 'l10n_us_ca_hr_payroll.hr_payroll_salary_structure_us_ca_employee'),
+ schedule_pay=schedule_pay)
+ contract.ca_de4_allowances = allowances
+ contract.ca_additional_allowances = additional_allowances
+ contract.ca_de4_filing_status = 'married'
+
+ self.assertEqual(contract.schedule_pay, 'semi-monthly')
+ self.assertEqual(contract.ca_de4_filing_status, 'married')
+ self.assertEqual(contract.ca_de4_allowances, 4)
+ self.assertEqual(contract.ca_additional_allowances, 0)
+
+ self._log('2017 California tax last payslip:')
+ payslip = self._createPayslip(employee, '2017-12-01', '2017-12-31')
+ payslip.compute_sheet()
+ process_payslip(payslip)
+
+ self._log('2018 California tax first payslip:')
+ payslip = self._createPayslip(employee, '2018-01-01', '2018-01-31')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['CA_UIT_WAGES'], salary)
+ self.assertPayrollEqual(cats['CA_UIT'], round((cats['CA_UIT_WAGES'] * ca_uit)/-100, 2))
+ self.assertPayrollEqual(cats['CA_ETT_WAGES'], salary)
+ self.assertPayrollEqual(cats['CA_ETT'], round((cats['CA_ETT_WAGES'] * ca_ett)/-100, 2))
+ self.assertPayrollEqual(cats['CA_SDI_WAGES'], salary)
+ self.assertPayrollEqual(cats['CA_SDI'], round((cats['CA_SDI_WAGES'] * ca_sdi)/-100, 2))
+ self.assertPayrollEqual(cats['CA_WITHHOLD'], wh)
+
+ process_payslip(payslip)
+
+ # Make a new payslip, this one will have maximums
+
+ remaining_ca_uit_wages = self.CA_UIT_MAX_WAGE - salary if (self.CA_UIT_MAX_WAGE - 2 * salary < salary) \
+ else salary
+
+ self._log('2018 California tax second payslip:')
+ payslip = self._createPayslip(employee, '2018-02-01', '2018-02-28')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['CA_UIT_WAGES'], remaining_ca_uit_wages)
+ self.assertPayrollEqual(cats['CA_UIT'], round((remaining_ca_uit_wages * ca_uit)/-100, 2))
+
+ def test_example_f(self):
+ salary = 45000
+ schedule_pay = 'annually'
+ allowances = 4
+ additional_allowances = 0
+
+ wh = -121.11
+ # tax rates
+ ca_uit = 2.6
+ ca_ett = 0.1
+ ca_sdi = 1.0
+
+ employee = self._createEmployee()
+ employee.company_id.ca_uit_rate_2018 = ca_uit
+ employee.company_id.ca_ett_rate_2018 = ca_ett
+ employee.company_id.ca_sdi_rate_2018 = ca_sdi
+
+ contract = self._createContract(employee,
+ salary,
+ struct_id=self.ref(
+ 'l10n_us_ca_hr_payroll.hr_payroll_salary_structure_us_ca_employee'),
+ schedule_pay=schedule_pay)
+ contract.ca_de4_allowances = allowances
+ contract.ca_additional_allowances = additional_allowances
+ contract.ca_de4_filing_status = 'married'
+
+ self.assertEqual(contract.schedule_pay, 'annually')
+ self.assertEqual(contract.ca_de4_filing_status, 'married')
+ self.assertEqual(contract.ca_de4_allowances, 4)
+ self.assertEqual(contract.ca_additional_allowances, 0)
+
+ self._log('2017 California tax last payslip:')
+ payslip = self._createPayslip(employee, '2017-12-01', '2017-12-31')
+ payslip.compute_sheet()
+ process_payslip(payslip)
+
+ self._log('2018 California tax first payslip:')
+ payslip = self._createPayslip(employee, '2018-01-01', '2018-01-31')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['CA_UIT'], round((cats['CA_UIT_WAGES'] * ca_uit)/-100, 2))
+ self.assertPayrollEqual(cats['CA_ETT'], round((cats['CA_ETT_WAGES'] * ca_ett)/-100, 2))
+ self.assertPayrollEqual(cats['CA_SDI'], round((cats['CA_SDI_WAGES'] * ca_sdi)/-100, 2))
+ self.assertPayrollEqual(cats['CA_WITHHOLD'], wh)
+
+ process_payslip(payslip)
+
+ def test_estimated_deduction_table(self):
+ salary = 600
+ allowances = 5
+ schedule_pay = 'bi-weekly'
+ expected_deduction = 192
+ deduction = 0
+ taxable_pay = 0
+ estimated_deduction_table = {
+ 'weekly': (19, 38, 58, 77, 96, 115, 135, 154, 173, 192),
+ 'bi-weekly': (38, 77, 115, 154, 192, 231, 269, 308, 346, 385),
+ 'semi-monthly': (42, 83, 125, 167, 208, 250, 292, 333, 375, 417),
+ 'monthly': (83, 167, 250, 333, 417, 500, 583, 667, 750, 833),
+ 'quarterly': (250, 500, 750, 1000, 1250, 1500, 1750, 2000, 2250, 2500),
+ 'semi-annual': (500, 1000, 1500, 2000, 2500, 3000, 3500, 4000, 4500, 5000),
+ 'annual': (1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000),
+ }
+
+ allowance_index = allowances - 1
+ if allowances > 10:
+ deduction = (estimated_deduction_table[schedule_pay][0]) * allowances
+ taxable_pay = salary - deduction
+ elif allowances > 0:
+ deduction = estimated_deduction_table[schedule_pay][allowance_index]
+ taxable_pay = salary - deduction
+
+ self.assertEqual(expected_deduction, deduction)
+ self.assertTrue(taxable_pay < salary)
+ self.assertEqual(taxable_pay, salary - deduction)
+
+ def test_standard_deduction_table(self):
+ salary = 3000
+ schedule_pay = 'monthly'
+ filing_status = 'head_household'
+ expected_deduction = 706
+ deduction = 0
+ taxable_pay = 0
+ standard_deduction_table = {
+ 'weekly': (81, 81, 163, 163),
+ 'bi-weekly': (163, 163, 326, 326),
+ 'semi-monthly': (177, 177, 353, 353),
+ 'monthly': (353, 352, 706, 706),
+ 'quarterly': (1059, 1059, 2188, 2188),
+ 'semi-annual': (2118, 2118, 4236, 4236),
+ 'annual': (4236, 4236, 8471, 8472),
+ }
+
+ if filing_status == 'head_household':
+ _, _, _, deduction = standard_deduction_table[schedule_pay]
+ taxable_pay = salary - deduction
+ elif filing_status == 'married':
+ if allowances >= 2:
+ _, _, deduction, _ = standard_deduction_table[schedule_pay]
+ taxable_pay = salary - deduction
+ else:
+ _, deduction, _, _ = standard_deduction_table[schedule_pay]
+ taxable_pay = salary - deduction
+ else:
+ deduction, _, _, _ = standard_deduction_table[schedule_pay]
+ taxable_pay = salary - deduction
+
+ self.assertEqual(expected_deduction, deduction)
+ self.assertTrue(taxable_pay < salary)
+ self.assertEqual(taxable_pay, salary - deduction)
diff --git a/l10n_us_fl_hr_payroll/tests/test_us_fl_payslip_2016.py b/l10n_us_fl_hr_payroll/tests/test_us_fl_payslip_2016.py
index 841d2448..5b58a04f 100755
--- a/l10n_us_fl_hr_payroll/tests/test_us_fl_payslip_2016.py
+++ b/l10n_us_fl_hr_payroll/tests/test_us_fl_payslip_2016.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
from openerp.addons.l10n_us_hr_payroll.tests.test_us_payslip import TestUsPayslip, process_payslip
-from openerp.addons.l10n_us_hr_payroll.l10n_us_hr_payroll import USHrContract
+from openerp.addons.l10n_us_hr_payroll.models.l10n_us_hr_payroll import USHrContract
class TestUsFlPayslip(TestUsPayslip):
diff --git a/l10n_us_fl_hr_payroll/tests/test_us_fl_payslip_2017.py b/l10n_us_fl_hr_payroll/tests/test_us_fl_payslip_2017.py
index 8c0e841c..0085b5f3 100755
--- a/l10n_us_fl_hr_payroll/tests/test_us_fl_payslip_2017.py
+++ b/l10n_us_fl_hr_payroll/tests/test_us_fl_payslip_2017.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
from openerp.addons.l10n_us_hr_payroll.tests.test_us_payslip import TestUsPayslip, process_payslip
-from openerp.addons.l10n_us_hr_payroll.l10n_us_hr_payroll import USHrContract
+from openerp.addons.l10n_us_hr_payroll.models.l10n_us_hr_payroll import USHrContract
class TestUsFlPayslip(TestUsPayslip):
diff --git a/l10n_us_fl_hr_payroll/tests/test_us_fl_payslip_2018.py b/l10n_us_fl_hr_payroll/tests/test_us_fl_payslip_2018.py
index 5c4a25ab..070581ec 100755
--- a/l10n_us_fl_hr_payroll/tests/test_us_fl_payslip_2018.py
+++ b/l10n_us_fl_hr_payroll/tests/test_us_fl_payslip_2018.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
from odoo.addons.l10n_us_hr_payroll.tests.test_us_payslip import TestUsPayslip, process_payslip
-from odoo.addons.l10n_us_hr_payroll.l10n_us_hr_payroll import USHrContract
+from odoo.addons.l10n_us_hr_payroll.models.l10n_us_hr_payroll import USHrContract
class TestUsFlPayslip(TestUsPayslip):
diff --git a/l10n_us_hr_payroll/README.rst b/l10n_us_hr_payroll/README.rst
new file mode 100644
index 00000000..8f3669b3
--- /dev/null
+++ b/l10n_us_hr_payroll/README.rst
@@ -0,0 +1,77 @@
+******************
+Hibou - US Payroll
+******************
+
+Calculations and contribution registers for United States Payroll.
+
+For more information and add-ons, visit `Hibou.io `_.
+
+=============
+Main Features
+=============
+
+* Contribution registers and partners for:
+ * The Electronic Federal Tax Payment System (EFTPS) - Form 941
+ * The Electronic Federal Tax Payment System (EFTPS) - Form 940
+ * The Electronic Federal Tax Payment System (EFTPS) - Form 941 (FICA + Federal Withholding)
+ * The Electronic Federal Tax Payment System (EFTPS) - Form 940 (FUTA)
+
+* Contract level FICA Social Security
+* Contract level FICA Employee Medicare
+* Contract level FICA Employee Medicare Additional
+* Contract level Federal Income Withholding
+* Company level FICA Social Security
+* Company level FICA Medicare
+* Company level FUTA Federal Unemployment
+
+
+.. image:: https://user-images.githubusercontent.com/15882954/41485460-76a0060c-7095-11e8-851a-fec562013ce4.png
+ :alt: 'Employee Contract Detail'
+ :width: 988
+ :align: left
+
+USA Employee added to Contract Salary Structure Menu
+
+.. image:: https://user-images.githubusercontent.com/15882954/41485484-880f0816-7095-11e8-9ad0-874b3270c308.png
+ :alt: 'Computed Pay Slip Detail'
+ :width: 988
+ :align: left
+
+Upgrading to 2019
+==========================
+
+If you were using this prior to January 2019, then you will need to run the following
+migration script.
+
+Odoo Shell code::
+
+ def migrate_rule_name(rule_id):
+ main = env.ref(rule_id)
+ old_2017 = env.ref(rule_id.replace('2018', '2017'))
+ old_2016 = env.ref(rule_id.replace('2018', '2016'))
+ lines = env['hr.payslip.line'].search([('salary_rule_id', 'in', [old_2017.id, old_2016.id,])])
+ lines.write({'salary_rule_id': main.id})
+
+ rules = [
+ 'l10n_us_hr_payroll.hr_payroll_rules_fica_emp_ss_wages_2018',
+ 'l10n_us_hr_payroll.hr_payroll_rules_fica_emp_m_wages_2018',
+ 'l10n_us_hr_payroll.hr_payroll_rules_fica_emp_m_add_wages_2018',
+ 'l10n_us_hr_payroll.hr_payroll_rules_fica_emp_ss_2018',
+ 'l10n_us_hr_payroll.hr_payroll_rules_fica_emp_m_2018',
+ 'l10n_us_hr_payroll.hr_payroll_rules_fica_emp_m_add_2018',
+ 'l10n_us_hr_payroll.hr_payroll_rules_fed_inc_withhold_2018_single',
+ 'l10n_us_hr_payroll.hr_payroll_rules_fed_inc_withhold_2018_married',
+ 'l10n_us_hr_payroll.hr_payroll_rules_futa_wages_2018',
+ 'l10n_us_hr_payroll.hr_payroll_rules_futa_2018',
+ ]
+ for rule_id in rules:
+ migrate_rule_name(rule_id)
+
+ env.cr.commit()
+
+
+=======
+License
+=======
+Please see `LICENSE `_.
+Copyright Hibou Corp. 2018
diff --git a/l10n_us_hr_payroll/__init__.py b/l10n_us_hr_payroll/__init__.py
index e7a7e19b..c7b5ac7e 100755
--- a/l10n_us_hr_payroll/__init__.py
+++ b/l10n_us_hr_payroll/__init__.py
@@ -1,3 +1,3 @@
# -*- coding: utf-8 -*-
-import l10n_us_hr_payroll
+import models
diff --git a/l10n_us_hr_payroll/__openerp__.py b/l10n_us_hr_payroll/__openerp__.py
index f2bf0b7f..33b41b90 100755
--- a/l10n_us_hr_payroll/__openerp__.py
+++ b/l10n_us_hr_payroll/__openerp__.py
@@ -4,8 +4,8 @@
'author': 'Hibou Corp. ',
'license': 'AGPL-3',
'category': 'Localization',
- 'depends': ['hr_payroll'],
- 'version': '2017.0.0',
+ 'depends': ['hr_payroll', 'hr_payroll_rate'],
+ 'version': '10.0.2019.0.0',
'description': """
USA Payroll Rules.
==================
@@ -21,11 +21,10 @@ USA Payroll Rules.
'auto_install': False,
'website': 'https://hibou.io/',
'data': [
- 'l10n_us_hr_payroll_view.xml',
+ 'views/l10n_us_hr_payroll_view.xml',
'data/base.xml',
- 'data/rules_2016.xml',
- 'data/rules_2017.xml',
- 'data/rules_2018.xml',
+ 'data/rates.xml',
+ 'data/rules.xml',
'data/final.xml',
],
'installable': True
diff --git a/l10n_us_hr_payroll/data/base.xml b/l10n_us_hr_payroll/data/base.xml
index 9b1aebbf..18bdb6d4 100755
--- a/l10n_us_hr_payroll/data/base.xml
+++ b/l10n_us_hr_payroll/data/base.xml
@@ -26,111 +26,58 @@
- FICA Employee Social Security - Wages
- FICA_EMP_SS_WAGES
+ Wage: US FICA Social Security
+ WAGE_US_FICA_SS
- FICA Employee Medicare - Wages
- FICA_EMP_M_WAGES
+ Wage: US FICA Medicare
+ WAGE_US_FICA_M
- FICA Employee Medicare Additional - Wages
- FICA_EMP_M_ADD_WAGES
+ Wage: US FICA Medicare Additional
+ WAGE_US_FICA_M_ADD
- FUTA Federal Unemployment - Wages
- FUTA_WAGES
+ Wage: US FUTA Federal Unemployment
+ WAGE_US_FUTA
- FICA Employee Social Security
- FICA_EMP_SS
+ EE: US FICA Social Security
+ EE_US_FICA_SS
- FICA Employee Medicare
- FICA_EMP_M
+ EE: US FICA Medicare
+ EE_US_FICA_M
- FICA Employee Medicare Additional
- FICA_EMP_M_ADD
+ EE: US FICA Medicare Additional
+ EE_US_FICA_M_ADD
- Federal Income Withholding
- FED_INC_WITHHOLD
+ EE: US Federal Income Tax Withholding
+ EE_US_FED_INC_WITHHOLD
- FICA Company Social Security
- FICA_COMP_SS
+ ER: US FICA Social Security
+ ER_US_FICA_SS
- FICA Company Medicare
- FICA_COMP_M
+ ER: US FICA Medicare
+ ER_US_FICA_M
- FUTA Federal Unemployment
- FUTA
+ ER: US FUTA Federal Unemployment
+ ER_US_FUTA
-
-
-
-
-
-
-
-
- FICA Company Social Security
- FICA_COMP_SS
- none
- code
- result = categories.FICA_EMP_SS
-
-
-
-
-
-
- FICA Company Medicare
- FICA_COMP_M
- none
- code
- result = categories.FICA_EMP_M
-
-
-
-
-
-
-
-
-
-
- FICA Company Social Security
- FICA_COMP_SS
- none
- code
- result = categories.FICA_EMP_SS
-
-
-
-
-
-
- FICA Company Medicare
- FICA_COMP_M
- none
- code
- result = categories.FICA_EMP_M
-
-
-
-
+
diff --git a/l10n_us_hr_payroll/data/final.xml b/l10n_us_hr_payroll/data/final.xml
index e8b76757..dea1869b 100755
--- a/l10n_us_hr_payroll/data/final.xml
+++ b/l10n_us_hr_payroll/data/final.xml
@@ -10,28 +10,6 @@
ref('hr_payroll_rules_fica_comp_ss'),
ref('hr_payroll_rules_fica_comp_m'),
- ref('hr_payroll_rules_fica_emp_ss_wages_2016'),
- ref('hr_payroll_rules_fica_emp_m_wages_2016'),
- ref('hr_payroll_rules_fica_emp_m_add_wages_2016'),
- ref('hr_payroll_rules_fica_emp_ss_2016'),
- ref('hr_payroll_rules_fica_emp_m_2016'),
- ref('hr_payroll_rules_fica_emp_m_add_2016'),
- ref('hr_payroll_rules_futa_wages_2016'),
- ref('hr_payroll_rules_futa_2016'),
- ref('hr_payroll_rules_fed_inc_withhold_2016_single'),
- ref('hr_payroll_rules_fed_inc_withhold_2016_married'),
-
- ref('hr_payroll_rules_fica_emp_ss_wages_2017'),
- ref('hr_payroll_rules_fica_emp_m_wages_2017'),
- ref('hr_payroll_rules_fica_emp_m_add_wages_2017'),
- ref('hr_payroll_rules_fica_emp_ss_2017'),
- ref('hr_payroll_rules_fica_emp_m_2017'),
- ref('hr_payroll_rules_fica_emp_m_add_2017'),
- ref('hr_payroll_rules_futa_wages_2017'),
- ref('hr_payroll_rules_futa_2017'),
- ref('hr_payroll_rules_fed_inc_withhold_2017_single'),
- ref('hr_payroll_rules_fed_inc_withhold_2017_married'),
-
ref('hr_payroll_rules_fica_emp_ss_wages_2018'),
ref('hr_payroll_rules_fica_emp_m_wages_2018'),
ref('hr_payroll_rules_fica_emp_m_add_wages_2018'),
diff --git a/l10n_us_hr_payroll/data/rates.xml b/l10n_us_hr_payroll/data/rates.xml
new file mode 100644
index 00000000..6af3f48e
--- /dev/null
+++ b/l10n_us_hr_payroll/data/rates.xml
@@ -0,0 +1,68 @@
+
+
+
+
+ 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
new file mode 100755
index 00000000..b0abf7cf
--- /dev/null
+++ b/l10n_us_hr_payroll/data/rules.xml
@@ -0,0 +1,1058 @@
+
+
+
+
+
+
+
+
+
+ Wage: US FICA Social Security
+ WAGE_US_FICA_SS
+ python
+ result = not contract.fica_exempt
+ code
+
+###
+year = int(payslip.dict.date_to[:4])
+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 = int(payslip.dict.date_to[:4])
+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 = int(payslip.dict.date_to[:4])
+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 = int(payslip.dict.date_to[:4])
+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 = int(payslip.dict.date_to[:4])
+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 = int(payslip.dict.date_to[:4])
+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
new file mode 100644
index 00000000..ff687165
--- /dev/null
+++ b/l10n_us_hr_payroll/models/__init__.py
@@ -0,0 +1 @@
+from . import l10n_us_hr_payroll
diff --git a/l10n_us_hr_payroll/l10n_us_hr_payroll.py b/l10n_us_hr_payroll/models/l10n_us_hr_payroll.py
similarity index 73%
rename from l10n_us_hr_payroll/l10n_us_hr_payroll.py
rename to l10n_us_hr_payroll/models/l10n_us_hr_payroll.py
index 6d2c472c..5c1ac2d2 100755
--- a/l10n_us_hr_payroll/l10n_us_hr_payroll.py
+++ b/l10n_us_hr_payroll/models/l10n_us_hr_payroll.py
@@ -1,15 +1,24 @@
from openerp 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'
- FUTA_YEARS_VALID = (
- 2016,
- 2017,
- 2018,
- )
_inherit = 'hr.contract'
@@ -31,17 +40,3 @@ class USHrContract(models.Model):
(FUTA_TYPE_NORMAL, 'Normal Net Rate (0.6%)'),
(FUTA_TYPE_BASIC, 'Basic Rate (6%)'),
], string="Federal Unemployment Tax Type (FUTA)", default='normal')
-
- @api.multi
- def futa_rate(self, year):
- self.ensure_one()
-
- if year not in self.FUTA_YEARS_VALID:
- raise NotImplemented('FUTA rate for Year: ' + str(year) + ' not known.')
-
- if self.futa_type == self.FUTA_TYPE_EXEMPT:
- return 0.0
- elif self.futa_type == self.FUTA_TYPE_NORMAL:
- return 0.6
- else:
- return 6.0
diff --git a/l10n_us_hr_payroll/tests/__init__.py b/l10n_us_hr_payroll/tests/__init__.py
index 774c98d0..984a9040 100755
--- a/l10n_us_hr_payroll/tests/__init__.py
+++ b/l10n_us_hr_payroll/tests/__init__.py
@@ -1,6 +1,5 @@
# -*- coding: utf-8 -*-
from . import test_us_payslip
-from . import test_us_payslip_2016
-from . import test_us_payslip_2017
from . import test_us_payslip_2018
+from . import test_us_payslip_2019
diff --git a/l10n_us_hr_payroll/tests/test_us_payslip.py b/l10n_us_hr_payroll/tests/test_us_payslip.py
index e392c172..9a9e0232 100755
--- a/l10n_us_hr_payroll/tests/test_us_payslip.py
+++ b/l10n_us_hr_payroll/tests/test_us_payslip.py
@@ -5,7 +5,7 @@ from sys import float_info as sys_float_info
from openerp.tests import common
from openerp.tools.float_utils import float_round as odoo_float_round
-from openerp.addons.l10n_us_hr_payroll.l10n_us_hr_payroll import USHrContract
+from openerp.addons.l10n_us_hr_payroll.models.l10n_us_hr_payroll import USHrContract
def process_payslip(payslip):
diff --git a/l10n_us_hr_payroll/tests/test_us_payslip_2018.py b/l10n_us_hr_payroll/tests/test_us_payslip_2018.py
index 9e676218..3bfffda1 100755
--- a/l10n_us_hr_payroll/tests/test_us_payslip_2018.py
+++ b/l10n_us_hr_payroll/tests/test_us_payslip_2018.py
@@ -1,6 +1,6 @@
from .test_us_payslip import TestUsPayslip, process_payslip
-from odoo.addons.l10n_us_hr_payroll.l10n_us_hr_payroll import USHrContract
+from odoo.addons.l10n_us_hr_payroll.models.l10n_us_hr_payroll import USHrContract
from sys import float_info
@@ -48,16 +48,16 @@ class TestUsPayslip2018(TestUsPayslip):
cats = self._getCategories(payslip)
- self.assertPayrollEqual(cats['FICA_EMP_SS_WAGES'], salary)
- self.assertPayrollEqual(cats['FICA_EMP_SS'], cats['FICA_EMP_SS_WAGES'] * self.FICA_SS)
- self.assertPayrollEqual(cats['FICA_EMP_M_WAGES'], salary)
- self.assertPayrollEqual(cats['FICA_EMP_M'], cats['FICA_EMP_M_WAGES'] * self.FICA_M)
- self.assertPayrollEqual(cats['FICA_EMP_M_ADD_WAGES'], 0.0)
- self.assertPayrollEqual(cats['FICA_EMP_M_ADD'], 0.0)
- self.assertPayrollEqual(cats['FICA_COMP_SS'], cats['FICA_EMP_SS'])
- self.assertPayrollEqual(cats['FICA_COMP_M'], cats['FICA_EMP_M'])
- self.assertPayrollEqual(cats['FUTA_WAGES'], self.FUTA_MAX_WAGE)
- self.assertPayrollEqual(cats['FUTA'], cats['FUTA_WAGES'] * self.FUTA)
+ 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)
@@ -73,16 +73,16 @@ class TestUsPayslip2018(TestUsPayslip):
cats = self._getCategories(payslip)
- self.assertPayrollEqual(cats['FICA_EMP_SS_WAGES'], remaining_ss_wages)
- self.assertPayrollEqual(cats['FICA_EMP_SS'], cats['FICA_EMP_SS_WAGES'] * self.FICA_SS)
- self.assertPayrollEqual(cats['FICA_EMP_M_WAGES'], remaining_m_wages)
- self.assertPayrollEqual(cats['FICA_EMP_M'], cats['FICA_EMP_M_WAGES'] * self.FICA_M)
- self.assertPayrollEqual(cats['FICA_EMP_M_ADD_WAGES'], 0.0)
- self.assertPayrollEqual(cats['FICA_EMP_M_ADD'], 0.0)
- self.assertPayrollEqual(cats['FICA_COMP_SS'], cats['FICA_EMP_SS'])
- self.assertPayrollEqual(cats['FICA_COMP_M'], cats['FICA_EMP_M'])
- self.assertPayrollEqual(cats['FUTA_WAGES'], 0)
- self.assertPayrollEqual(cats['FUTA'], 0)
+ 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)
@@ -95,9 +95,9 @@ class TestUsPayslip2018(TestUsPayslip):
cats = self._getCategories(payslip)
- self.assertPayrollEqual(cats['FICA_EMP_M_WAGES'], salary)
- self.assertPayrollEqual(cats['FICA_EMP_M_ADD_WAGES'], self.FICA_M_ADD_START_WAGE - (salary * 2)) # aka 40k
- self.assertPayrollEqual(cats['FICA_EMP_M_ADD'], cats['FICA_EMP_M_ADD_WAGES'] * self.FICA_M_ADD)
+ 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)
@@ -110,9 +110,9 @@ class TestUsPayslip2018(TestUsPayslip):
cats = self._getCategories(payslip)
- self.assertPayrollEqual(cats['FICA_EMP_M_WAGES'], salary)
- self.assertPayrollEqual(cats['FICA_EMP_M_ADD_WAGES'], salary)
- self.assertPayrollEqual(cats['FICA_EMP_M_ADD'], cats['FICA_EMP_M_ADD_WAGES'] * self.FICA_M_ADD)
+ 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)
@@ -136,7 +136,7 @@ class TestUsPayslip2018(TestUsPayslip):
cats = self._getCategories(payslip)
- self.assertPayrollEqual(cats['FED_INC_WITHHOLD'], expected_withholding)
+ self.assertPayrollEqual(cats['EE_US_FED_INC_WITHHOLD'], expected_withholding)
def test_2018_fed_income_withholding_married_as_single(self):
salary = 500.00
@@ -158,7 +158,7 @@ class TestUsPayslip2018(TestUsPayslip):
cats = self._getCategories(payslip)
- self.assertPayrollEqual(cats['FED_INC_WITHHOLD'], expected_withholding)
+ self.assertPayrollEqual(cats['EE_US_FED_INC_WITHHOLD'], expected_withholding)
def test_2018_fed_income_withholding_married(self):
salary = 14000.00
@@ -180,7 +180,7 @@ class TestUsPayslip2018(TestUsPayslip):
cats = self._getCategories(payslip)
- self.assertPayrollEqual(cats['FED_INC_WITHHOLD'], expected_withholding)
+ self.assertPayrollEqual(cats['EE_US_FED_INC_WITHHOLD'], expected_withholding)
def test_2018_taxes_with_external(self):
@@ -199,16 +199,16 @@ class TestUsPayslip2018(TestUsPayslip):
cats = self._getCategories(payslip)
- self.assertPayrollEqual(cats['FICA_EMP_SS_WAGES'], self.FICA_SS_MAX_WAGE - external_wages)
- self.assertPayrollEqual(cats['FICA_EMP_SS'], cats['FICA_EMP_SS_WAGES'] * self.FICA_SS)
- self.assertPayrollEqual(cats['FICA_EMP_M_WAGES'], salary)
- self.assertPayrollEqual(cats['FICA_EMP_M'], cats['FICA_EMP_M_WAGES'] * self.FICA_M)
- self.assertPayrollEqual(cats['FICA_EMP_M_ADD_WAGES'], 0.0)
- self.assertPayrollEqual(cats['FICA_EMP_M_ADD'], cats['FICA_EMP_M_ADD_WAGES'] * self.FICA_M_ADD)
- self.assertPayrollEqual(cats['FICA_COMP_SS'], cats['FICA_EMP_SS'])
- self.assertPayrollEqual(cats['FICA_COMP_M'], cats['FICA_EMP_M'])
- self.assertPayrollEqual(cats['FUTA_WAGES'], self.FUTA_MAX_WAGE - external_wages)
- self.assertPayrollEqual(cats['FUTA'], cats['FUTA_WAGES'] * self.FUTA)
+ 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
@@ -226,16 +226,16 @@ class TestUsPayslip2018(TestUsPayslip):
cats = self._getCategories(payslip)
- self.assertPayrollEqual(cats['FICA_EMP_SS_WAGES'], self.FICA_SS_MAX_WAGE)
- self.assertPayrollEqual(cats['FICA_EMP_SS'], cats['FICA_EMP_SS_WAGES'] * self.FICA_SS)
- self.assertPayrollEqual(cats['FICA_EMP_M_WAGES'], salary)
- self.assertPayrollEqual(cats['FICA_EMP_M'], cats['FICA_EMP_M_WAGES'] * self.FICA_M)
- self.assertPayrollEqual(cats['FICA_EMP_M_ADD_WAGES'], 0.0)
- self.assertPayrollEqual(cats['FICA_EMP_M_ADD'], cats['FICA_EMP_M_ADD_WAGES'] * self.FICA_M_ADD)
- self.assertPayrollEqual(cats['FICA_COMP_SS'], cats['FICA_EMP_SS'])
- self.assertPayrollEqual(cats['FICA_COMP_M'], cats['FICA_EMP_M'])
- self.assertPayrollEqual(cats['FUTA_WAGES'], self.FUTA_MAX_WAGE)
- self.assertPayrollEqual(cats['FUTA'], cats['FUTA_WAGES'] * futa_rate)
+ 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
@@ -254,21 +254,21 @@ class TestUsPayslip2018(TestUsPayslip):
cats = self._getCategories(payslip)
- self.assertPayrollEqual(cats['FICA_EMP_SS_WAGES'], self.FICA_SS_MAX_WAGE)
- self.assertPayrollEqual(cats['FICA_EMP_SS'], cats['FICA_EMP_SS_WAGES'] * self.FICA_SS)
- self.assertPayrollEqual(cats['FICA_EMP_M_WAGES'], salary)
- self.assertPayrollEqual(cats['FICA_EMP_M'], cats['FICA_EMP_M_WAGES'] * self.FICA_M)
- self.assertPayrollEqual(cats['FICA_EMP_M_ADD_WAGES'], 0.0)
- self.assertPayrollEqual(cats['FICA_EMP_M_ADD'], cats['FICA_EMP_M_ADD_WAGES'] * self.FICA_M_ADD)
- self.assertPayrollEqual(cats['FICA_COMP_SS'], cats['FICA_EMP_SS'])
- self.assertPayrollEqual(cats['FICA_COMP_M'], cats['FICA_EMP_M'])
+ self.assertPayrollEqual(cats['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 'FUTA_WAGES' in cats:
- futa_wages = cats['FUTA_WAGES']
+ if 'WAGE_US_FUTA' in cats:
+ futa_wages = cats['WAGE_US_FUTA']
futa = 0.0
- if 'FUTA' in cats:
- futa = cats['FUTA']
+ if 'ER_US_FUTA' in cats:
+ futa = cats['ER_US_FUTA']
self.assertPayrollEqual(futa_wages, 0.0)
self.assertPayrollEqual(futa, futa_wages * futa_rate)
@@ -295,7 +295,7 @@ class TestUsPayslip2018(TestUsPayslip):
cats = self._getCategories(payslip)
- self.assertPayrollEqual(cats['FED_INC_WITHHOLD'], expected_withholding)
+ self.assertPayrollEqual(cats['EE_US_FED_INC_WITHHOLD'], expected_withholding)
def test_2018_fed_income_additional_withholding(self):
salary = 50000.00
@@ -322,7 +322,7 @@ class TestUsPayslip2018(TestUsPayslip):
cats = self._getCategories(payslip)
- self.assertPayrollEqual(cats['FED_INC_WITHHOLD'], expected_withholding)
+ self.assertPayrollEqual(cats['EE_US_FED_INC_WITHHOLD'], expected_withholding)
def test_2018_taxes_with_w4_exempt(self):
salary = 6000.0
@@ -339,6 +339,30 @@ class TestUsPayslip2018(TestUsPayslip):
cats = self._getCategories(payslip)
fed_inc_withhold = 0.0
- if 'FED_INC_WITHHOLD' in cats:
- fed_inc_withhold = cats['FED_INC_WITHHOLD']
+ 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
new file mode 100755
index 00000000..000c063b
--- /dev/null
+++ b/l10n_us_hr_payroll/tests/test_us_payslip_2019.py
@@ -0,0 +1,368 @@
+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 TestUsPayslip2019(TestUsPayslip):
+ # FUTA Constants
+ FUTA_RATE_NORMAL = 0.6
+ FUTA_RATE_BASIC = 6.0
+ FUTA_RATE_EXEMPT = 0.0
+
+ # Wage caps
+ FICA_SS_MAX_WAGE = 132900.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
+
+ ###
+ # 2019 Taxes and Rates
+ ###
+
+ def test_2019_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('2018 tax last slip')
+ payslip = self._createPayslip(employee, '2018-12-01', '2018-12-31')
+ payslip.compute_sheet()
+ process_payslip(payslip)
+
+ 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)
+
+ 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('2019 tax second payslip:')
+ payslip = self._createPayslip(employee, '2019-02-01', '2019-02-28')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['WAGE_US_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('2019 tax third payslip:')
+ payslip = self._createPayslip(employee, '2019-03-01', '2019-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('2019 tax fourth payslip:')
+ payslip = self._createPayslip(employee, '2019-04-01', '2019-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_2019_fed_income_withholding_single(self):
+ salary = 6000.00
+ schedule_pay = 'monthly'
+ w4_allowances = 3
+ w4_allowance_amt = 350.00 * 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(-(378.52 + ((adjusted_salary - 3606) * 0.22)), self.payroll_digits)
+
+ employee = self._createEmployee()
+ self._createContract(employee, salary, schedule_pay, w4_allowances, 'single')
+
+ 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)
+
+ def test_2019_fed_income_withholding_married_as_single(self):
+ salary = 500.00
+ schedule_pay = 'weekly'
+ w4_allowances = 1
+ 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')
+
+ 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)
+
+ def test_2019_fed_income_withholding_married(self):
+ salary = 14000.00
+ schedule_pay = 'bi-weekly'
+ w4_allowances = 2
+ w4_allowance_amt = 161.50 * 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(-(2519.06 + ((adjusted_salary - 12817) * 0.32)), self.payroll_digits)
+
+ employee = self._createEmployee()
+ self._createContract(employee, salary, schedule_pay, w4_allowances, 'married')
+
+ 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)
+
+ 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._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)
+
+ def test_2019_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('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'])
+ self.assertPayrollEqual(cats['WAGE_US_FUTA'], self.FUTA_MAX_WAGE)
+ self.assertPayrollEqual(cats['ER_US_FUTA'], cats['WAGE_US_FUTA'] * 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._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)
+
+ def test_2019_fed_income_withholding_nonresident_alien(self):
+ salary = 3500.00
+ schedule_pay = 'quarterly'
+ 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
+
+ ###
+ # 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)
+
+ 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)
+
+ def test_2019_fed_income_additional_withholding(self):
+ salary = 50000.00
+ schedule_pay = 'annually'
+ w4_additional_withholding = 5000.0
+ w4_allowances = 2
+ w4_allowance_amt = 4200.0 * w4_allowances
+ adjusted_salary = salary - w4_allowance_amt # 41700
+
+ ###
+ # Single ANNUAL form Publication 15
+ expected_withholding = \
+ self.float_round(-((1940.00 + ((adjusted_salary - 31200) * 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('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)
+
+ 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, '')
+
+ 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)
+
+ 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
+
+ self._log('2019 tax w4 exempt payslip:')
+ payslip = self._createPayslip(employee, '2019-01-01', '2019-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/l10n_us_hr_payroll_view.xml b/l10n_us_hr_payroll/views/l10n_us_hr_payroll_view.xml
similarity index 100%
rename from l10n_us_hr_payroll/l10n_us_hr_payroll_view.xml
rename to l10n_us_hr_payroll/views/l10n_us_hr_payroll_view.xml
diff --git a/l10n_us_ks_hr_payroll/tests/test_us_ks_payslip_2018.py b/l10n_us_ks_hr_payroll/tests/test_us_ks_payslip_2018.py
new file mode 100755
index 00000000..cf1c1437
--- /dev/null
+++ b/l10n_us_ks_hr_payroll/tests/test_us_ks_payslip_2018.py
@@ -0,0 +1,92 @@
+from odoo.addons.l10n_us_hr_payroll.tests.test_us_payslip import TestUsPayslip, process_payslip
+from odoo.addons.l10n_us_hr_payroll.models.l10n_us_hr_payroll import USHrContract
+
+
+class TestUsKSPayslip(TestUsPayslip):
+ ###
+ # 2018 Taxes and Rates
+ ###
+ KS_UNEMP_MAX_WAGE = 14000.0
+
+ def test_2018_taxes(self):
+ self.debug = True
+ salary = 210
+ schedule_pay = 'weekly'
+ allowances = 2
+ additional_withholding = 0
+
+ # Amount of each withholding allowance for weekly from Withholding Allowance Amounts Table
+ # https://www.ksrevenue.org/pdf/kw1002017.pdf
+ withholding_allowance = 43.27 * allowances
+ taxable_pay = salary - withholding_allowance
+
+ # Tax Percentage Method for Single, taxable pay over $58, under $346
+ wh = -round(((taxable_pay - 58) * 0.031) - additional_withholding, 2)
+
+ employee = self._createEmployee()
+
+ contract = self._createContract(employee,
+ salary,
+ struct_id=self.ref(
+ 'l10n_us_ks_hr_payroll.hr_payroll_salary_structure_us_ks_employee'),
+ schedule_pay=schedule_pay)
+ contract.ks_k4_allowances = allowances
+ contract.ks_additional_withholding = additional_withholding
+ contract.ks_k4_filing_status = 'single'
+
+ self.assertEqual(contract.schedule_pay, 'weekly')
+
+ # tax rates
+ ks_unemp = contract.ks_unemp_rate(2018) / -100.0
+
+ self._log('2018 Kansas tax first payslip:')
+ payslip = self._createPayslip(employee, '2018-01-01', '2018-01-31')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['KS_UNEMP_WAGES'], salary)
+ self.assertPayrollEqual(cats['KS_UNEMP'], cats['KS_UNEMP_WAGES'] * ks_unemp)
+ self.assertPayrollEqual(cats['KS_WITHHOLD'], wh)
+
+ process_payslip(payslip)
+
+ # Make a new payslip, this one will have maximums
+
+ remaining_ks_unemp_wages = self.KS_UNEMP_MAX_WAGE - salary if (self.KS_UNEMP_MAX_WAGE - 2 * salary < salary) \
+ else salary
+
+ self._log('2018 Kansas tax second payslip:')
+ payslip = self._createPayslip(employee, '2018-02-01', '2018-02-28')
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['KS_UNEMP_WAGES'], remaining_ks_unemp_wages)
+ self.assertPayrollEqual(cats['KS_UNEMP'], remaining_ks_unemp_wages * ks_unemp)
+
+ def test_2018_taxes_with_state_exempt(self):
+ salary = 210
+ schedule_pay = 'weekly'
+ allowances = 2
+
+ # Tax Exempt
+ wh = 0
+
+ employee = self._createEmployee()
+
+ contract = self._createContract(employee,
+ salary,
+ struct_id=self.ref(
+ 'l10n_us_ks_hr_payroll.hr_payroll_salary_structure_us_ks_employee'),
+ schedule_pay=schedule_pay)
+ contract.ks_k4_allowances = allowances
+ contract.ks_k4_filing_status = 'exempt'
+
+ payslip = self._createPayslip(employee, '2018-02-01', '2018-02-28')
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+
+ self.assertEqual(cats['KS_WITHHOLD'], wh)
diff --git a/l10n_us_mo_hr_payroll/tests/test_us_mo_payslip_2018.py b/l10n_us_mo_hr_payroll/tests/test_us_mo_payslip_2018.py
index 282b3373..9204b171 100755
--- a/l10n_us_mo_hr_payroll/tests/test_us_mo_payslip_2018.py
+++ b/l10n_us_mo_hr_payroll/tests/test_us_mo_payslip_2018.py
@@ -1,7 +1,6 @@
# -*- coding: utf-8 -*-
from openerp.addons.l10n_us_hr_payroll.tests.test_us_payslip import TestUsPayslip, process_payslip
-from openerp.addons.l10n_us_hr_payroll.l10n_us_hr_payroll import USHrContract
class TestUsMoPayslip(TestUsPayslip):
diff --git a/l10n_us_nc_hr_payroll/tests/test_us_nc_payslip_2018.py b/l10n_us_nc_hr_payroll/tests/test_us_nc_payslip_2018.py
new file mode 100755
index 00000000..12612f50
--- /dev/null
+++ b/l10n_us_nc_hr_payroll/tests/test_us_nc_payslip_2018.py
@@ -0,0 +1,418 @@
+from odoo.addons.l10n_us_hr_payroll.tests.test_us_payslip import TestUsPayslip, process_payslip
+from odoo.addons.l10n_us_hr_payroll.models.l10n_us_hr_payroll import USHrContract
+
+
+class TestUsNCPayslip(TestUsPayslip):
+ ###
+ # Taxes and Rates
+ ###
+ NC_UNEMP_MAX_WAGE = 7000.0
+
+
+ def test_2018_taxes_weekly(self):
+ salary = 5000.0
+ schedule_pay = 'weekly'
+ # allowance_multiplier and Portion of Standard Deduction for weekly
+ allowance_multiplier = 48.08
+ PST = 168.27
+
+ exemption = 1
+ # Algorithm derived from percentage method in https://files.nc.gov/ncdor/documents/files/nc-30_book_web.pdf
+
+ wh = -round((salary - (PST + (allowance_multiplier * exemption))) * 0.05599)
+
+ employee = self._createEmployee()
+ employee.company_id.nc_unemp_rate_2018 = 0.06
+
+ contract = self._createContract(employee, salary, struct_id=self.ref('l10n_us_nc_hr_payroll.hr_payroll_salary_structure_us_nc_employee'), schedule_pay=schedule_pay)
+ contract.nc_nc4_allowances = exemption
+
+ self.assertEqual(contract.schedule_pay, 'weekly')
+
+ # tax rates
+ nc_unemp = contract.nc_unemp_rate(2018) / -100.0
+
+
+ self._log('2018 North Carolina tax first payslip weekly:')
+ payslip = self._createPayslip(employee, '2018-01-01', '2018-01-31')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['NC_UNEMP_WAGES'], salary)
+ self.assertPayrollEqual(cats['NC_UNEMP'], cats['NC_UNEMP_WAGES'] * nc_unemp)
+ self.assertPayrollEqual(cats['NC_INC_WITHHOLD'], wh)
+
+ process_payslip(payslip)
+
+ # Make a new payslip, this one will have maximums
+
+ remaining_nc_unemp_wages = self.NC_UNEMP_MAX_WAGE - salary if (self.NC_UNEMP_MAX_WAGE - 2*salary < salary) \
+ else salary
+
+ self._log('2018 North Carolina tax second payslip weekly:')
+ payslip = self._createPayslip(employee, '2018-02-01', '2018-02-28')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['NC_UNEMP_WAGES'], remaining_nc_unemp_wages)
+ self.assertPayrollEqual(cats['NC_UNEMP'], remaining_nc_unemp_wages * nc_unemp)
+
+ def test_2018_taxes_with_external_weekly(self):
+ salary = 5000.0
+ external_wages = 7000.0
+
+ schedule_pay = 'weekly'
+
+ employee = self._createEmployee()
+ employee.company_id.nc_unemp_rate_2018 = 0.06
+
+ contract = self._createContract(employee, salary, external_wages=external_wages,
+ struct_id=self.ref('l10n_us_nc_hr_payroll.hr_payroll_salary_structure_us_nc_employee'), schedule_pay=schedule_pay)
+
+ # tax rates
+ nc_unemp = contract.nc_unemp_rate(2018) / -100.0
+
+ self._log('2018 NorthCarolina_external tax first payslip weekly:')
+ payslip = self._createPayslip(employee, '2018-01-01', '2018-01-31')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['NC_UNEMP_WAGES'], self.NC_UNEMP_MAX_WAGE - external_wages)
+ self.assertPayrollEqual(cats['NC_UNEMP'], cats['NC_UNEMP_WAGES'] * nc_unemp)
+
+ def test_2018_taxes_with_state_exempt_weekly(self):
+ salary = 5000.0
+ external_wages = 6000.0
+ schedule_pay = 'weekly'
+
+ employee = self._createEmployee()
+ employee.company_id.nc_unemp_rate_2018 = 0.06
+
+ contract = self._createContract(employee, salary, external_wages=external_wages, struct_id=self.ref(
+ 'l10n_us_nc_hr_payroll.hr_payroll_salary_structure_us_nc_employee'), futa_type=USHrContract.FUTA_TYPE_BASIC, schedule_pay=schedule_pay)
+
+ # tax rates
+ nc_unemp = contract.nc_unemp_rate(2018) / -100.0
+
+ self.assertPayrollEqual(nc_unemp, 0.0)
+
+ self._log('2018 North Carolina exempt tax first payslip weekly:')
+ payslip = self._createPayslip(employee, '2018-01-01', '2018-01-31')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['NC_UNEMP_WAGES'], self.NC_UNEMP_MAX_WAGE - external_wages)
+ self.assertPayrollEqual(cats['NC_UNEMP'], cats['NC_UNEMP_WAGES'] * nc_unemp)
+
+ def test_2018_taxes_biweekly(self):
+ salary = 5000.0
+ schedule_pay = 'bi-weekly'
+ # allowance_multiplier and Portion of Standard Deduction for weekly
+ allowance_multiplier = 96.15
+ PST = 336.54
+
+ allowances = 2
+ # Algorithm derived from percentage method in https://files.nc.gov/ncdor/documents/files/nc-30_book_web.pdf
+
+ wh = -round((salary - (PST + (allowance_multiplier * allowances))) * 0.05599)
+
+ employee = self._createEmployee()
+ employee.company_id.nc_unemp_rate_2018 = 0.06
+
+ contract = self._createContract(employee, salary, struct_id=self.ref('l10n_us_nc_hr_payroll.hr_payroll_salary_structure_us_nc_employee'), schedule_pay=schedule_pay)
+ contract.nc_nc4_allowances = allowances
+
+ self.assertEqual(contract.schedule_pay, 'bi-weekly')
+
+ # tax rates
+ nc_unemp = contract.nc_unemp_rate(2018) / -100.0
+
+
+ self._log('2018 North Carolina tax first payslip bi-weekly:')
+ payslip = self._createPayslip(employee, '2018-01-01', '2018-01-31')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['NC_UNEMP_WAGES'], salary)
+ self.assertPayrollEqual(cats['NC_UNEMP'], cats['NC_UNEMP_WAGES'] * nc_unemp)
+ self.assertPayrollEqual(cats['NC_INC_WITHHOLD'], wh)
+
+ process_payslip(payslip)
+
+ # Make a new payslip, this one will have maximums
+
+ remaining_nc_unemp_wages = self.NC_UNEMP_MAX_WAGE - salary if (self.NC_UNEMP_MAX_WAGE - 2*salary < salary) \
+ else salary
+
+ self._log('2018 North Carolina tax second payslip bi-weekly:')
+ payslip = self._createPayslip(employee, '2018-02-01', '2018-02-28')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['NC_UNEMP_WAGES'], remaining_nc_unemp_wages)
+ self.assertPayrollEqual(cats['NC_UNEMP'], remaining_nc_unemp_wages * nc_unemp)
+
+ def test_2018_taxes_semimonthly(self):
+ salary = 4000.0
+ schedule_pay = 'semi-monthly'
+ nc_nc4_filing_status = 'head_household'
+ # allowance_multiplier and Portion of Standard Deduction for weekly
+ allowance_multiplier = 104.17
+ PST = 583.33
+
+ allowances = 1
+ # Algorithm derived from percentage method in https://files.nc.gov/ncdor/documents/files/nc-30_book_web.pdf
+
+ wh = -round((salary - (PST + (allowance_multiplier * allowances))) * 0.05599)
+
+ employee = self._createEmployee()
+ employee.company_id.nc_unemp_rate_2018 = 0.06
+
+ contract = self._createContract(employee, salary, struct_id=self.ref(
+ 'l10n_us_nc_hr_payroll.hr_payroll_salary_structure_us_nc_employee'), schedule_pay=schedule_pay)
+ contract.nc_nc4_allowances = allowances
+ contract.nc_nc4_filing_status = nc_nc4_filing_status
+
+ self.assertEqual(contract.schedule_pay, 'semi-monthly')
+
+ # tax rates
+ nc_unemp = contract.nc_unemp_rate(2018) / -100.0
+
+ self._log('2018 North Carolina tax first payslip:')
+ payslip = self._createPayslip(employee, '2018-01-01', '2018-01-31')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['NC_UNEMP_WAGES'], salary)
+ self.assertPayrollEqual(cats['NC_UNEMP'], cats['NC_UNEMP_WAGES'] * nc_unemp)
+ self.assertPayrollEqual(cats['NC_INC_WITHHOLD'], wh)
+
+ process_payslip(payslip)
+
+ # Make a new payslip, this one will have maximums
+
+ remaining_nc_unemp_wages = self.NC_UNEMP_MAX_WAGE - salary if (self.NC_UNEMP_MAX_WAGE - 2 * salary < salary) \
+ else salary
+
+ self._log('2018 North Carolina tax second payslip:')
+ payslip = self._createPayslip(employee, '2018-02-01', '2018-02-28')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['NC_UNEMP_WAGES'], remaining_nc_unemp_wages)
+ self.assertPayrollEqual(cats['NC_UNEMP'], remaining_nc_unemp_wages * nc_unemp)
+
+ def test_2018_taxes_monthly(self):
+ salary = 4000.0
+ schedule_pay = 'monthly'
+ # allowance_multiplier and Portion of Standard Deduction for weekly
+ allowance_multiplier = 208.33
+ PST = 729.17
+
+ allowances = 1
+ # Algorithm derived from percentage method in https://files.nc.gov/ncdor/documents/files/nc-30_book_web.pdf
+
+ wh = -round((salary - (PST + (allowance_multiplier * allowances))) * 0.05599)
+
+ employee = self._createEmployee()
+ employee.company_id.nc_unemp_rate_2018 = 0.06
+
+ contract = self._createContract(employee, salary, struct_id=self.ref(
+ 'l10n_us_nc_hr_payroll.hr_payroll_salary_structure_us_nc_employee'), schedule_pay=schedule_pay)
+ contract.nc_nc4_allowances = allowances
+
+ self.assertEqual(contract.schedule_pay, 'monthly')
+
+ # tax rates
+ nc_unemp = contract.nc_unemp_rate(2018) / -100.0
+
+ self._log('2018 North Carolina tax first payslip:')
+ payslip = self._createPayslip(employee, '2018-01-01', '2018-01-31')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['NC_UNEMP_WAGES'], salary)
+ self.assertPayrollEqual(cats['NC_UNEMP'], cats['NC_UNEMP_WAGES'] * nc_unemp)
+ self.assertPayrollEqual(cats['NC_INC_WITHHOLD'], wh)
+
+ process_payslip(payslip)
+
+ # Make a new payslip, this one will have maximums
+
+ remaining_nc_unemp_wages = self.NC_UNEMP_MAX_WAGE - salary if (
+ self.NC_UNEMP_MAX_WAGE - 2 * salary < salary) \
+ else salary
+
+ self._log('2018 North Carolina tax second payslip:')
+ payslip = self._createPayslip(employee, '2018-02-01', '2018-02-28')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['NC_UNEMP_WAGES'], remaining_nc_unemp_wages)
+ self.assertPayrollEqual(cats['NC_UNEMP'], remaining_nc_unemp_wages * nc_unemp)
+
+ def test_tax_exempt(self):
+ salary = 4000.0
+ wh = 0
+ schedule_pay = 'weekly'
+ excemptions = 1
+
+ employee = self._createEmployee()
+ employee.company_id.nc_unemp_rate_2018 = 0.06
+
+ contract = self._createContract(employee, salary, struct_id=self.ref(
+ 'l10n_us_nc_hr_payroll.hr_payroll_salary_structure_us_nc_employee'), schedule_pay=schedule_pay)
+ contract.nc_nc4_allowances = excemptions
+ contract.nc_nc4_filing_status = 'exempt'
+
+ self.assertEqual(contract.schedule_pay, 'weekly')
+
+ # tax rates
+ nc_unemp = contract.nc_unemp_rate(2018) / -100.0
+
+
+ self._log('2018 North Carolina tax first payslip:')
+ payslip = self._createPayslip(employee, '2018-01-01', '2018-01-31')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['NC_UNEMP_WAGES'], salary)
+ self.assertPayrollEqual(cats['NC_UNEMP'], cats['NC_UNEMP_WAGES'] * nc_unemp)
+ self.assertPayrollEqual(cats['NC_INC_WITHHOLD'], wh)
+
+ process_payslip(payslip)
+
+ # Make a new payslip, this one will have maximums
+
+ remaining_nc_unemp_wages = self.NC_UNEMP_MAX_WAGE - salary if (
+ self.NC_UNEMP_MAX_WAGE - 2 * salary < salary) \
+ else salary
+
+ self._log('2018 North Carolina tax second payslip:')
+ payslip = self._createPayslip(employee, '2018-02-01', '2018-02-28')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['NC_UNEMP_WAGES'], remaining_nc_unemp_wages)
+ self.assertPayrollEqual(cats['NC_UNEMP'], remaining_nc_unemp_wages * nc_unemp)
+
+ def test_additional_withholding(self):
+ salary = 4000.0
+ schedule_pay = 'weekly'
+ # allowance_multiplier and Portion of Standard Deduction for weekly
+ allowance_multiplier = 48.08
+ PST = 168.27
+ additional_wh = 40.0
+
+ #4000 - (168.27 + (48.08 * 1)
+
+ allowances = 1
+ # Algorithm derived from percentage method in https://files.nc.gov/ncdor/documents/files/nc-30_book_web.pdf
+
+ wh = -round(((salary - (PST + (allowance_multiplier * allowances))) * 0.05599) + additional_wh)
+
+ employee = self._createEmployee()
+ employee.company_id.nc_unemp_rate_2018 = 0.06
+
+ contract = self._createContract(employee,
+ salary,
+ struct_id=self.ref('l10n_us_nc_hr_payroll.hr_payroll_salary_structure_us_nc_employee'),
+ schedule_pay=schedule_pay)
+ contract.w4_is_nonresident_alien = True
+ contract.nc_nc4_additional_wh = additional_wh
+ contract.nc_nc4_allowances = allowances
+
+ self.assertEqual(contract.schedule_pay, 'weekly')
+ self.assertEqual(contract.w4_is_nonresident_alien, True)
+
+ # tax rates
+ nc_unemp = contract.nc_unemp_rate(2018) / -100.0
+
+ self._log('2018 North Carolina tax first payslip weekly:')
+ payslip = self._createPayslip(employee, '2018-01-01', '2018-01-31')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['NC_UNEMP_WAGES'], salary)
+ self.assertPayrollEqual(cats['NC_UNEMP'], cats['NC_UNEMP_WAGES'] * nc_unemp)
+ self.assertPayrollEqual(cats['NC_INC_WITHHOLD'], wh)
+
+ process_payslip(payslip)
+
+ # Make a new payslip, this one will have maximums
+
+ remaining_nc_unemp_wages = self.NC_UNEMP_MAX_WAGE - salary if (self.NC_UNEMP_MAX_WAGE - 2 * salary < salary) \
+ else salary
+
+ self._log('2018 North Carolina tax second payslip weekly:')
+ payslip = self._createPayslip(employee, '2018-02-01', '2018-02-28')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['NC_UNEMP_WAGES'], remaining_nc_unemp_wages)
+ self.assertPayrollEqual(cats['NC_UNEMP'], remaining_nc_unemp_wages * nc_unemp)
+
+ def test_underflow(self):
+ salary = 150.0
+ schedule_pay = 'weekly'
+ # allowance_multiplier and Portion of Standard Deduction for weekly
+ allowance_multiplier = 48.08
+ PST = 168.27
+
+ exemption = 1
+
+ # Withholding should be 0, since pay is so low it's less than PST.
+ wh = 0.0
+
+ employee = self._createEmployee()
+ employee.company_id.nc_unemp_rate_2018 = 0.06
+
+ contract = self._createContract(employee, salary, struct_id=self.ref(
+ 'l10n_us_nc_hr_payroll.hr_payroll_salary_structure_us_nc_employee'), schedule_pay=schedule_pay)
+ contract.nc_nc4_allowances = exemption
+
+ self.assertEqual(contract.schedule_pay, 'weekly')
+
+ # tax rates
+ nc_unemp = contract.nc_unemp_rate(2018) / -100.0
+
+ self._log('2018 North Carolina tax first payslip weekly:')
+ payslip = self._createPayslip(employee, '2018-01-01', '2018-01-31')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['NC_UNEMP_WAGES'], salary)
+ self.assertPayrollEqual(cats['NC_UNEMP'], cats['NC_UNEMP_WAGES'] * nc_unemp)
+ self.assertPayrollEqual(cats['NC_INC_WITHHOLD'], wh)
+
+ process_payslip(payslip)
diff --git a/l10n_us_nj_hr_payroll/tests/test_us_nj_payslip_2018.py b/l10n_us_nj_hr_payroll/tests/test_us_nj_payslip_2018.py
new file mode 100755
index 00000000..24b0dd54
--- /dev/null
+++ b/l10n_us_nj_hr_payroll/tests/test_us_nj_payslip_2018.py
@@ -0,0 +1,134 @@
+from odoo.addons.l10n_us_hr_payroll.tests.test_us_payslip import TestUsPayslip, process_payslip
+
+
+class TestUsNJPayslip(TestUsPayslip):
+ ###
+ # 2018 Taxes and Rates
+ ###
+ NJ_UNEMP_MAX_WAGE = 33700.0
+
+ # Examples found on page 24 of http://www.state.nj.us/treasury/taxation/pdf/current/njwt.pdf
+ def test_2018_taxes_example1(self):
+ salary = 300
+ schedule_pay = 'weekly'
+ allowances = 1
+ additional_withholding = 0
+
+ # Tax Percentage Method for Single, taxable pay over $58, under $346
+ wh = -4.21
+
+ employee = self._createEmployee()
+ employee.company_id.nj_unemp_employee = 0.3825
+ employee.company_id.nj_unemp_company = 3.4
+ employee.company_id.nj_sdi_employee = 0.19
+ employee.company_id.nj_sdi_company = 0.5
+ employee.company_id.nj_fli = 0.09
+ employee.company_id.nj_wf = 0.0
+
+ contract = self._createContract(employee,
+ salary,
+ struct_id=self.ref(
+ 'l10n_us_nj_hr_payroll.hr_payroll_salary_structure_us_nj_employee'),
+ schedule_pay=schedule_pay)
+ contract.nj_njw4_allowances = allowances
+ contract.nj_additional_withholding = additional_withholding
+ contract.nj_njw4_filing_status = 'single'
+ contract.nj_njw4_rate_table = 'A'
+
+ # tax rates
+ nj_unemp_employee = contract.nj_unemp_employee_rate(2018) / -100.0
+ nj_unemp_company = contract.nj_unemp_company_rate(2018) / -100.0
+ nj_sdi_employee = contract.nj_sdi_employee_rate(2018) / -100.0
+ nj_sdi_company = contract.nj_sdi_company_rate(2018) / -100.0
+ nj_fli = contract.nj_fli_rate(2018) / -100.0
+ nj_wf = contract.nj_wf_rate(2018) / -100.0
+
+ self.assertEqual(contract.schedule_pay, 'weekly')
+
+ self._log('2018 New Jersey tax first payslip:')
+ payslip = self._createPayslip(employee, '2018-01-01', '2018-01-31')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['NJ_UNEMP_WAGES'], salary)
+ self.assertPayrollEqual(cats['NJ_UNEMP_EMPLOYEE'], round(cats['NJ_UNEMP_WAGES'] * nj_unemp_employee, 2))
+ self.assertPayrollEqual(cats['NJ_UNEMP_COMPANY'], cats['NJ_UNEMP_WAGES'] * nj_unemp_company)
+ self.assertPayrollEqual(cats['NJ_SDI_EMPLOYEE'], cats['NJ_SDI_WAGES'] * nj_sdi_employee)
+ self.assertPayrollEqual(cats['NJ_SDI_COMPANY'], cats['NJ_SDI_WAGES'] * nj_sdi_company)
+ self.assertPayrollEqual(cats['NJ_FLI'], cats['NJ_FLI_WAGES'] * nj_fli)
+ self.assertPayrollEqual(cats['NJ_WF'], cats['NJ_WF_WAGES'] * nj_wf)
+ self.assertPayrollEqual(cats['NJ_WITHHOLD'], wh)
+
+ process_payslip(payslip)
+
+ # Make a new payslip, this one will have maximums
+
+ remaining_nj_unemp_wages = self.NJ_UNEMP_MAX_WAGE - salary if (self.NJ_UNEMP_MAX_WAGE - 2 * salary < salary) \
+ else salary
+
+ self._log('2018 New Jersey tax second payslip:')
+ payslip = self._createPayslip(employee, '2018-02-01', '2018-02-28')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['NJ_UNEMP_WAGES'], remaining_nj_unemp_wages)
+ self.assertPayrollEqual(cats['NJ_UNEMP_COMPANY'], remaining_nj_unemp_wages * nj_unemp_company)
+ self.assertPayrollEqual(cats['NJ_UNEMP_EMPLOYEE'], remaining_nj_unemp_wages * nj_unemp_employee)
+
+ def test_2018_taxes_example2(self):
+ salary = 1400.00
+ schedule_pay = 'weekly'
+ allowances = 3
+ additional_withholding = 0
+
+ # Tax Percentage Method for Single, taxable pay over $58, under $346
+ wh = -27.60
+
+ employee = self._createEmployee()
+ employee.company_id.nj_unemp_employee = 0.3825
+ employee.company_id.nj_unemp_company = 3.4
+ employee.company_id.nj_sdi_employee = 0.19
+ employee.company_id.nj_sdi_company = 0.5
+ employee.company_id.nj_fli = 0.09
+ employee.company_id.nj_wf = 0.0
+
+ contract = self._createContract(employee,
+ salary,
+ struct_id=self.ref(
+ 'l10n_us_nj_hr_payroll.hr_payroll_salary_structure_us_nj_employee'),
+ schedule_pay=schedule_pay)
+ contract.nj_njw4_allowances = allowances
+ contract.nj_additional_withholding = additional_withholding
+ contract.nj_njw4_filing_status = 'married_joint'
+
+ # tax rates
+ nj_unemp_employee = contract.nj_unemp_employee_rate(2018) / -100.0
+ nj_unemp_company = contract.nj_unemp_company_rate(2018) / -100.0
+ nj_sdi_employee = contract.nj_sdi_employee_rate(2018) / -100.0
+ nj_sdi_company = contract.nj_sdi_company_rate(2018) / -100.0
+ nj_fli = contract.nj_fli_rate(2018) / -100.0
+ nj_wf = contract.nj_wf_rate(2018) / -100.0
+
+ self.assertEqual(contract.schedule_pay, 'weekly')
+
+ self._log('2018 New Jersey tax first payslip:')
+ payslip = self._createPayslip(employee, '2018-01-01', '2018-01-31')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['NJ_UNEMP_WAGES'], salary)
+ self.assertPayrollEqual(cats['NJ_UNEMP_EMPLOYEE'], round((cats['NJ_UNEMP_WAGES'] * nj_unemp_employee), 2))
+ self.assertPayrollEqual(cats['NJ_UNEMP_COMPANY'], cats['NJ_UNEMP_WAGES'] * nj_unemp_company)
+ self.assertPayrollEqual(cats['NJ_SDI_EMPLOYEE'], cats['NJ_SDI_WAGES'] * nj_sdi_employee)
+ self.assertPayrollEqual(cats['NJ_SDI_COMPANY'], cats['NJ_SDI_WAGES'] * nj_sdi_company)
+ self.assertPayrollEqual(cats['NJ_FLI'], cats['NJ_FLI_WAGES'] * nj_fli)
+ self.assertPayrollEqual(cats['NJ_WF'], cats['NJ_WF_WAGES'] * nj_wf)
+ self.assertPayrollEqual(cats['NJ_WITHHOLD'], wh)
+
+ process_payslip(payslip)
diff --git a/l10n_us_ny_hr_payroll/tests/test_us_ny_payslip_2018.py b/l10n_us_ny_hr_payroll/tests/test_us_ny_payslip_2018.py
new file mode 100755
index 00000000..d7400b6d
--- /dev/null
+++ b/l10n_us_ny_hr_payroll/tests/test_us_ny_payslip_2018.py
@@ -0,0 +1,151 @@
+from odoo.addons.l10n_us_hr_payroll.tests.test_us_payslip import TestUsPayslip, process_payslip
+
+
+class TestUsNYPayslip(TestUsPayslip):
+ ###
+ # Taxes and Rates
+ ###
+ NY_UNEMP_MAX_WAGE = 11100
+
+ # Examples from http://www.edd.ca.gov/pdf_pub_ctr/18methb.pdf
+ def test_single_example1(self):
+ salary = 400
+ schedule_pay = 'weekly'
+ allowances = 3
+ additional_withholding = 0
+
+ wh = -8.20
+
+ employee = self._createEmployee()
+ employee.company_id.ny_unemp_rate_2018 = 0.825
+ employee.company_id.ny_rsf_rate_2018 = 0.075
+ employee.company_id.ny_mctmt_rate_2018 = 0.0
+
+ contract = self._createContract(employee,
+ salary,
+ struct_id=self.ref(
+ 'l10n_us_ny_hr_payroll.hr_payroll_salary_structure_us_ny_employee'),
+ schedule_pay=schedule_pay)
+ contract.ny_it2104_allowances = allowances
+ contract.ny_additional_withholding = additional_withholding
+ contract.ny_it2104_filing_status = 'single'
+
+ self.assertEqual(contract.schedule_pay, 'weekly')
+
+ # tax rates
+ ny_unemp = contract.ny_unemp_rate(2018) / -100.0
+ ny_rsf = contract.ny_rsf_rate(2018) / -100.0
+ ny_mctmt = contract.ny_mctmt_rate(2018) / -100.0
+
+ self._log('2018 New York tax first payslip:')
+ payslip = self._createPayslip(employee, '2018-01-01', '2018-01-31')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['NY_UNEMP_WAGES'], salary)
+ self.assertPayrollEqual(cats['NY_UNEMP'], cats['NY_UNEMP_WAGES'] * ny_unemp)
+ self.assertPayrollEqual(cats['NY_RSF'], cats['NY_UNEMP_WAGES'] * ny_rsf)
+ self.assertPayrollEqual(cats['NY_MCTMT'], cats['NY_UNEMP_WAGES'] * ny_mctmt)
+ self.assertPayrollEqual(cats['NY_WITHHOLD'], wh)
+
+ process_payslip(payslip)
+
+ # Make a new payslip, this one will have maximums
+
+ remaining_ny_unemp_wages = self.NY_UNEMP_MAX_WAGE - salary if (self.NY_UNEMP_MAX_WAGE - 2 * salary < salary) \
+ else salary
+
+ self._log('2018 New York tax second payslip:')
+ payslip = self._createPayslip(employee, '2018-02-01', '2018-02-28')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['NY_UNEMP_WAGES'], remaining_ny_unemp_wages)
+ self.assertPayrollEqual(cats['NY_UNEMP'], remaining_ny_unemp_wages * ny_unemp)
+
+ def test_single_example2(self):
+ salary = 5000
+ schedule_pay = 'semi-monthly'
+ allowances = 3
+ additional_withholding = 0
+
+ wh = -284.19
+
+ employee = self._createEmployee()
+ employee.company_id.ny_unemp_rate_2018 = 0.825
+ employee.company_id.ny_rsf_rate_2018 = 0.075
+ employee.company_id.ny_mctmt_rate_2018 = 0.0
+
+ contract = self._createContract(employee,
+ salary,
+ struct_id=self.ref(
+ 'l10n_us_ny_hr_payroll.hr_payroll_salary_structure_us_ny_employee'),
+ schedule_pay=schedule_pay)
+ contract.ny_it2104_allowances = allowances
+ contract.ny_additional_withholding = additional_withholding
+ contract.ny_it2104_filing_status = 'married'
+
+ self.assertEqual(contract.schedule_pay, 'semi-monthly')
+
+ # tax rates
+ ny_unemp = contract.ny_unemp_rate(2018) / -100.0
+ ny_rsf = contract.ny_rsf_rate(2018) / -100.0
+ ny_mctmt = contract.ny_mctmt_rate(2018) / -100.0
+
+ self._log('2018 New York tax first payslip:')
+ payslip = self._createPayslip(employee, '2018-01-01', '2018-01-31')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['NY_UNEMP_WAGES'], salary)
+ self.assertPayrollEqual(cats['NY_UNEMP'], cats['NY_UNEMP_WAGES'] * ny_unemp)
+ self.assertPayrollEqual(cats['NY_RSF'], cats['NY_UNEMP_WAGES'] * ny_rsf)
+ self.assertPayrollEqual(cats['NY_MCTMT'], cats['NY_UNEMP_WAGES'] * ny_mctmt)
+ self.assertPayrollEqual(cats['NY_WITHHOLD'], wh)
+
+ process_payslip(payslip)
+
+ def test_single_example3(self):
+ salary = 50000
+ schedule_pay = 'monthly'
+ allowances = 3
+ additional_withholding = 0
+
+ wh = -3575.63
+
+ employee = self._createEmployee()
+ employee.company_id.ny_unemp_rate_2018 = 0.825
+ employee.company_id.ny_rsf_rate_2018 = 0.075
+ employee.company_id.ny_mctmt_rate_2018 = 0.0
+
+ contract = self._createContract(employee,
+ salary,
+ struct_id=self.ref(
+ 'l10n_us_ny_hr_payroll.hr_payroll_salary_structure_us_ny_employee'),
+ schedule_pay=schedule_pay)
+ contract.ny_it2104_allowances = allowances
+ contract.ny_additional_withholding = additional_withholding
+ contract.ny_it2104_filing_status = 'single'
+
+ self.assertEqual(contract.schedule_pay, 'monthly')
+
+ # tax rates
+ ny_unemp = contract.ny_unemp_rate(2018) / -100.0
+ ny_rsf = contract.ny_rsf_rate(2018) / -100.0
+ ny_mctmt = contract.ny_mctmt_rate(2018) / -100.0
+
+ self._log('2018 New York tax first payslip:')
+ payslip = self._createPayslip(employee, '2018-01-01', '2018-01-31')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['NY_WITHHOLD'], wh)
+
diff --git a/l10n_us_oh_hr_payroll/tests/test_us_oh_payslip_2016.py b/l10n_us_oh_hr_payroll/tests/test_us_oh_payslip_2016.py
index f71af35f..dde1cf7b 100755
--- a/l10n_us_oh_hr_payroll/tests/test_us_oh_payslip_2016.py
+++ b/l10n_us_oh_hr_payroll/tests/test_us_oh_payslip_2016.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
from openerp.addons.l10n_us_hr_payroll.tests.test_us_payslip import TestUsPayslip, process_payslip
-from openerp.addons.l10n_us_hr_payroll.l10n_us_hr_payroll import USHrContract
+from openerp.addons.l10n_us_hr_payroll.models.l10n_us_hr_payroll import USHrContract
class TestUsOhPayslip(TestUsPayslip):
diff --git a/l10n_us_oh_hr_payroll/tests/test_us_oh_payslip_2017.py b/l10n_us_oh_hr_payroll/tests/test_us_oh_payslip_2017.py
index c0c7b321..57261538 100755
--- a/l10n_us_oh_hr_payroll/tests/test_us_oh_payslip_2017.py
+++ b/l10n_us_oh_hr_payroll/tests/test_us_oh_payslip_2017.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
from openerp.addons.l10n_us_hr_payroll.tests.test_us_payslip import TestUsPayslip, process_payslip
-from openerp.addons.l10n_us_hr_payroll.l10n_us_hr_payroll import USHrContract
+from openerp.addons.l10n_us_hr_payroll.models.l10n_us_hr_payroll import USHrContract
class TestUsOhPayslip(TestUsPayslip):
diff --git a/l10n_us_oh_hr_payroll/tests/test_us_oh_payslip_2018.py b/l10n_us_oh_hr_payroll/tests/test_us_oh_payslip_2018.py
index dbf05fc5..ac7ae8f9 100755
--- a/l10n_us_oh_hr_payroll/tests/test_us_oh_payslip_2018.py
+++ b/l10n_us_oh_hr_payroll/tests/test_us_oh_payslip_2018.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
from odoo.addons.l10n_us_hr_payroll.tests.test_us_payslip import TestUsPayslip, process_payslip
-from odoo.addons.l10n_us_hr_payroll.l10n_us_hr_payroll import USHrContract
+from odoo.addons.l10n_us_hr_payroll.models.l10n_us_hr_payroll import USHrContract
class TestUsOhPayslip(TestUsPayslip):
diff --git a/l10n_us_pa_hr_payroll/tests/test_us_pa_payslip_2018.py b/l10n_us_pa_hr_payroll/tests/test_us_pa_payslip_2018.py
new file mode 100755
index 00000000..f6717bfe
--- /dev/null
+++ b/l10n_us_pa_hr_payroll/tests/test_us_pa_payslip_2018.py
@@ -0,0 +1,39 @@
+from odoo.addons.l10n_us_hr_payroll.tests.test_us_payslip import TestUsPayslip, process_payslip
+from odoo.addons.l10n_us_hr_payroll.models.l10n_us_hr_payroll import USHrContract
+
+
+class TestUsPAPayslip(TestUsPayslip):
+ ###
+ # Taxes and Rates
+ ###
+ PA_UNEMP_MAX_WAGE = 10000.0
+
+ def test_2018_taxes(self):
+ self.debug = True
+ salary = 4166.67
+ wh = -127.92
+
+ employee = self._createEmployee()
+ employee.company_id.pa_unemp_employee_rate_2018 = 0.06
+ employee.company_id.pa_unemp_company_rate_2018 = 3.6785
+ employee.company_id.pa_withhold_rate_2018 = 3.07
+
+ contract = self._createContract(employee, salary, struct_id=self.ref('l10n_us_pa_hr_payroll.hr_payroll_salary_structure_us_pa_employee'))
+
+ # tax rates
+ pa_unemp_employee = contract.pa_unemp_employee_rate(2018) / -100.0
+ pa_unemp_company = contract.pa_unemp_company_rate(2018) / -100.0
+
+
+ self._log('2018 Pennsylvania tax first payslip:')
+ payslip = self._createPayslip(employee, '2018-01-01', '2018-01-31')
+ payslip.onchange_contract()
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['PA_UNEMP_WAGES'], salary)
+ self.assertPayrollEqual(cats['PA_UNEMP_EMPLOYEE'], cats['PA_UNEMP_WAGES'] * pa_unemp_employee)
+ self.assertPayrollEqual(cats['PA_UNEMP_COMPANY'], cats['PA_UNEMP_WAGES'] * pa_unemp_company)
+ self.assertPayrollEqual(cats['PA_WITHHOLD'], wh)
+
diff --git a/l10n_us_tx_hr_payroll/tests/test_us_tx_payslip_2018.py b/l10n_us_tx_hr_payroll/tests/test_us_tx_payslip_2018.py
new file mode 100755
index 00000000..5f794581
--- /dev/null
+++ b/l10n_us_tx_hr_payroll/tests/test_us_tx_payslip_2018.py
@@ -0,0 +1,161 @@
+from odoo.addons.l10n_us_hr_payroll.tests.test_us_payslip import TestUsPayslip, process_payslip
+from odoo.addons.l10n_us_hr_payroll.models.l10n_us_hr_payroll import USHrContract
+
+
+class TestUsTXPayslip(TestUsPayslip):
+ ###
+ # 2018 Taxes and Rates
+ ###
+ TX_UNEMP_MAX_WAGE = 9000.0
+
+ def test_2018_taxes(self):
+ salary = 5000.0
+
+ employee = self._createEmployee()
+ employee.company_id.tx_unemp_rate_2018 = 2.7
+ employee.company_id.tx_oa_rate_2018 = 0.0
+ employee.company_id.tx_etia_rate_2018 = 0.1
+
+ contract = self._createContract(employee, salary, struct_id=self.ref('l10n_us_tx_hr_payroll.hr_payroll_salary_structure_us_tx_employee'))
+
+ # tax rates
+ tx_unemp = contract.tx_unemp_rate(2018) / -100.0
+ tx_oa = contract.tx_oa_rate(2018) / -100.00
+ tx_etia = contract.tx_etia_rate(2018) / -100.00
+
+ self._log('2018 Texas tax first payslip:')
+ payslip = self._createPayslip(employee, '2018-01-01', '2018-01-31')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['TX_UNEMP_WAGES'], salary)
+ self.assertPayrollEqual(cats['TX_UNEMP'], cats['TX_UNEMP_WAGES'] * tx_unemp)
+ self.assertPayrollEqual(cats['TX_OA'], cats['TX_UNEMP_WAGES'] * tx_oa)
+ self.assertPayrollEqual(cats['TX_ETIA'], cats['TX_UNEMP_WAGES'] * tx_etia)
+
+ process_payslip(payslip)
+
+ # Make a new payslip, this one will have maximums
+
+ remaining_tx_unemp_wages = self.TX_UNEMP_MAX_WAGE - salary if (self.TX_UNEMP_MAX_WAGE - 2*salary < salary) \
+ else salary
+
+ self._log('2018 Texas tax second payslip:')
+ payslip = self._createPayslip(employee, '2018-02-01', '2018-02-28')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['TX_UNEMP_WAGES'], remaining_tx_unemp_wages)
+ self.assertPayrollEqual(cats['TX_UNEMP'], remaining_tx_unemp_wages * tx_unemp)
+
+ def test_2018_taxes_with_external(self):
+ salary = 5000.0
+ external_wages = 6000.0
+
+ employee = self._createEmployee()
+ employee.company_id.tx_unemp_rate_2018 = 2.7
+ employee.company_id.tx_oa_rate_2018 = 0.0
+ employee.company_id.tx_etia_rate_2018 = 0.1
+
+ contract = self._createContract(employee, salary, external_wages=external_wages,
+ struct_id=self.ref('l10n_us_tx_hr_payroll.hr_payroll_salary_structure_us_tx_employee'))
+
+ # tax rates
+ tx_unemp = contract.tx_unemp_rate(2018) / -100.0
+ tx_oa = contract.tx_oa_rate(2018) / -100.00
+ tx_etia = contract.tx_etia_rate(2018) / -100.00
+
+ self._log('2018 Texas_external tax first payslip:')
+ payslip = self._createPayslip(employee, '2018-01-01', '2018-01-31')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['TX_UNEMP_WAGES'], self.TX_UNEMP_MAX_WAGE - external_wages)
+ self.assertPayrollEqual(cats['TX_UNEMP'], cats['TX_UNEMP_WAGES'] * tx_unemp)
+ self.assertPayrollEqual(cats['TX_OA'], cats['TX_UNEMP_WAGES'] * tx_oa)
+ self.assertPayrollEqual(cats['TX_ETIA'], cats['TX_UNEMP_WAGES'] * tx_etia)
+
+ def test_2018_taxes_with_state_exempt(self):
+ salary = 5000.0
+ external_wages = 6000.0
+
+ employee = self._createEmployee()
+ employee.company_id.tx_unemp_rate_2018 = 2.7
+ employee.company_id.tx_oa_rate_2018 = 0.0
+ employee.company_id.tx_etia_rate_2018 = 0.1
+
+ contract = self._createContract(employee, salary, external_wages=external_wages, struct_id=self.ref(
+ 'l10n_us_tx_hr_payroll.hr_payroll_salary_structure_us_tx_employee'), futa_type=USHrContract.FUTA_TYPE_BASIC)
+
+ # tax rates
+ tx_unemp = contract.tx_unemp_rate(2018) / -100.0
+ tx_oa = contract.tx_oa_rate(2018) / -100.00
+ tx_etia = contract.tx_etia_rate(2018) / -100.00
+
+ self.assertPayrollEqual(tx_unemp, 0.0)
+
+ self._log('2018 Texas_external tax first payslip:')
+ payslip = self._createPayslip(employee, '2018-01-01', '2018-01-31')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['TX_UNEMP_WAGES'], self.TX_UNEMP_MAX_WAGE - external_wages)
+ self.assertPayrollEqual(cats['TX_UNEMP'], cats['TX_UNEMP_WAGES'] * tx_unemp)
+ self.assertPayrollEqual(cats['TX_OA'], cats['TX_UNEMP_WAGES'] * tx_oa)
+ self.assertPayrollEqual(cats['TX_ETIA'], cats['TX_UNEMP_WAGES'] * tx_etia)
+
+ def test_payslip_example(self):
+ salary = 2916.67
+
+ employee = self._createEmployee()
+ employee.company_id.tx_unemp_rate_2018 = 2.7
+ employee.company_id.tx_oa_rate_2018 = 0.0
+ employee.company_id.tx_etia_rate_2018 = 0.1
+
+ contract = self._createContract(employee, salary, struct_id=self.ref(
+ 'l10n_us_tx_hr_payroll.hr_payroll_salary_structure_us_tx_employee'))
+ contract.w4_allowances = 2
+ contract.w4_filing_status = 'single'
+
+ # tax rates
+ tx_unemp = contract.tx_unemp_rate(2018) / -100.0
+ tx_oa = contract.tx_oa_rate(2018) / -100.00
+ tx_etia = contract.tx_etia_rate(2018) / -100.00
+
+ self._log('2018 Texas tax first payslip:')
+ payslip = self._createPayslip(employee, '2018-01-01', '2018-01-31')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['TX_UNEMP_WAGES'], salary)
+ self.assertPayrollEqual(cats['TX_UNEMP'], cats['TX_UNEMP_WAGES'] * tx_unemp)
+ self.assertPayrollEqual(cats['TX_OA'], cats['TX_UNEMP_WAGES'] * tx_oa)
+ self.assertPayrollEqual(cats['TX_ETIA'], cats['TX_UNEMP_WAGES'] * tx_etia)
+
+ process_payslip(payslip)
+
+ # Make a new payslip, this one will have maximums
+
+ remaining_tx_unemp_wages = self.TX_UNEMP_MAX_WAGE - salary if (self.TX_UNEMP_MAX_WAGE - 2 * salary < salary) \
+ else salary
+
+ self._log('2018 Texas tax second payslip:')
+ payslip = self._createPayslip(employee, '2018-02-01', '2018-02-28')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['TX_UNEMP_WAGES'], remaining_tx_unemp_wages)
+ self.assertPayrollEqual(cats['TX_UNEMP'], remaining_tx_unemp_wages * tx_unemp)
+
diff --git a/l10n_us_va_hr_payroll/tests/test_us_va_payslip_2018.py b/l10n_us_va_hr_payroll/tests/test_us_va_payslip_2018.py
index 19e5671c..047e4d03 100755
--- a/l10n_us_va_hr_payroll/tests/test_us_va_payslip_2018.py
+++ b/l10n_us_va_hr_payroll/tests/test_us_va_payslip_2018.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
from openerp.addons.l10n_us_hr_payroll.tests.test_us_payslip import TestUsPayslip, process_payslip
-from openerp.addons.l10n_us_hr_payroll.l10n_us_hr_payroll import USHrContract
+from openerp.addons.l10n_us_hr_payroll.models.l10n_us_hr_payroll import USHrContract
class TestUsVaPayslip(TestUsPayslip):
diff --git a/l10n_us_wa_hr_payroll/tests/test_us_wa_payslip_2018.py b/l10n_us_wa_hr_payroll/tests/test_us_wa_payslip_2018.py
index 54631587..63090be9 100755
--- a/l10n_us_wa_hr_payroll/tests/test_us_wa_payslip_2018.py
+++ b/l10n_us_wa_hr_payroll/tests/test_us_wa_payslip_2018.py
@@ -1,5 +1,4 @@
from odoo.addons.l10n_us_hr_payroll.tests.test_us_payslip import TestUsPayslip, process_payslip
-from odoo.addons.l10n_us_hr_payroll.l10n_us_hr_payroll import USHrContract
class TestUsWAPayslip(TestUsPayslip):