diff --git a/l10n_us_mo_hr_payroll/README.rst b/l10n_us_mo_hr_payroll/README.rst
new file mode 100644
index 00000000..16fbf4d7
--- /dev/null
+++ b/l10n_us_mo_hr_payroll/README.rst
@@ -0,0 +1,40 @@
+***********************************
+Hibou - US Payroll - Missouri State
+***********************************
+
+Calculations and contribution registers for Missouri State Payroll.
+
+For more information and add-ons, visit `Hibou.io `_.
+
+=============
+Main Features
+=============
+
+* New Partners and Contribution Registers for:
+ * Missouri Department of Revenue - Unemployment
+ * Missouri Department of Revenue - Income Tax Withholding
+* Contract level Missouri Exemptions and MO W-4 fields
+* Company level Missouri Unemployment Rate
+
+.. image:: https://user-images.githubusercontent.com/15882954/41440915-40ac183e-6fe5-11e8-966a-7dd2d85bade1.png
+ :alt: 'Employee Contract Detail'
+ :width: 988
+ :align: left
+
+USA Missouri Employee added to Contract Salary Structure Menu
+
+.. image:: https://user-images.githubusercontent.com/15882954/41440928-4b3fc214-6fe5-11e8-84f4-d35b401963c4.png
+ :alt: 'Computed Pay Slip Detail'
+ :width: 988
+ :align: left
+
+New Payslip Categories for:
+
+* Missouri Income Withholding
+* Missouri Unemployment and Missouri Unemployment - Wages
+
+=======
+License
+=======
+Please see `LICENSE `_.
+Copyright Hibou Corp. 2018
diff --git a/l10n_us_mo_hr_payroll/__init__.py b/l10n_us_mo_hr_payroll/__init__.py
new file mode 100755
index 00000000..0650744f
--- /dev/null
+++ b/l10n_us_mo_hr_payroll/__init__.py
@@ -0,0 +1 @@
+from . import models
diff --git a/l10n_us_mo_hr_payroll/__manifest__.py b/l10n_us_mo_hr_payroll/__manifest__.py
new file mode 100755
index 00000000..636d550e
--- /dev/null
+++ b/l10n_us_mo_hr_payroll/__manifest__.py
@@ -0,0 +1,28 @@
+{
+ 'name': 'USA - Missouri - Payroll',
+ 'author': 'Hibou Corp. ',
+ 'license': 'AGPL-3',
+ 'category': 'Localization',
+ 'depends': ['l10n_us_hr_payroll'],
+ 'version': '12.0.2019.0.0',
+ 'description': """
+USA::Missouri Payroll Rules.
+============================
+
+* Contribution register and partner for Missouri Department of Revenue - Unemployment
+* Contribution register and partner for Missouri Department of Revenue - Income Tax Withholding
+* Contract level Missouri Exemptions and MO W-4 fields
+* Company level Missouri Unemployment Rate
+ """,
+
+ 'auto_install': False,
+ 'website': 'https://hibou.io/',
+ 'data': [
+ 'views/hr_payroll_views.xml',
+ 'data/base.xml',
+ 'data/rates.xml',
+ 'data/rules.xml',
+ 'data/final.xml',
+ ],
+ 'installable': True
+}
diff --git a/l10n_us_mo_hr_payroll/data/base.xml b/l10n_us_mo_hr_payroll/data/base.xml
new file mode 100755
index 00000000..cc4d3ff1
--- /dev/null
+++ b/l10n_us_mo_hr_payroll/data/base.xml
@@ -0,0 +1,46 @@
+
+
+
+
+
+ Missouri Department of Revenue - Unemployment Tax
+ 1
+
+
+
+ Missouri Department of Revenue - Income Tax Withholding
+ 1
+
+
+
+ Missouri Unemployment
+ Missouri Department of Revemie - Unemployment
+
+
+
+ Missouri Income Tax Withholding
+ Missouri Department of Revenue - Income Tax Withholding
+
+
+
+
+
+
+ Wage: US-MO Unemployment
+ WAGE_US_MO_UNEMP
+
+
+
+ ER: US-MO Unemployment
+ ER_US_MO_UNEMP
+
+
+
+
+ EE: US-MO Income Withholding
+ EE_US_MO_INC_WITHHOLD
+
+
+
+
+
diff --git a/l10n_us_mo_hr_payroll/data/final.xml b/l10n_us_mo_hr_payroll/data/final.xml
new file mode 100755
index 00000000..031e30cd
--- /dev/null
+++ b/l10n_us_mo_hr_payroll/data/final.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+ US_MO_EMP
+ USA Missouri Employee
+
+
+
+
+
+
+
diff --git a/l10n_us_mo_hr_payroll/data/rates.xml b/l10n_us_mo_hr_payroll/data/rates.xml
new file mode 100644
index 00000000..5c139747
--- /dev/null
+++ b/l10n_us_mo_hr_payroll/data/rates.xml
@@ -0,0 +1,19 @@
+
+
+
+
+ US Missouri Unemployment
+ US_MO_UNEMP
+ 2.511
+ 2018-01-01
+
+
+
+ US Missouri Unemployment
+ US_MO_UNEMP
+ 2.376
+ 2019-01-01
+
+
+
+
\ No newline at end of file
diff --git a/l10n_us_mo_hr_payroll/data/rules.xml b/l10n_us_mo_hr_payroll/data/rules.xml
new file mode 100755
index 00000000..7107c5ac
--- /dev/null
+++ b/l10n_us_mo_hr_payroll/data/rules.xml
@@ -0,0 +1,251 @@
+
+
+
+
+
+
+
+
+ Wage: US-MO Unemployment
+ WAGE_US_MO_UNEMP
+ python
+ result = (contract.futa_type != contract.FUTA_TYPE_BASIC)
+ code
+
+###
+year = payslip.dict.date_to.year
+rate = payslip.dict.get_rate('US_MO_UNEMP')
+ytd = payslip.sum('WAGE_US_MO_UNEMP', str(year) + '-01-01', str(year+1) + '-01-01')
+ytd += contract.external_wages
+remaining = rate.wage_limit_year - ytd
+if remaining <= 0.0:
+ result = 0
+elif remaining < categories.BASIC:
+ result = remaining
+else:
+ result = categories.BASIC
+
+
+
+
+
+
+ ER: US-MO Unemployment
+ ER_US_MO_UNEMP
+ python
+ result = (contract.futa_type != contract.FUTA_TYPE_BASIC)
+ code
+
+rate = payslip.dict.get_rate('US_MO_UNEMP')
+result_rate = -rate.rate
+result = categories.WAGE_US_MO_UNEMP
+
+# result_rate of 0 implies 100% due to bug
+if result_rate == 0.0:
+ result = 0.0
+
+
+
+
+
+
+
+
+ EE: US-MO Income Withholding
+ EE_US_MO_INC_WITHHOLD
+ python
+ result = True
+ code
+
+year = payslip.dict.date_to.year
+wages = categories.GROSS
+exemptions = contract.mo_mow4_exemptions
+filing_status = contract.mo_mow4_filing_status
+additional = contract.mo_mow4_additional_withholding
+spouse_employed = contract.mo_mow4_spouse_employed
+schedule_pay = contract.schedule_pay
+result = 0.00
+
+if year == 2018:
+ TAX = [
+ (1028.0, 1.5),
+ (1028.0, 2.0),
+ (1028.0, 2.5),
+ (1028.0, 3.0),
+ (1028.0, 3.5),
+ (1028.0, 4.0),
+ (1028.0, 4.5),
+ (1028.0, 5.0),
+ (1028.0, 5.5),
+ (999999999.0, 5.9),
+ ]
+ if filing_status:
+ PP = 0
+ if 'weekly' == schedule_pay:
+ PP = 52
+ elif 'bi-weekly' == schedule_pay:
+ PP = 26
+ elif 'semi-monthly' == schedule_pay:
+ PP = 24
+ elif 'monthly' == schedule_pay:
+ PP = 12
+ elif 'quarterly' == schedule_pay:
+ PP = 4
+ elif 'semi-annually' == schedule_pay:
+ PP = 2
+ elif 'annually' == schedule_pay:
+ PP = 1
+ else:
+ raise Exception('Invalid schedule_pay="' + schedule_pay + '" for MO Income Withholding calculation')
+
+ gross_salary = PP * wages
+ deduction = -1
+ if filing_status == 'single' or (filing_status == 'married' and spouse_employed):
+ deduction = 6500.0
+ elif filing_status == 'married' and not spouse_employed:
+ deduction = 13000.0
+ else:
+ deduction = 9550.0
+
+ if deduction < 0:
+ raise UserError('Invalid deduction calculation')
+
+ allowances = 0
+ if filing_status == 'single' or (filing_status == 'married' and spouse_employed):
+ if exemptions == 1:
+ allowances = 2100.0
+ elif exemptions > 1:
+ allowances = 2100.0 + ((exemptions - 1) * 1200.0)
+ elif filing_status == 'married' and not spouse_employed:
+ if exemptions > 0 and exemptions < 3:
+ allowances = 2100.0 * exemptions
+ elif exemptions >= 3:
+ allowances = 2100.0 + 2100.0 + ((exemptions - 2) * 1200.0)
+ else:
+ if exemptions == 1:
+ allowances = 3500.0
+ elif exemptions > 1:
+ allowances = 3500.0 + ((exemptions - 1) * 1200.0)
+
+ federal = categories.EE_US_FED_INC_WITHHOLD * -1 * PP
+ if filing_status == 'married' and not spouse_employed:
+ federal = min(10000.0, federal)
+ else:
+ federal = min(5000.0, federal)
+
+ mo_taxable_income = gross_salary - deduction - allowances - federal
+
+ remaining_taxable_income = mo_taxable_income
+ tax = 0.0
+ for amt, rate in TAX:
+ rate = rate / 100.0
+ remaining_taxable_income = remaining_taxable_income - amt
+ if remaining_taxable_income > 0.0 or remaining_taxable_income == 0.0:
+ tax += rate * amt
+ else:
+ tax += rate * (remaining_taxable_income + amt)
+ break
+ tax = -tax
+ tax = tax / PP
+
+ # Small GROSS results in an underflow
+ if tax > 0.0:
+ tax = 0.0
+
+ if additional:
+ tax -= additional
+
+ result = round(tax)
+else:
+ if year == 2019:
+ # There are no longer allowances in the Missouri Withholding tables.
+ exemptions = 0
+
+ TAX = [
+ (1053.0, 1.5),
+ (1053.0, 2.0),
+ (1053.0, 2.5),
+ (1053.0, 3.0),
+ (1053.0, 3.5),
+ (1053.0, 4.0),
+ (1053.0, 4.5),
+ (1053.0, 5.0),
+ (999999999.0, 5.4),
+ ]
+ if filing_status:
+ PP = 0
+ if 'weekly' == schedule_pay:
+ PP = 52
+ elif 'bi-weekly' == schedule_pay:
+ PP = 26
+ elif 'semi-monthly' == schedule_pay:
+ PP = 24
+ elif 'monthly' == schedule_pay:
+ PP = 12
+ elif 'quarterly' == schedule_pay:
+ PP = 4
+ elif 'semi-annually' == schedule_pay:
+ PP = 2
+ elif 'annually' == schedule_pay:
+ PP = 1
+ else:
+ raise Exception('Invalid schedule_pay="' + schedule_pay + '" for MO Income Withholding calculation')
+
+ gross_salary = PP * wages
+ deduction = -1
+ if filing_status == 'single' or (filing_status == 'married' and spouse_employed):
+ deduction = 12200.0 / 2.0
+ elif filing_status == 'married' and not spouse_employed:
+ deduction = 24400.0 / 2.0
+ else:
+ deduction = 18350.0 / 2.0
+
+ if deduction < 0:
+ raise UserError('Invalid deduction calculation')
+
+ allowances = 0
+ if filing_status == 'single' or (filing_status == 'married' and spouse_employed):
+ if exemptions == 1:
+ allowances = 2100.0
+ elif exemptions > 1:
+ allowances = 2100.0 + ((exemptions - 1) * 1200.0)
+ elif filing_status == 'married' and not spouse_employed:
+ if exemptions > 0 and exemptions < 3:
+ allowances = 2100.0 * exemptions
+ elif exemptions >= 3:
+ allowances = 2100.0 + 2100.0 + ((exemptions - 2) * 1200.0)
+ else:
+ if exemptions == 1:
+ allowances = 3500.0
+ elif exemptions > 1:
+ allowances = 3500.0 + ((exemptions - 1) * 1200.0)
+
+ mo_taxable_income = gross_salary - deduction - allowances
+
+ remaining_taxable_income = mo_taxable_income
+ tax = 0.0
+ for amt, rate in TAX:
+ rate = rate / 100.0
+ remaining_taxable_income = remaining_taxable_income - amt
+ if remaining_taxable_income > 0.0 or remaining_taxable_income == 0.0:
+ tax += rate * amt
+ else:
+ tax += rate * (remaining_taxable_income + amt)
+ break
+ tax = -tax
+ tax = tax / PP
+
+ # Small GROSS results in an underflow
+ if tax > 0.0:
+ tax = 0.0
+
+ if additional:
+ tax -= additional
+
+ result = round(tax)
+
+
+
+
+
+
diff --git a/l10n_us_mo_hr_payroll/models/__init__.py b/l10n_us_mo_hr_payroll/models/__init__.py
new file mode 100644
index 00000000..e99aa24a
--- /dev/null
+++ b/l10n_us_mo_hr_payroll/models/__init__.py
@@ -0,0 +1 @@
+from . import hr_payroll
diff --git a/l10n_us_mo_hr_payroll/models/hr_payroll.py b/l10n_us_mo_hr_payroll/models/hr_payroll.py
new file mode 100755
index 00000000..626c9bc2
--- /dev/null
+++ b/l10n_us_mo_hr_payroll/models/hr_payroll.py
@@ -0,0 +1,17 @@
+from odoo import models, fields, api
+
+
+class USMOHrContract(models.Model):
+ _inherit = 'hr.contract'
+
+ mo_mow4_filing_status = fields.Selection([
+ ('', 'Exempt'),
+ ('single', 'Single'),
+ ('married', 'Married'),
+ ('head_of_household', 'Head of Household'),
+ ], string='Federal W4 Filing Status', default='single')
+ mo_mow4_spouse_employed = fields.Boolean(string='Missouri W-4 Spouse Employed', default=False)
+ mo_mow4_exemptions = fields.Integer(string='Missouri W-4 Exemptions', default=0,
+ help="As of 2019, there are no longer allowances"
+ " in the Missouri Withholding tables.")
+ mo_mow4_additional_withholding = fields.Float(string="Missouri W-4 Additional Withholding", default=0.0)
diff --git a/l10n_us_mo_hr_payroll/tests/__init__.py b/l10n_us_mo_hr_payroll/tests/__init__.py
new file mode 100755
index 00000000..033cf841
--- /dev/null
+++ b/l10n_us_mo_hr_payroll/tests/__init__.py
@@ -0,0 +1,2 @@
+from . import test_us_mo_payslip_2018
+from . import test_us_mo_payslip_2019
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
new file mode 100755
index 00000000..5ba3bf66
--- /dev/null
+++ b/l10n_us_mo_hr_payroll/tests/test_us_mo_payslip_2018.py
@@ -0,0 +1,218 @@
+from odoo.addons.l10n_us_hr_payroll.tests.test_us_payslip import TestUsPayslip, process_payslip
+
+
+class TestUsMoPayslip(TestUsPayslip):
+ # Calculations from http://dor.mo.gov/forms/4282_2018.pdf
+ SALARY = 5000.0
+ MO_ALLOWANCES = 3 # Different calculated amounts for different filing statuses
+ MO_UNEMP = -2.511 / 100.0
+
+ TAX = [
+ (1028.0, 1.5),
+ (1028.0, 2.0),
+ (1028.0, 2.5),
+ (1028.0, 3.0),
+ (1028.0, 3.5),
+ (1028.0, 4.0),
+ (1028.0, 4.5),
+ (1028.0, 5.0),
+ (1028.0, 5.5),
+ (999999999.0, 5.9),
+ ]
+
+ def test_2018_taxes_single(self):
+ # Payroll Period Monthly
+ salary = self.SALARY
+ pp = 12.0
+ gross_salary = salary * pp
+ spouse_employed = False
+
+ # Single
+ standard_deduction = 6500.0
+ mo_allowance_calculated = 2100.0 + 1200.0 + 1200.0
+
+ employee = self._createEmployee()
+ contract = self._createContract(employee, salary, struct_id=self.ref('l10n_us_mo_hr_payroll.hr_payroll_salary_structure_us_mo_employee'))
+ contract.mo_mow4_spouse_employed = spouse_employed
+ contract.mo_mow4_filing_status = 'single'
+ contract.mo_mow4_exemptions = 3
+ contract.mo_mow4_additional_withholding = 0.0
+
+ self._log('2018 Missouri tax single first payslip:')
+ payslip = self._createPayslip(employee, '2018-01-01', '2018-01-31')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['WAGE_US_MO_UNEMP'], salary)
+ self.assertPayrollEqual(cats['ER_US_MO_UNEMP'], cats['WAGE_US_MO_UNEMP'] * self.MO_UNEMP)
+
+ US_WITHHOLDING = cats['EE_US_FED_INC_WITHHOLD']
+ # -693.86
+ self._log(US_WITHHOLDING)
+ us_withholding = -US_WITHHOLDING * pp
+
+ # 5000 for single and married with spouse working, 10000 for married spouse not working
+ us_withholding = min(5000, us_withholding)
+ # 5000
+ self._log(us_withholding)
+
+ mo_taxable_income = gross_salary - standard_deduction - mo_allowance_calculated - us_withholding
+ # 44000.0
+ self._log('%s = %s - %s - %s - %s' % (mo_taxable_income, gross_salary, standard_deduction, mo_allowance_calculated, us_withholding))
+
+ remaining_taxable_income = mo_taxable_income
+ tax = 0.0
+ for amt, rate in self.TAX:
+ rate = rate / 100.0
+ self._log(str(amt) + ' : ' + str(rate) + ' : ' + str(remaining_taxable_income))
+ remaining_taxable_income = remaining_taxable_income - amt
+ if remaining_taxable_income > 0.0 or remaining_taxable_income == 0.0:
+ tax += rate * amt
+ else:
+ tax += rate * (remaining_taxable_income + amt)
+ break
+ tax = -tax
+ self._log('Computed annual tax: ' + str(tax))
+
+ tax = tax / pp
+ tax = round(tax)
+ self._log('Computed period tax: ' + str(tax))
+ self.assertPayrollEqual(cats['EE_US_MO_INC_WITHHOLD'], tax)
+
+ def test_2018_spouse_not_employed(self):
+ # Payroll Period Semi-monthly
+ salary = self.SALARY
+ pp = 24.0
+ gross_salary = salary * pp
+ spouse_employed = False
+
+ # Single
+ standard_deduction = 13000.0
+ mo_allowance_calculated = 2100.0 + 2100.0 + 1200.0
+
+ employee = self._createEmployee()
+
+ contract = self._createContract(employee, salary,
+ struct_id=self.ref('l10n_us_mo_hr_payroll.hr_payroll_salary_structure_us_mo_employee'),
+ schedule_pay='semi-monthly')
+ contract.mo_mow4_spouse_employed = spouse_employed
+ contract.mo_mow4_filing_status = 'married'
+ contract.mo_mow4_exemptions = 3
+ contract.mo_mow4_additional_withholding = 0.0
+
+ self._log('2018 Missouri tax first payslip:')
+ payslip = self._createPayslip(employee, '2018-01-01', '2018-01-31')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+
+ US_WITHHOLDING = cats['EE_US_FED_INC_WITHHOLD']
+ # -693.86
+ self._log(US_WITHHOLDING)
+ us_withholding = -US_WITHHOLDING * pp
+
+ # 5000 for single and married with spouse working, 10000 for married spouse not working
+ us_withholding = min(10000, us_withholding)
+
+
+ mo_taxable_income = gross_salary - standard_deduction - mo_allowance_calculated - us_withholding
+ # 48306.14
+ self._log(mo_taxable_income)
+
+ remaining_taxable_income = mo_taxable_income
+ tax = 0.0
+ for amt, rate in self.TAX:
+ rate = rate / 100.0
+ self._log(str(amt) + ' : ' + str(rate) + ' : ' + str(remaining_taxable_income))
+ remaining_taxable_income = remaining_taxable_income - amt
+ if remaining_taxable_income > 0.0 or remaining_taxable_income == 0.0:
+ tax += rate * amt
+ else:
+ tax += rate * (remaining_taxable_income + amt)
+ break
+ tax = -tax
+ self._log('Computed annual tax: ' + str(tax))
+
+ tax = tax / pp
+ tax = round(tax)
+ self._log('Computed period tax: ' + str(tax))
+ self.assertPayrollEqual(cats['EE_US_MO_INC_WITHHOLD'], tax)
+
+ def test_2018_head_of_household(self):
+ # Payroll Period Weekly
+ salary = self.SALARY
+
+ # Payroll Period Weekly
+ salary = self.SALARY
+ pp = 52.0
+ gross_salary = salary * pp
+ spouse_employed = False
+
+ # Single HoH
+ standard_deduction = 9550.0
+ mo_allowance_calculated = 3500.0 + 1200.0 + 1200.0
+
+ employee = self._createEmployee()
+
+ contract = self._createContract(employee, salary,
+ struct_id=self.ref('l10n_us_mo_hr_payroll.hr_payroll_salary_structure_us_mo_employee'),
+ schedule_pay='weekly')
+ contract.mo_mow4_spouse_employed = spouse_employed
+ contract.mo_mow4_filing_status = 'head_of_household'
+ contract.mo_mow4_exemptions = 3
+ contract.mo_mow4_additional_withholding = 0.0
+
+ self._log('2018 Missouri tax first payslip:')
+ payslip = self._createPayslip(employee, '2018-01-01', '2018-01-31')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+
+ US_WITHHOLDING = cats['EE_US_FED_INC_WITHHOLD']
+ self._log(US_WITHHOLDING)
+ us_withholding = -US_WITHHOLDING * pp
+
+ # 5000 for single and married with spouse working, 10000 for married spouse not working
+ us_withholding = min(5000, us_withholding)
+
+
+ mo_taxable_income = gross_salary - standard_deduction - mo_allowance_calculated - us_withholding
+ self._log(mo_taxable_income)
+
+ remaining_taxable_income = mo_taxable_income
+ tax = 0.0
+ for amt, rate in self.TAX:
+ rate = rate / 100.0
+ self._log(str(amt) + ' : ' + str(rate) + ' : ' + str(remaining_taxable_income))
+ remaining_taxable_income = remaining_taxable_income - amt
+ if remaining_taxable_income > 0.0 or remaining_taxable_income == 0.0:
+ tax += rate * amt
+ else:
+ tax += rate * (remaining_taxable_income + amt)
+ break
+ tax = -tax
+ self._log('Computed annual tax: ' + str(tax))
+
+ tax = tax / pp
+ tax = round(tax)
+ self._log('Computed period tax: ' + str(tax))
+ self.assertPayrollEqual(cats['EE_US_MO_INC_WITHHOLD'], tax)
+
+ def test_2018_underflow(self):
+ # Payroll Period Weekly
+ salary = 200.0
+
+ employee = self._createEmployee()
+
+ contract = self._createContract(employee, salary,
+ struct_id=self.ref('l10n_us_mo_hr_payroll.hr_payroll_salary_structure_us_mo_employee'))
+
+ payslip = self._createPayslip(employee, '2018-01-01', '2018-01-31')
+ payslip.compute_sheet()
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['EE_US_MO_INC_WITHHOLD'], 0.0)
diff --git a/l10n_us_mo_hr_payroll/tests/test_us_mo_payslip_2019.py b/l10n_us_mo_hr_payroll/tests/test_us_mo_payslip_2019.py
new file mode 100755
index 00000000..9d3cd1f0
--- /dev/null
+++ b/l10n_us_mo_hr_payroll/tests/test_us_mo_payslip_2019.py
@@ -0,0 +1,188 @@
+from odoo.addons.l10n_us_hr_payroll.tests.test_us_payslip import TestUsPayslip, process_payslip
+
+
+class TestUsMoPayslip(TestUsPayslip):
+ # Calculations from http://dor.mo.gov/forms/4282_2019.pdf
+ SALARY = 5000.0
+ MO_ALLOWANCES = 3 # Different calculated amounts for different filing statuses
+ MO_UNEMP = -2.376 / 100.0
+
+ TAX = [
+ (1053.0, 1.5),
+ (1053.0, 2.0),
+ (1053.0, 2.5),
+ (1053.0, 3.0),
+ (1053.0, 3.5),
+ (1053.0, 4.0),
+ (1053.0, 4.5),
+ (1053.0, 5.0),
+ (999999999.0, 5.4),
+ ]
+
+ def test_2019_taxes_single(self):
+ # Payroll Period Monthly
+ salary = self.SALARY
+ pp = 12.0
+ gross_salary = salary * pp
+ spouse_employed = False
+
+ # Single
+ standard_deduction = 6100.0
+ mo_allowance_calculated = 0.0
+
+ employee = self._createEmployee()
+ contract = self._createContract(employee, salary, struct_id=self.ref('l10n_us_mo_hr_payroll.hr_payroll_salary_structure_us_mo_employee'))
+ contract.mo_mow4_spouse_employed = spouse_employed
+ contract.mo_mow4_filing_status = 'single'
+ contract.mo_mow4_exemptions = 3
+ contract.mo_mow4_additional_withholding = 0.0
+
+ self._log('2019 Missouri tax single first payslip:')
+ payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['WAGE_US_MO_UNEMP'], salary)
+ self.assertPayrollEqual(cats['ER_US_MO_UNEMP'], cats['WAGE_US_MO_UNEMP'] * self.MO_UNEMP)
+
+ mo_taxable_income = gross_salary - standard_deduction - mo_allowance_calculated
+ self._log('%s = %s - %s - %s' % (mo_taxable_income, gross_salary, standard_deduction, mo_allowance_calculated))
+
+ remaining_taxable_income = mo_taxable_income
+ tax = 0.0
+ for amt, rate in self.TAX:
+ rate = rate / 100.0
+ self._log(str(amt) + ' : ' + str(rate) + ' : ' + str(remaining_taxable_income))
+ remaining_taxable_income = remaining_taxable_income - amt
+ if remaining_taxable_income > 0.0 or remaining_taxable_income == 0.0:
+ tax += rate * amt
+ else:
+ tax += rate * (remaining_taxable_income + amt)
+ break
+ tax = -tax
+ self._log('Computed annual tax: ' + str(tax))
+
+ tax = tax / pp
+ tax = round(tax)
+ self._log('Computed period tax: ' + str(tax))
+ self.assertPayrollEqual(cats['EE_US_MO_INC_WITHHOLD'], tax)
+
+ def test_2019_spouse_not_employed(self):
+ # Payroll Period Semi-monthly
+ salary = self.SALARY
+ pp = 24.0
+ gross_salary = salary * pp
+ spouse_employed = False
+
+ # Single
+ standard_deduction = 12200.0
+ mo_allowance_calculated = 0.0
+
+ employee = self._createEmployee()
+
+ contract = self._createContract(employee, salary,
+ struct_id=self.ref('l10n_us_mo_hr_payroll.hr_payroll_salary_structure_us_mo_employee'),
+ schedule_pay='semi-monthly')
+ contract.mo_mow4_spouse_employed = spouse_employed
+ contract.mo_mow4_filing_status = 'married'
+ contract.mo_mow4_exemptions = 3
+ contract.mo_mow4_additional_withholding = 0.0
+
+ self._log('2019 Missouri tax first payslip:')
+ payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+
+ mo_taxable_income = gross_salary - standard_deduction - mo_allowance_calculated
+ self._log(mo_taxable_income)
+
+ remaining_taxable_income = mo_taxable_income
+ tax = 0.0
+ for amt, rate in self.TAX:
+ rate = rate / 100.0
+ self._log(str(amt) + ' : ' + str(rate) + ' : ' + str(remaining_taxable_income))
+ remaining_taxable_income = remaining_taxable_income - amt
+ if remaining_taxable_income > 0.0 or remaining_taxable_income == 0.0:
+ tax += rate * amt
+ else:
+ tax += rate * (remaining_taxable_income + amt)
+ break
+ tax = -tax
+ self._log('Computed annual tax: ' + str(tax))
+
+ tax = tax / pp
+ tax = round(tax)
+ self._log('Computed period tax: ' + str(tax))
+ self.assertPayrollEqual(cats['EE_US_MO_INC_WITHHOLD'], tax)
+
+ def test_2019_head_of_household(self):
+ # Payroll Period Weekly
+ salary = self.SALARY
+
+ # Payroll Period Weekly
+ salary = self.SALARY
+ pp = 52.0
+ gross_salary = salary * pp
+ spouse_employed = False
+
+ # Single HoH
+ standard_deduction = 18350.0 / 2.0
+ mo_allowance_calculated = 0.0 # 2019 Allowances are no longer in the Missouri Withholding tables.
+
+ employee = self._createEmployee()
+
+ contract = self._createContract(employee, salary,
+ struct_id=self.ref('l10n_us_mo_hr_payroll.hr_payroll_salary_structure_us_mo_employee'),
+ schedule_pay='weekly')
+ contract.mo_mow4_spouse_employed = spouse_employed
+ contract.mo_mow4_filing_status = 'head_of_household'
+ contract.mo_mow4_exemptions = 3
+ contract.mo_mow4_additional_withholding = 0.0
+
+ self._log('2019 Missouri tax first payslip:')
+ payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+
+ mo_taxable_income = gross_salary - standard_deduction - mo_allowance_calculated
+ self._log(mo_taxable_income)
+
+ remaining_taxable_income = mo_taxable_income
+ tax = 0.0
+ for amt, rate in self.TAX:
+ rate = rate / 100.0
+ self._log(str(amt) + ' : ' + str(rate) + ' : ' + str(remaining_taxable_income))
+ remaining_taxable_income = remaining_taxable_income - amt
+ if remaining_taxable_income > 0.0 or remaining_taxable_income == 0.0:
+ tax += rate * amt
+ else:
+ tax += rate * (remaining_taxable_income + amt)
+ break
+ tax = -tax
+ self._log('Computed annual tax: ' + str(tax))
+
+ tax = tax / pp
+ tax = round(tax)
+ self._log('Computed period tax: ' + str(tax))
+ self.assertPayrollEqual(cats['EE_US_MO_INC_WITHHOLD'], tax)
+
+ def test_2019_underflow(self):
+ # Payroll Period Weekly
+ salary = 200.0
+
+ employee = self._createEmployee()
+
+ contract = self._createContract(employee, salary,
+ struct_id=self.ref('l10n_us_mo_hr_payroll.hr_payroll_salary_structure_us_mo_employee'))
+
+ payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
+ payslip.compute_sheet()
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['EE_US_MO_INC_WITHHOLD'], 0.0)
diff --git a/l10n_us_mo_hr_payroll/views/hr_payroll_views.xml b/l10n_us_mo_hr_payroll/views/hr_payroll_views.xml
new file mode 100755
index 00000000..8db3b723
--- /dev/null
+++ b/l10n_us_mo_hr_payroll/views/hr_payroll_views.xml
@@ -0,0 +1,23 @@
+
+
+
+
+ hr.contract.form.inherit
+ hr.contract
+ 138
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+