diff --git a/l10n_us_nc_hr_payroll/README.rst b/l10n_us_nc_hr_payroll/README.rst
new file mode 100644
index 00000000..fad4ccf4
--- /dev/null
+++ b/l10n_us_nc_hr_payroll/README.rst
@@ -0,0 +1,41 @@
+*****************************************
+Hibou - US Payroll - North Carolina State
+*****************************************
+
+Calculations and contribution registers for North Carolina State Payroll.
+
+For more information and add-ons, visit `Hibou.io `_.
+
+=============
+Main Features
+=============
+
+* Contribution registers and partners for:
+ * North Carolina Department of Revenue - Income Tax withholding
+ * North Carolina Department of Taxation - Unemployment
+* Contract level North Carolina Exemptions
+* Company level North Carolina Unemployment Rate
+
+.. image:: https://user-images.githubusercontent.com/15882954/41436210-cb7f6a52-6fd5-11e8-9095-48799360f4e9.png
+ :alt: 'Employee Contract Detail'
+ :width: 988
+ :align: left
+
+USA North Carolina Employee added to Contract Salary Structure menu
+
+.. image:: https://user-images.githubusercontent.com/15882954/41436282-fcfd3096-6fd5-11e8-8bc8-3c4985908b23.png
+ :alt: 'Computed Pay Slip Detail'
+ :width: 988
+ :align: left
+
+New Payslip Categories for:
+
+* North Carolina Income Withholding
+* North Carolina Unemployment - Wages
+* North Carolina Unemployment.
+
+=======
+License
+=======
+Please see `LICENSE `_.
+Copyright Hibou Corp. 2018
diff --git a/l10n_us_nc_hr_payroll/__init__.py b/l10n_us_nc_hr_payroll/__init__.py
new file mode 100755
index 00000000..0650744f
--- /dev/null
+++ b/l10n_us_nc_hr_payroll/__init__.py
@@ -0,0 +1 @@
+from . import models
diff --git a/l10n_us_nc_hr_payroll/__manifest__.py b/l10n_us_nc_hr_payroll/__manifest__.py
new file mode 100755
index 00000000..4960939c
--- /dev/null
+++ b/l10n_us_nc_hr_payroll/__manifest__.py
@@ -0,0 +1,28 @@
+{
+ 'name': 'USA - North Carolina - Payroll',
+ 'author': 'Hibou Corp. ',
+ 'license': 'AGPL-3',
+ 'category': 'Localization',
+ 'depends': ['l10n_us_hr_payroll'],
+ 'version': '12.0.2019.0.0',
+ 'description': """
+USA::North Carolina Payroll Rules.
+==================================
+
+* Contribution register and partner for North Carolina Department of Taxaction - Unemployment
+* Contribution register and partner for North Carolina Department of Taxaction - Income Tax Withholding
+* Contract level North Carolina Exemptions
+* Company level North Carolina 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_nc_hr_payroll/data/base.xml b/l10n_us_nc_hr_payroll/data/base.xml
new file mode 100755
index 00000000..fdefcda9
--- /dev/null
+++ b/l10n_us_nc_hr_payroll/data/base.xml
@@ -0,0 +1,46 @@
+
+
+
+
+
+ North Carolina Division of Unemployment Security - Unemployment Tax
+ 1
+
+
+
+ North Carolina Department of Revenue - Income Tax Withholding
+ 1
+
+
+
+ North Carolina Unemployment
+ North Carolina Division of Unemployment Security - Unemployment
+
+
+
+ North Carolina Income Tax Withholding
+ North Carolina Department of Revenue - Income Tax Withholding
+
+
+
+
+
+
+ Wage: US-NC Unemployment
+ WAGE_US_NC_UNEMP
+
+
+
+ ER: US-NC Unemployment
+ ER_US_NC_UNEMP
+
+
+
+
+ EE: US-NC Income Tax Withholding
+ EE_US_NC_INC_WITHHOLD
+
+
+
+
+
diff --git a/l10n_us_nc_hr_payroll/data/final.xml b/l10n_us_nc_hr_payroll/data/final.xml
new file mode 100755
index 00000000..d8086c80
--- /dev/null
+++ b/l10n_us_nc_hr_payroll/data/final.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+ US_NC_EMP
+ USA North Carolina Employee
+
+
+
+
+
+
+
diff --git a/l10n_us_nc_hr_payroll/data/rates.xml b/l10n_us_nc_hr_payroll/data/rates.xml
new file mode 100755
index 00000000..1aaebf0d
--- /dev/null
+++ b/l10n_us_nc_hr_payroll/data/rates.xml
@@ -0,0 +1,19 @@
+
+
+
+
+ US NC Unemployment
+ US_NC_UNEMP
+ 0.6
+ 2018-01-01
+
+
+
+ US NC Unemployment
+ US_NC_UNEMP
+ 1.0
+ 2019-01-01
+
+
+
+
diff --git a/l10n_us_nc_hr_payroll/data/rules.xml b/l10n_us_nc_hr_payroll/data/rules.xml
new file mode 100755
index 00000000..8d06ea80
--- /dev/null
+++ b/l10n_us_nc_hr_payroll/data/rules.xml
@@ -0,0 +1,154 @@
+
+
+
+
+
+
+
+
+ Wage: US-NC Unemployment
+ WAGE_US_NC_UNEMP
+ python
+ result = (contract.futa_type != contract.FUTA_TYPE_BASIC)
+ code
+
+rate = payslip.dict.get_rate('US_NC_UNEMP')
+year = payslip.dict.date_to.year
+ytd = payslip.sum('NC_UNEMP_WAGES_2018', 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-NC Unemployment
+ ER_US_NC_UNEMP
+ python
+ result = (contract.futa_type != contract.FUTA_TYPE_BASIC)
+ code
+
+rate = payslip.dict.get_rate('US_NC_UNEMP')
+result_rate = -rate.rate
+result = categories.WAGE_US_NC_UNEMP
+
+# result_rate of 0 implies 100% due to bug
+if result_rate == 0.0:
+ result = 0.0
+
+
+
+
+
+
+
+
+ EE: US-NC Income Tax Withholding
+ EE_US_NC_INC_WITHHOLD
+ python
+ result = True
+ code
+
+year = payslip.dict.date_to.year
+wages = categories.GROSS
+allowances = contract.nc_nc4_allowances
+schedule_pay = contract.schedule_pay
+val = 0.00
+
+
+# PST -> Portion of standard deduction
+if year == 2018:
+ PST = 0.0
+ allowance_multiplier = 0.0
+ if 'weekly' == schedule_pay:
+ allowance_multiplier = 48.08
+ if contract.nc_nc4_filing_status == 'head_household':
+ PST = 269.23
+ else:
+ PST = 168.27
+ elif 'bi-weekly' == schedule_pay:
+ allowance_multiplier = 96.15
+ if contract.nc_nc4_filing_status == 'head_household':
+ PST = 538.46
+ else:
+ PST = 336.54
+ elif 'semi-monthly' == schedule_pay:
+ allowance_multiplier = 104.17
+ if contract.nc_nc4_filing_status == 'head_household':
+ PST = 583.33
+ else:
+ PST = 364.58
+ elif 'monthly' == schedule_pay:
+ allowance_multiplier = 208.33
+ if contract.nc_nc4_filing_status == 'head_household':
+ PST = 1166.67
+ else:
+ PST = 729.17
+
+ else:
+ raise Exception('Invalid schedule_pay="' + schedule_pay + '" for NC Income Withholding calculation')
+
+ # Algorithm derived from percentage method in https://files.nc.gov/ncdor/documents/files/nc-30_book_web.pdf
+ if contract.nc_nc4_filing_status == 'exempt':
+ result = 0
+ else:
+ withholding = round(((wages - (PST + (allowance_multiplier * allowances))) * 0.05599) + contract.nc_nc4_additional_wh)
+ if withholding > 0.0:
+ result = - withholding
+ else:
+ result = 0
+else:
+ PST = 0.0
+ allowance_multiplier = 0.0
+ if 'weekly' == schedule_pay:
+ allowance_multiplier = 48.08
+ if contract.nc_nc4_filing_status == 'head_household':
+ PST = 288.46
+ else:
+ PST = 192.31
+ elif 'bi-weekly' == schedule_pay:
+ allowance_multiplier = 96.15
+ if contract.nc_nc4_filing_status == 'head_household':
+ PST = 576.92
+ else:
+ PST = 384.62
+ elif 'semi-monthly' == schedule_pay:
+ allowance_multiplier = 104.17
+ if contract.nc_nc4_filing_status == 'head_household':
+ PST = 625.00
+ else:
+ PST = 416.67
+ elif 'monthly' == schedule_pay:
+ allowance_multiplier = 208.33
+ if contract.nc_nc4_filing_status == 'head_household':
+ PST = 1250.00
+ else:
+ PST = 833.33
+
+ else:
+ raise Exception('Invalid schedule_pay="' + schedule_pay + '" for NC Income Withholding calculation')
+
+ # Algorithm derived from percentage method in https://files.nc.gov/ncdor/documents/files/nc-30_book_web.pdf
+ if contract.nc_nc4_filing_status == 'exempt':
+ result = 0
+ else:
+ withholding = round(((wages - (PST + (allowance_multiplier * allowances))) * 0.0535) + contract.nc_nc4_additional_wh)
+ if withholding > 0.0:
+ result = - withholding
+ else:
+ result = 0
+
+
+
+
+
+
+
diff --git a/l10n_us_nc_hr_payroll/models/__init__.py b/l10n_us_nc_hr_payroll/models/__init__.py
new file mode 100644
index 00000000..e99aa24a
--- /dev/null
+++ b/l10n_us_nc_hr_payroll/models/__init__.py
@@ -0,0 +1 @@
+from . import hr_payroll
diff --git a/l10n_us_nc_hr_payroll/models/hr_payroll.py b/l10n_us_nc_hr_payroll/models/hr_payroll.py
new file mode 100755
index 00000000..0746427c
--- /dev/null
+++ b/l10n_us_nc_hr_payroll/models/hr_payroll.py
@@ -0,0 +1,17 @@
+from odoo import models, fields, api
+
+
+class USNCHrContract(models.Model):
+ _inherit = 'hr.contract'
+
+ nc_nc4_allowances = fields.Integer(string='North Carolina NC-4 Allowances', default=0)
+ nc_nc4_filing_status = fields.Selection([
+ ('exempt', 'Exempt'),
+ ('single', 'Single'),
+ ('married', 'Married'),
+ ('surviving_spouse', 'Surviving Spouse'),
+ ('head_household', 'Head of Household')
+ ], string='NC Filing Status', default='single')
+ nc_nc4_additional_wh = fields.Float(string="Additional Witholding", default=0.0,
+ help="If you are filling out NC-4 NRA, this would be box 4; " \
+ "otherwise box 2.")
diff --git a/l10n_us_nc_hr_payroll/tests/__init__.py b/l10n_us_nc_hr_payroll/tests/__init__.py
new file mode 100755
index 00000000..f5eee306
--- /dev/null
+++ b/l10n_us_nc_hr_payroll/tests/__init__.py
@@ -0,0 +1,2 @@
+from . import test_us_nc_payslip_2018
+from . import test_us_nc_payslip_2019
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..eb5079fe
--- /dev/null
+++ b/l10n_us_nc_hr_payroll/tests/test_us_nc_payslip_2018.py
@@ -0,0 +1,378 @@
+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 = 23500.0
+ NC_UNEMP = -0.6 / 100.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()
+
+ 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')
+
+ 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['WAGE_US_NC_UNEMP'], salary)
+ self.assertPayrollEqual(cats['ER_US_NC_UNEMP'], cats['WAGE_US_NC_UNEMP'] * self.NC_UNEMP)
+ self.assertPayrollEqual(cats['EE_US_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['WAGE_US_NC_UNEMP'], remaining_NC_UNEMP_wages)
+ self.assertPayrollEqual(cats['ER_US_NC_UNEMP'], remaining_NC_UNEMP_wages * self.NC_UNEMP)
+
+ def test_2018_taxes_with_external_weekly(self):
+ salary = 5000.0
+ external_wages = 30000.0
+
+ schedule_pay = 'weekly'
+
+ employee = self._createEmployee()
+
+ 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)
+
+ 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['WAGE_US_NC_UNEMP'], max(self.NC_UNEMP_MAX_WAGE - external_wages, 0.0))
+ self.assertPayrollEqual(cats['ER_US_NC_UNEMP'], cats['WAGE_US_NC_UNEMP'] * self.NC_UNEMP)
+
+ def test_2018_taxes_with_state_exempt_weekly(self):
+ salary = 5000.0
+ external_wages = 10000.0
+ schedule_pay = 'weekly'
+
+ employee = self._createEmployee()
+
+ 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)
+
+ 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.get('WAGE_US_NC_UNEMP', 0.0), 0.0)
+ self.assertPayrollEqual(cats.get('ER_US_NC_UNEMP', 0.0), cats.get('WAGE_US_NC_UNEMP', 0.0) * self.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()
+
+ 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')
+
+ 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['WAGE_US_NC_UNEMP'], salary)
+ self.assertPayrollEqual(cats['ER_US_NC_UNEMP'], cats['WAGE_US_NC_UNEMP'] * self.NC_UNEMP)
+ self.assertPayrollEqual(cats['EE_US_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['WAGE_US_NC_UNEMP'], remaining_NC_UNEMP_wages)
+ self.assertPayrollEqual(cats['ER_US_NC_UNEMP'], remaining_NC_UNEMP_wages * self.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()
+
+ 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')
+
+ 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['WAGE_US_NC_UNEMP'], salary)
+ self.assertPayrollEqual(cats['ER_US_NC_UNEMP'], cats['WAGE_US_NC_UNEMP'] * self.NC_UNEMP)
+ self.assertPayrollEqual(cats['EE_US_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['WAGE_US_NC_UNEMP'], remaining_NC_UNEMP_wages)
+ self.assertPayrollEqual(cats['ER_US_NC_UNEMP'], remaining_NC_UNEMP_wages * self.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()
+
+ 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')
+
+ 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['WAGE_US_NC_UNEMP'], salary)
+ self.assertPayrollEqual(cats['ER_US_NC_UNEMP'], cats['WAGE_US_NC_UNEMP'] * self.NC_UNEMP)
+ self.assertPayrollEqual(cats['EE_US_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['WAGE_US_NC_UNEMP'], remaining_NC_UNEMP_wages)
+ self.assertPayrollEqual(cats['ER_US_NC_UNEMP'], remaining_NC_UNEMP_wages * self.NC_UNEMP)
+
+ def test_tax_exempt(self):
+ salary = 4000.0
+ wh = 0
+ schedule_pay = 'weekly'
+ excemptions = 1
+
+ employee = self._createEmployee()
+
+ 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')
+
+ 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['WAGE_US_NC_UNEMP'], salary)
+ self.assertPayrollEqual(cats['ER_US_NC_UNEMP'], cats['WAGE_US_NC_UNEMP'] * self.NC_UNEMP)
+ self.assertPayrollEqual(cats['EE_US_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['WAGE_US_NC_UNEMP'], remaining_NC_UNEMP_wages)
+ self.assertPayrollEqual(cats['ER_US_NC_UNEMP'], remaining_NC_UNEMP_wages * self.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()
+
+ 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)
+
+ 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['WAGE_US_NC_UNEMP'], salary)
+ self.assertPayrollEqual(cats['ER_US_NC_UNEMP'], cats['WAGE_US_NC_UNEMP'] * self.NC_UNEMP)
+ self.assertPayrollEqual(cats['EE_US_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['WAGE_US_NC_UNEMP'], remaining_NC_UNEMP_wages)
+ self.assertPayrollEqual(cats['ER_US_NC_UNEMP'], remaining_NC_UNEMP_wages * self.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()
+
+ 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')
+
+ 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['WAGE_US_NC_UNEMP'], salary)
+ self.assertPayrollEqual(cats['ER_US_NC_UNEMP'], cats['WAGE_US_NC_UNEMP'] * self.NC_UNEMP)
+ self.assertPayrollEqual(cats['EE_US_NC_INC_WITHHOLD'], wh)
+
+ process_payslip(payslip)
diff --git a/l10n_us_nc_hr_payroll/tests/test_us_nc_payslip_2019.py b/l10n_us_nc_hr_payroll/tests/test_us_nc_payslip_2019.py
new file mode 100755
index 00000000..2b30e8c1
--- /dev/null
+++ b/l10n_us_nc_hr_payroll/tests/test_us_nc_payslip_2019.py
@@ -0,0 +1,379 @@
+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 = 24300.0
+ NC_UNEMP = -1.0 / 100.0
+ NC_INC_TAX = -0.0535
+
+
+ def test_2019_taxes_weekly(self):
+ salary = 5000.0
+ schedule_pay = 'weekly'
+ # allowance_multiplier and Portion of Standard Deduction for weekly
+ allowance_multiplier = 48.08
+ PST = 192.31
+
+ 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))) * -self.NC_INC_TAX)
+
+ employee = self._createEmployee()
+
+ 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')
+
+ self._log('2019 North Carolina tax first payslip weekly:')
+ payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['WAGE_US_NC_UNEMP'], salary)
+ self.assertPayrollEqual(cats['ER_US_NC_UNEMP'], cats['WAGE_US_NC_UNEMP'] * self.NC_UNEMP)
+ self.assertPayrollEqual(cats['EE_US_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('2019 North Carolina tax second payslip weekly:')
+ payslip = self._createPayslip(employee, '2019-02-01', '2019-02-28')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['WAGE_US_NC_UNEMP'], remaining_NC_UNEMP_wages)
+ self.assertPayrollEqual(cats['ER_US_NC_UNEMP'], remaining_NC_UNEMP_wages * self.NC_UNEMP)
+
+ def test_2019_taxes_with_external_weekly(self):
+ salary = 5000.0
+ external_wages = 30000.0
+
+ schedule_pay = 'weekly'
+
+ employee = self._createEmployee()
+
+ 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)
+
+ self._log('2019 NorthCarolina_external tax first payslip weekly:')
+ payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['WAGE_US_NC_UNEMP'], max(self.NC_UNEMP_MAX_WAGE - external_wages, 0.0))
+ self.assertPayrollEqual(cats['ER_US_NC_UNEMP'], cats['WAGE_US_NC_UNEMP'] * self.NC_UNEMP)
+
+ def test_2019_taxes_with_state_exempt_weekly(self):
+ salary = 5000.0
+ external_wages = 10000.0
+ schedule_pay = 'weekly'
+
+ employee = self._createEmployee()
+
+ 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)
+
+ self._log('2019 North Carolina exempt tax first payslip weekly:')
+ payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats.get('WAGE_US_NC_UNEMP', 0.0), 0.0)
+ self.assertPayrollEqual(cats.get('ER_US_NC_UNEMP', 0.0), cats.get('WAGE_US_NC_UNEMP', 0.0) * self.NC_UNEMP)
+
+ def test_2019_taxes_biweekly(self):
+ salary = 5000.0
+ schedule_pay = 'bi-weekly'
+ # allowance_multiplier and Portion of Standard Deduction for weekly
+ allowance_multiplier = 96.15
+ PST = 384.62
+
+ 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))) * -self.NC_INC_TAX)
+
+ employee = self._createEmployee()
+
+ 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')
+
+ self._log('2019 North Carolina tax first payslip bi-weekly:')
+ payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['WAGE_US_NC_UNEMP'], salary)
+ self.assertPayrollEqual(cats['ER_US_NC_UNEMP'], cats['WAGE_US_NC_UNEMP'] * self.NC_UNEMP)
+ self.assertPayrollEqual(cats['EE_US_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('2019 North Carolina tax second payslip bi-weekly:')
+ payslip = self._createPayslip(employee, '2019-02-01', '2019-02-28')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['WAGE_US_NC_UNEMP'], remaining_NC_UNEMP_wages)
+ self.assertPayrollEqual(cats['ER_US_NC_UNEMP'], remaining_NC_UNEMP_wages * self.NC_UNEMP)
+
+ def test_2019_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 = 625.00
+
+ 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))) * -self.NC_INC_TAX)
+
+ employee = self._createEmployee()
+
+ 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')
+
+ self._log('2019 North Carolina 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_NC_UNEMP'], salary)
+ self.assertPayrollEqual(cats['ER_US_NC_UNEMP'], cats['WAGE_US_NC_UNEMP'] * self.NC_UNEMP)
+ self.assertPayrollEqual(cats['EE_US_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('2019 North Carolina 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_NC_UNEMP'], remaining_NC_UNEMP_wages)
+ self.assertPayrollEqual(cats['ER_US_NC_UNEMP'], remaining_NC_UNEMP_wages * self.NC_UNEMP)
+
+ def test_2019_taxes_monthly(self):
+ salary = 4000.0
+ schedule_pay = 'monthly'
+ # allowance_multiplier and Portion of Standard Deduction for weekly
+ allowance_multiplier = 208.33
+ PST = 833.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))) * -self.NC_INC_TAX)
+
+ employee = self._createEmployee()
+
+ 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')
+
+ self._log('2019 North Carolina 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_NC_UNEMP'], salary)
+ self.assertPayrollEqual(cats['ER_US_NC_UNEMP'], cats['WAGE_US_NC_UNEMP'] * self.NC_UNEMP)
+ self.assertPayrollEqual(cats['EE_US_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('2019 North Carolina 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_NC_UNEMP'], remaining_NC_UNEMP_wages)
+ self.assertPayrollEqual(cats['ER_US_NC_UNEMP'], remaining_NC_UNEMP_wages * self.NC_UNEMP)
+
+ def test_tax_exempt(self):
+ salary = 4000.0
+ wh = 0
+ schedule_pay = 'weekly'
+ excemptions = 1
+
+ employee = self._createEmployee()
+
+ 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')
+
+ self._log('2019 North Carolina 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_NC_UNEMP'], salary)
+ self.assertPayrollEqual(cats['ER_US_NC_UNEMP'], cats['WAGE_US_NC_UNEMP'] * self.NC_UNEMP)
+ self.assertPayrollEqual(cats['EE_US_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('2019 North Carolina 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_NC_UNEMP'], remaining_NC_UNEMP_wages)
+ self.assertPayrollEqual(cats['ER_US_NC_UNEMP'], remaining_NC_UNEMP_wages * self.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 = 192.31
+ 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))) * -self.NC_INC_TAX) + additional_wh)
+
+ employee = self._createEmployee()
+
+ 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)
+
+ self._log('2019 North Carolina tax first payslip weekly:')
+ payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['WAGE_US_NC_UNEMP'], salary)
+ self.assertPayrollEqual(cats['ER_US_NC_UNEMP'], cats['WAGE_US_NC_UNEMP'] * self.NC_UNEMP)
+ self.assertPayrollEqual(cats['EE_US_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('2019 North Carolina tax second payslip weekly:')
+ payslip = self._createPayslip(employee, '2019-02-01', '2019-02-28')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['WAGE_US_NC_UNEMP'], remaining_NC_UNEMP_wages)
+ self.assertPayrollEqual(cats['ER_US_NC_UNEMP'], remaining_NC_UNEMP_wages * self.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 = 192.31
+
+ exemption = 1
+
+ # Withholding should be 0, since pay is so low it's less than PST.
+ wh = 0.0
+
+ employee = self._createEmployee()
+
+ 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')
+
+ self._log('2019 North Carolina tax first payslip weekly:')
+ payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
+
+ payslip.compute_sheet()
+
+ cats = self._getCategories(payslip)
+
+ self.assertPayrollEqual(cats['WAGE_US_NC_UNEMP'], salary)
+ self.assertPayrollEqual(cats['ER_US_NC_UNEMP'], cats['WAGE_US_NC_UNEMP'] * self.NC_UNEMP)
+ self.assertPayrollEqual(cats['EE_US_NC_INC_WITHHOLD'], wh)
+
+ process_payslip(payslip)
diff --git a/l10n_us_nc_hr_payroll/views/hr_payroll_views.xml b/l10n_us_nc_hr_payroll/views/hr_payroll_views.xml
new file mode 100755
index 00000000..ef850399
--- /dev/null
+++ b/l10n_us_nc_hr_payroll/views/hr_payroll_views.xml
@@ -0,0 +1,22 @@
+
+
+
+
+ hr.contract.form.inherit
+ hr.contract
+ 128
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+