diff --git a/l10n_us_ms_hr_payroll/README.rst b/l10n_us_ms_hr_payroll/README.rst
new file mode 100644
index 00000000..e6355ec9
--- /dev/null
+++ b/l10n_us_ms_hr_payroll/README.rst
@@ -0,0 +1,24 @@
+**************************************
+Hibou - US Payroll - Mississippi State
+**************************************
+
+Calculations and contribution registers for Mississippi State Payroll.
+
+For more information and add-ons, visit `Hibou.io `_.
+
+=============
+Main Features
+=============
+
+* New Partners and Contribution Registers for:
+ * Mississippi Department of Revenue
+ * Mississippi Department of Employment Security
+* Contract level Mississippi Exemptions and 89-350 fields
+* Payroll Rate for Mississippi Unemployment Rate
+
+
+=======
+License
+=======
+Please see `LICENSE `_.
+Copyright Hibou Corp. 2019
diff --git a/l10n_us_ms_hr_payroll/__init__.py b/l10n_us_ms_hr_payroll/__init__.py
new file mode 100755
index 00000000..0650744f
--- /dev/null
+++ b/l10n_us_ms_hr_payroll/__init__.py
@@ -0,0 +1 @@
+from . import models
diff --git a/l10n_us_ms_hr_payroll/__manifest__.py b/l10n_us_ms_hr_payroll/__manifest__.py
new file mode 100755
index 00000000..c5e3085c
--- /dev/null
+++ b/l10n_us_ms_hr_payroll/__manifest__.py
@@ -0,0 +1,28 @@
+{
+ 'name': 'USA - Mississippi - Payroll',
+ 'author': 'Hibou Corp. ',
+ 'license': 'AGPL-3',
+ 'category': 'Localization',
+ 'depends': ['l10n_us_hr_payroll'],
+ 'version': '11.0.2019.0.0',
+ 'description': """
+USA::Mississippi Payroll Rules.
+===============================
+
+* New Partners and Contribution Registers for:
+ * Mississippi Department of Revenue
+ * Mississippi Department of Employment Security
+* Contract level Mississippi Exemptions and 89-350 fields
+* Payroll Rate for Mississippi 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_ms_hr_payroll/data/base.xml b/l10n_us_ms_hr_payroll/data/base.xml
new file mode 100755
index 00000000..8cc26c9c
--- /dev/null
+++ b/l10n_us_ms_hr_payroll/data/base.xml
@@ -0,0 +1,44 @@
+
+
+
+
+ Mississippi Department of Employment Security
+ 1
+
+
+
+ Mississippi Department of Revenue
+ 1
+
+
+
+ Mississippi Unemployment
+ Mississippi Department of Employment Security
+
+
+
+ Mississippi Income Tax Withholding
+ Mississippi Department of Revenue - Income Tax Withholding
+
+
+
+
+
+
+ Wage: US-MS Unemployment
+ WAGE_US_MS_UNEMP
+
+
+
+ ER: US-MS Unemployment
+ ER_US_MS_UNEMP
+
+
+
+
+ EE: US-MS Income Withholding
+ EE_US_MS_INC_WITHHOLD
+
+
+
+
diff --git a/l10n_us_ms_hr_payroll/data/final.xml b/l10n_us_ms_hr_payroll/data/final.xml
new file mode 100755
index 00000000..63d9665d
--- /dev/null
+++ b/l10n_us_ms_hr_payroll/data/final.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+ US_MS_EMP
+ USA Mississippi Employee
+
+
+
+
+
+
diff --git a/l10n_us_ms_hr_payroll/data/rates.xml b/l10n_us_ms_hr_payroll/data/rates.xml
new file mode 100644
index 00000000..14a237da
--- /dev/null
+++ b/l10n_us_ms_hr_payroll/data/rates.xml
@@ -0,0 +1,12 @@
+
+
+
+
+ US Mississippi Unemployment
+ US_MS_UNEMP
+ 1.2
+ 2019-01-01
+
+
+
+
\ No newline at end of file
diff --git a/l10n_us_ms_hr_payroll/data/rules.xml b/l10n_us_ms_hr_payroll/data/rules.xml
new file mode 100755
index 00000000..8a4dccf2
--- /dev/null
+++ b/l10n_us_ms_hr_payroll/data/rules.xml
@@ -0,0 +1,123 @@
+
+
+
+
+
+
+
+ Wage: US-MS Unemployment
+ WAGE_US_MS_UNEMP
+ python
+ result = (contract.futa_type != contract.FUTA_TYPE_BASIC)
+ code
+
+###
+year = int(payslip.dict.date_to[:4])
+rate = payslip.dict.get_rate('US_MS_UNEMP')
+ytd = payslip.sum('WAGE_US_MS_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-MS Unemployment
+ ER_US_MS_UNEMP
+ python
+ result = (contract.futa_type != contract.FUTA_TYPE_BASIC)
+ code
+
+rate = payslip.dict.get_rate('US_MS_UNEMP')
+result_rate = -rate.rate
+result = categories.WAGE_US_MS_UNEMP
+
+# result_rate of 0 implies 100% due to bug
+if result_rate == 0.0:
+ result = 0.0
+
+
+
+
+
+
+
+
+ EE: US-MS Income Withholding
+ EE_US_MS_INC_WITHHOLD
+ python
+ result = contract.ms_89_350_filing_status
+ code
+
+year = int(payslip.dict.date_to[:4])
+filing_status = contract.ms_89_350_filing_status
+schedule_pay = contract.schedule_pay
+PPAY = categories.GROSS
+EX = contract.ms_89_350_exemption
+additional = contract.ms_89_350_additional_withholding
+
+STDED = 0.0
+
+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')
+
+AGP = PPAY * PP
+
+if year == 2019 or True:
+ if filing_status == 'single':
+ STDED = 2300.0
+ elif filing_status == 'head_of_household':
+ STDED = 3400.0
+ elif filing_status == 'married_dual':
+ STDED = 2300.0
+ elif filing_status == 'married':
+ STDED = 4600.0
+
+ bracket = [
+ (10000.0, 0.05, 290.0),
+ (5000.0, 0.04, 90.0),
+ (2000.0, 0.03, 0.0),
+ ]
+
+
+TI = AGP - (EX + STDED)
+if TI <= 0.01:
+ result = 0.0
+else:
+ for data in bracket:
+ if TI >= data[0]:
+ TAX = ((TI - data[0]) * data[1]) + data[2]
+ break
+ else:
+ TAX = 0.0
+
+ result = round(TAX / PP)
+
+result += additional
+
+
+
+
+
diff --git a/l10n_us_ms_hr_payroll/models/__init__.py b/l10n_us_ms_hr_payroll/models/__init__.py
new file mode 100644
index 00000000..e99aa24a
--- /dev/null
+++ b/l10n_us_ms_hr_payroll/models/__init__.py
@@ -0,0 +1 @@
+from . import hr_payroll
diff --git a/l10n_us_ms_hr_payroll/models/hr_payroll.py b/l10n_us_ms_hr_payroll/models/hr_payroll.py
new file mode 100755
index 00000000..8782d346
--- /dev/null
+++ b/l10n_us_ms_hr_payroll/models/hr_payroll.py
@@ -0,0 +1,18 @@
+from odoo import models, fields
+
+
+class USMSHrContract(models.Model):
+ _inherit = 'hr.contract'
+
+ ms_89_350_filing_status = fields.Selection([
+ ('', 'Exempt (e.g. Box 8)'),
+ ('single', 'Single'),
+ ('married', 'Married (spouse NOT employed)'),
+ ('married_dual', 'Married (spouse IS employed)'),
+ ('head_of_household', 'Head of Household'),
+ ], string='Mississippi 89-350 Filing Status', default='single')
+
+ ms_89_350_exemption = fields.Float(string='Mississippi 89-350 Exemptions', default=0.0,
+ help='Box 6')
+ ms_89_350_additional_withholding = fields.Float(string="Mississippi 89-350 Additional Withholding", default=0.0,
+ help='Box 7')
diff --git a/l10n_us_ms_hr_payroll/tests/__init__.py b/l10n_us_ms_hr_payroll/tests/__init__.py
new file mode 100755
index 00000000..a617d593
--- /dev/null
+++ b/l10n_us_ms_hr_payroll/tests/__init__.py
@@ -0,0 +1 @@
+from . import test_us_ms_payslip_2019
diff --git a/l10n_us_ms_hr_payroll/tests/test_us_ms_payslip_2019.py b/l10n_us_ms_hr_payroll/tests/test_us_ms_payslip_2019.py
new file mode 100755
index 00000000..e738f8b2
--- /dev/null
+++ b/l10n_us_ms_hr_payroll/tests/test_us_ms_payslip_2019.py
@@ -0,0 +1,86 @@
+from odoo.addons.l10n_us_hr_payroll.tests.test_us_payslip import TestUsPayslip, process_payslip
+
+
+class TestUsMsPayslip(TestUsPayslip):
+ # Calculations from https://www.dor.ms.gov/Documents/Computer%20Payroll%20Accounting%201-2-19.pdf
+ MS_UNEMP = -1.2 / 100.0
+
+ def test_2019_taxes_one(self):
+ salary = 1250.0
+ ms_89_350_exemption = 11000.0
+
+ employee = self._createEmployee()
+ contract = self._createContract(employee, salary, struct_id=self.ref('l10n_us_ms_hr_payroll.hr_payroll_salary_structure_us_ms_employee'),
+ schedule_pay='semi-monthly')
+ contract.ms_89_350_filing_status = 'head_of_household'
+ contract.ms_89_350_exemption = ms_89_350_exemption
+
+ self._log('2019 Mississippi 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_MS_UNEMP'], salary)
+ self.assertPayrollEqual(cats['ER_US_MS_UNEMP'], cats['WAGE_US_MS_UNEMP'] * self.MS_UNEMP)
+
+ STDED = 3400.0 # Head of Household
+ AGP = salary * 24 # Semi-Monthly
+ TI = AGP - (ms_89_350_exemption + STDED)
+ self.assertPayrollEqual(TI, 15600.0)
+ TAX = ((TI - 10000) * 0.05) + 290 # Over 10,000
+ self.assertPayrollEqual(TAX, 570.0)
+
+ ms_withhold = round(TAX / 24) # Semi-Monthly
+ self.assertPayrollEqual(cats['EE_US_MS_INC_WITHHOLD'], ms_withhold)
+
+ def test_2019_taxes_one_exempt(self):
+ salary = 1250.0
+ ms_89_350_exemption = 11000.0
+
+ employee = self._createEmployee()
+ contract = self._createContract(employee, salary, struct_id=self.ref('l10n_us_ms_hr_payroll.hr_payroll_salary_structure_us_ms_employee'),
+ schedule_pay='semi-monthly')
+ contract.ms_89_350_filing_status = ''
+ contract.ms_89_350_exemption = ms_89_350_exemption
+
+ self._log('2019 Mississippi tax single first payslip:')
+ payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+ self.assertPayrollEqual(cats.get('EE_US_MS_INC_WITHHOLD', 0.0), 0.0)
+
+ def test_2019_taxes_additional(self):
+ salary = 1250.0
+ ms_89_350_exemption = 11000.0
+ additional = 40.0
+
+ employee = self._createEmployee()
+ contract = self._createContract(employee, salary, struct_id=self.ref('l10n_us_ms_hr_payroll.hr_payroll_salary_structure_us_ms_employee'),
+ schedule_pay='semi-monthly')
+ contract.ms_89_350_filing_status = 'head_of_household'
+ contract.ms_89_350_exemption = ms_89_350_exemption
+ contract.ms_89_350_additional_withholding = additional
+
+ self._log('2019 Mississippi 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_MS_UNEMP'], salary)
+ self.assertPayrollEqual(cats['ER_US_MS_UNEMP'], cats['WAGE_US_MS_UNEMP'] * self.MS_UNEMP)
+
+ STDED = 3400.0 # Head of Household
+ AGP = salary * 24 # Semi-Monthly
+ TI = AGP - (ms_89_350_exemption + STDED)
+ self.assertPayrollEqual(TI, 15600.0)
+ TAX = ((TI - 10000) * 0.05) + 290 # Over 10,000
+ self.assertPayrollEqual(TAX, 570.0)
+
+ ms_withhold = round(TAX / 24) # Semi-Monthly
+ self.assertPayrollEqual(cats['EE_US_MS_INC_WITHHOLD'], ms_withhold + additional)
diff --git a/l10n_us_ms_hr_payroll/views/hr_payroll_views.xml b/l10n_us_ms_hr_payroll/views/hr_payroll_views.xml
new file mode 100755
index 00000000..2fe8cf3e
--- /dev/null
+++ b/l10n_us_ms_hr_payroll/views/hr_payroll_views.xml
@@ -0,0 +1,20 @@
+
+
+
+ hr.contract.form.inherit
+ hr.contract
+ 139
+
+
+
+
+
+
+
+
+
+
+
+
+
+