From 2785672a8e5477f37118a0629993c5d7ad98cc2e Mon Sep 17 00:00:00 2001
From: Jared Kipe
Date: Sun, 5 Jan 2020 19:07:55 -0800
Subject: [PATCH] MIG `l10n_us_hr_payroll` Major refactor and Federal 2020
Rules (new W4 Form)
---
hr_payroll_rate/models/payroll.py | 27 +-
hr_payroll_rate/tests/test_payroll_rate.py | 9 +
hr_payroll_rate/views/payroll_views.xml | 1 +
l10n_us_hr_payroll/__init__.py | 2 +
l10n_us_hr_payroll/__manifest__.py | 19 +-
l10n_us_hr_payroll/data/base.xml | 83 +-
.../data/federal/fed_940_futa_parameters.xml | 26 +
.../data/federal/fed_940_futa_rules.xml | 39 +
.../data/federal/fed_941_fica_parameters.xml | 66 +
.../data/federal/fed_941_fica_rules.xml | 101 ++
.../data/federal/fed_941_fit_parameters.xml | 492 ++++++++
.../data/federal/fed_941_fit_rules.xml | 28 +
l10n_us_hr_payroll/data/final.xml | 29 +-
l10n_us_hr_payroll/data/integration_rules.xml | 27 +
l10n_us_hr_payroll/data/rates.xml | 68 --
l10n_us_hr_payroll/data/rules.xml | 1058 -----------------
l10n_us_hr_payroll/models/__init__.py | 6 +-
l10n_us_hr_payroll/models/federal/__init__.py | 1 +
l10n_us_hr_payroll/models/federal/fed_940.py | 37 +
l10n_us_hr_payroll/models/federal/fed_941.py | 239 ++++
l10n_us_hr_payroll/models/hr_contract.py | 24 +
l10n_us_hr_payroll/models/hr_payslip.py | 213 ++++
.../models/l10n_us_hr_payroll.py | 44 -
.../models/us_payroll_config.py | 45 +
.../security/ir.model.access.csv | 2 +
.../static/description/icon.png | Bin 0 -> 8776 bytes
l10n_us_hr_payroll/tests/__init__.py | 6 +-
l10n_us_hr_payroll/tests/common.py | 155 +++
l10n_us_hr_payroll/tests/test_us_payslip.py | 117 --
.../tests/test_us_payslip_2018.py | 368 ------
.../tests/test_us_payslip_2019.py | 239 ++--
.../tests/test_us_payslip_2020.py | 302 +++++
.../views/hr_contract_views.xml | 19 +
.../views/l10n_us_hr_payroll_view.xml | 35 -
.../views/us_payroll_config_views.xml | 75 ++
35 files changed, 2069 insertions(+), 1933 deletions(-)
mode change 100755 => 100644 l10n_us_hr_payroll/data/base.xml
create mode 100644 l10n_us_hr_payroll/data/federal/fed_940_futa_parameters.xml
create mode 100644 l10n_us_hr_payroll/data/federal/fed_940_futa_rules.xml
create mode 100644 l10n_us_hr_payroll/data/federal/fed_941_fica_parameters.xml
create mode 100644 l10n_us_hr_payroll/data/federal/fed_941_fica_rules.xml
create mode 100644 l10n_us_hr_payroll/data/federal/fed_941_fit_parameters.xml
create mode 100644 l10n_us_hr_payroll/data/federal/fed_941_fit_rules.xml
mode change 100755 => 100644 l10n_us_hr_payroll/data/final.xml
create mode 100644 l10n_us_hr_payroll/data/integration_rules.xml
delete mode 100644 l10n_us_hr_payroll/data/rates.xml
delete mode 100755 l10n_us_hr_payroll/data/rules.xml
create mode 100644 l10n_us_hr_payroll/models/federal/__init__.py
create mode 100644 l10n_us_hr_payroll/models/federal/fed_940.py
create mode 100644 l10n_us_hr_payroll/models/federal/fed_941.py
create mode 100644 l10n_us_hr_payroll/models/hr_contract.py
create mode 100644 l10n_us_hr_payroll/models/hr_payslip.py
delete mode 100755 l10n_us_hr_payroll/models/l10n_us_hr_payroll.py
create mode 100644 l10n_us_hr_payroll/models/us_payroll_config.py
create mode 100644 l10n_us_hr_payroll/security/ir.model.access.csv
create mode 100644 l10n_us_hr_payroll/static/description/icon.png
create mode 100755 l10n_us_hr_payroll/tests/common.py
delete mode 100755 l10n_us_hr_payroll/tests/test_us_payslip.py
delete mode 100755 l10n_us_hr_payroll/tests/test_us_payslip_2018.py
mode change 100755 => 100644 l10n_us_hr_payroll/tests/test_us_payslip_2019.py
create mode 100644 l10n_us_hr_payroll/tests/test_us_payslip_2020.py
create mode 100644 l10n_us_hr_payroll/views/hr_contract_views.xml
delete mode 100755 l10n_us_hr_payroll/views/l10n_us_hr_payroll_view.xml
create mode 100644 l10n_us_hr_payroll/views/us_payroll_config_views.xml
diff --git a/hr_payroll_rate/models/payroll.py b/hr_payroll_rate/models/payroll.py
index 66a89963..d1280593 100644
--- a/hr_payroll_rate/models/payroll.py
+++ b/hr_payroll_rate/models/payroll.py
@@ -1,23 +1,41 @@
+import ast
from odoo import api, fields, models
+from odoo.tools import ormcache
+from odoo.exceptions import UserError
class PayrollRate(models.Model):
_name = 'hr.payroll.rate'
_description = 'Payroll Rate'
+ _order = 'date_from DESC, company_id ASC'
active = fields.Boolean(string='Active', default=True)
name = fields.Char(string='Name')
- date_from = fields.Date(string='Date From', required=True)
+ date_from = fields.Date(string='Date From', index=True, required=True)
date_to = fields.Date(string='Date To')
company_id = fields.Many2one('res.company', string='Company', copy=False,
default=False)
- rate = fields.Float(string='Rate', digits=(12, 6), required=True)
- code = fields.Char(string='Code', required=True)
+ rate = fields.Float(string='Rate', digits=(12, 6), default=0.0, required=True)
+ code = fields.Char(string='Code', index=True, required=True)
limit_payslip = fields.Float(string='Payslip Limit')
limit_year = fields.Float(string='Year Limit')
wage_limit_payslip = fields.Float(string='Payslip Wage Limit')
wage_limit_year = fields.Float(string='Year Wage Limit')
+ parameter_value = fields.Text(help="Python data structure")
+
+ @api.model
+ @ormcache('code', 'date', 'company_id', 'self.env.user.company_id.id')
+ def _get_parameter_from_code(self, code, company_id, date=None):
+ if not date:
+ date = fields.Date.today()
+ rate = self.search([
+ ('code', '=', code),
+ ('date_from', '<=', date),
+ ], limit=1)
+ if not rate:
+ raise UserError(_("No rule parameter with code '%s' was found for %s ") % (code, date))
+ return ast.literal_eval(rate.parameter_value)
class Payslip(models.Model):
@@ -35,3 +53,6 @@ class Payslip(models.Model):
self.ensure_one()
return self.env['hr.payroll.rate'].search(
self._get_rate_domain(code), limit=1, order='date_from DESC, company_id ASC')
+
+ def rule_parameter(self, code):
+ return self.env['hr.payroll.rate']._get_parameter_from_code(code, self.company_id.id, self.date_to)
diff --git a/hr_payroll_rate/tests/test_payroll_rate.py b/hr_payroll_rate/tests/test_payroll_rate.py
index c86fe446..5354cfe1 100644
--- a/hr_payroll_rate/tests/test_payroll_rate.py
+++ b/hr_payroll_rate/tests/test_payroll_rate.py
@@ -44,6 +44,15 @@ class TestPayrollRate(common.TransactionCase):
rate = self.payslip.get_rate('TEST')
self.assertEqual(rate, test_rate)
+ test_rate.parameter_value = """[
+ (1, 2, 3),
+ (4, 5, 6),
+ ]"""
+
+ value = self.payslip.rule_parameter('TEST')
+ self.assertEqual(len(value), 2)
+ self.assertEqual(value[0], (1, 2, 3))
+
def test_payroll_rate_multicompany(self):
test_rate_other = self.env['hr.payroll.rate'].create({
'name': 'Test Rate',
diff --git a/hr_payroll_rate/views/payroll_views.xml b/hr_payroll_rate/views/payroll_views.xml
index 4132846e..49815884 100644
--- a/hr_payroll_rate/views/payroll_views.xml
+++ b/hr_payroll_rate/views/payroll_views.xml
@@ -27,6 +27,7 @@
+
diff --git a/l10n_us_hr_payroll/__init__.py b/l10n_us_hr_payroll/__init__.py
index 0650744f..09434554 100755
--- a/l10n_us_hr_payroll/__init__.py
+++ b/l10n_us_hr_payroll/__init__.py
@@ -1 +1,3 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
from . import models
diff --git a/l10n_us_hr_payroll/__manifest__.py b/l10n_us_hr_payroll/__manifest__.py
index dd863641..e32bde9c 100755
--- a/l10n_us_hr_payroll/__manifest__.py
+++ b/l10n_us_hr_payroll/__manifest__.py
@@ -1,10 +1,9 @@
{
'name': 'USA - Payroll',
'author': 'Hibou Corp. ',
- 'license': 'AGPL-3',
'category': 'Localization',
'depends': ['hr_payroll', 'hr_payroll_rate'],
- 'version': '12.0.2019.1.0',
+ 'version': '12.0.2020.1.0',
'description': """
USA Payroll Rules.
==================
@@ -20,11 +19,19 @@ USA Payroll Rules.
'auto_install': False,
'website': 'https://hibou.io/',
'data': [
- 'views/l10n_us_hr_payroll_view.xml',
+ 'security/ir.model.access.csv',
'data/base.xml',
- 'data/rates.xml',
- 'data/rules.xml',
+ 'data/integration_rules.xml',
+ 'data/federal/fed_940_futa_parameters.xml',
+ 'data/federal/fed_940_futa_rules.xml',
+ 'data/federal/fed_941_fica_parameters.xml',
+ 'data/federal/fed_941_fica_rules.xml',
+ 'data/federal/fed_941_fit_parameters.xml',
+ 'data/federal/fed_941_fit_rules.xml',
'data/final.xml',
+ 'views/hr_contract_views.xml',
+ 'views/us_payroll_config_views.xml',
],
- 'installable': True
+ 'installable': True,
+ 'license': 'OPL-1',
}
diff --git a/l10n_us_hr_payroll/data/base.xml b/l10n_us_hr_payroll/data/base.xml
old mode 100755
new mode 100644
index 0579e8f3..75cafa13
--- a/l10n_us_hr_payroll/data/base.xml
+++ b/l10n_us_hr_payroll/data/base.xml
@@ -1,83 +1,4 @@
-
+
-
-
-
- EFTPS - Form 941
- 1
-
-
-
- EFTPS - Form 940
- 1
-
-
-
- EFTPS - 941 (FICA + Federal Witholding)
- Electronic Federal Tax Payment System - Form 941
-
-
-
- EFTPS - 940 (FUTA)
- Electronic Federal Tax Payment System - Form 940
-
-
-
-
-
- Wage: US FICA Social Security
- WAGE_US_FICA_SS
-
-
- Wage: US FICA Medicare
- WAGE_US_FICA_M
-
-
- Wage: US FICA Medicare Additional
- WAGE_US_FICA_M_ADD
-
-
- Wage: US FUTA Federal Unemployment
- WAGE_US_FUTA
-
-
-
- EE: US FICA Social Security
- EE_US_FICA_SS
-
-
-
- EE: US FICA Medicare
- EE_US_FICA_M
-
-
-
- EE: US FICA Medicare Additional
- EE_US_FICA_M_ADD
-
-
-
- EE: US Federal Income Tax Withholding
- EE_US_FED_INC_WITHHOLD
-
-
-
-
- ER: US FICA Social Security
- ER_US_FICA_SS
-
-
-
- ER: US FICA Medicare
- ER_US_FICA_M
-
-
-
- ER: US FUTA Federal Unemployment
- ER_US_FUTA
-
-
-
-
-
+
\ No newline at end of file
diff --git a/l10n_us_hr_payroll/data/federal/fed_940_futa_parameters.xml b/l10n_us_hr_payroll/data/federal/fed_940_futa_parameters.xml
new file mode 100644
index 00000000..e2ed116b
--- /dev/null
+++ b/l10n_us_hr_payroll/data/federal/fed_940_futa_parameters.xml
@@ -0,0 +1,26 @@
+
+
+
+
+ Federal 940 FUTA Wage Base
+ fed_940_futa_wage_base
+ 7000.00
+
+
+
+
+
+ Federal 940 FUTA Rate Basic
+ fed_940_futa_rate_basic
+ 6.0
+
+
+
+
+ Federal 940 FUTA Rate Normal
+ fed_940_futa_rate_normal
+ 0.6
+
+
+
+
\ No newline at end of file
diff --git a/l10n_us_hr_payroll/data/federal/fed_940_futa_rules.xml b/l10n_us_hr_payroll/data/federal/fed_940_futa_rules.xml
new file mode 100644
index 00000000..c1073b06
--- /dev/null
+++ b/l10n_us_hr_payroll/data/federal/fed_940_futa_rules.xml
@@ -0,0 +1,39 @@
+
+
+
+
+ US Federal 940 - EFTPS
+
+
+
+ EFTPS - 940 (FUTA)
+ Electronic Federal Tax Payment System - Form 940
+
+
+
+
+ ER: Federal 940 FUTA
+ ER_US_940_FUTA
+
+
+
+
+
+ WAGE: Federal 940 FUTA Exempt
+ WAGE_US_940_FUTA_EXEMPT
+
+
+
+
+
+ ER: US FUTA Federal Unemployment
+ ER_US_940_FUTA
+ python
+ result, _ = er_us_940_futa(payslip, categories, worked_days, inputs)
+ code
+ result, result_rate = er_us_940_futa(payslip, categories, worked_days, inputs)
+
+
+
+
+
\ No newline at end of file
diff --git a/l10n_us_hr_payroll/data/federal/fed_941_fica_parameters.xml b/l10n_us_hr_payroll/data/federal/fed_941_fica_parameters.xml
new file mode 100644
index 00000000..8a25dde0
--- /dev/null
+++ b/l10n_us_hr_payroll/data/federal/fed_941_fica_parameters.xml
@@ -0,0 +1,66 @@
+
+
+
+
+
+ Federal 941 FICA Social Security Wage Base
+ fed_941_fica_ss_wage_base
+ 128400.0
+
+
+
+ Federal 941 FICA Social Security Wage Base
+ fed_941_fica_ss_wage_base
+ 132900.0
+
+
+
+ Federal 941 FICA Social Security Wage Base
+ fed_941_fica_ss_wage_base
+ 137700.0
+
+
+
+
+
+ Federal 941 FICA Rate
+ fed_941_fica_ss_rate
+ 6.2
+
+
+
+
+
+
+ Federal 941 FICA Medicare Wage Base
+ fed_941_fica_m_wage_base
+ "inf"
+
+
+
+
+
+ Federal 941 FICA Rate
+ fed_941_fica_m_rate
+ 1.45
+
+
+
+
+
+
+ Federal 941 FICA Medicare Additional Wage Start
+ fed_941_fica_m_add_wage_start
+ 200000.0
+
+
+
+
+
+ Federal 941 FICA Medicare Additional Rate
+ fed_941_fica_m_add_rate
+ 0.9
+
+
+
+
\ No newline at end of file
diff --git a/l10n_us_hr_payroll/data/federal/fed_941_fica_rules.xml b/l10n_us_hr_payroll/data/federal/fed_941_fica_rules.xml
new file mode 100644
index 00000000..6ce417fa
--- /dev/null
+++ b/l10n_us_hr_payroll/data/federal/fed_941_fica_rules.xml
@@ -0,0 +1,101 @@
+
+
+
+
+ US Federal 941 - EFTPS
+
+
+
+ EFTPS - 941 (FICA + Federal Witholding)
+ Electronic Federal Tax Payment System - Form 941
+
+
+
+
+ EE: Federal 941 FICA
+ EE_US_941_FICA
+
+
+
+
+ ER: Federal 941 FICA
+ ER_US_941_FICA
+
+
+
+
+
+ WAGE: Federal 941 FICA Exempt
+ WAGE_US_941_FICA_EXEMPT
+
+
+
+
+
+
+
+ EE: US FICA Social Security
+ EE_US_941_FICA_SS
+ python
+ result, _ = ee_us_941_fica_ss(payslip, categories, worked_days, inputs)
+ code
+ result, result_rate = ee_us_941_fica_ss(payslip, categories, worked_days, inputs)
+
+
+
+
+
+
+
+ ER: US FICA Social Security
+ ER_US_941_FICA_SS
+ python
+ result, _ = er_us_941_fica_ss(payslip, categories, worked_days, inputs)
+ code
+ result, result_rate = er_us_941_fica_ss(payslip, categories, worked_days, inputs)
+
+
+
+
+
+
+
+
+ EE: US FICA Medicare
+ EE_US_941_FICA_M
+ python
+ result, _ = ee_us_941_fica_m(payslip, categories, worked_days, inputs)
+ code
+ result, result_rate = ee_us_941_fica_m(payslip, categories, worked_days, inputs)
+
+
+
+
+
+
+
+ ER: US FICA Medicare
+ ER_US_941_FICA_M
+ python
+ result, _ = er_us_941_fica_m(payslip, categories, worked_days, inputs)
+ code
+ result, result_rate = er_us_941_fica_m(payslip, categories, worked_days, inputs)
+
+
+
+
+
+
+
+
+ EE: US FICA Medicare Additional
+ EE_US_941_FICA_M_ADD
+ python
+ result, _ = ee_us_941_fica_m_add(payslip, categories, worked_days, inputs)
+ code
+ result, result_rate = ee_us_941_fica_m_add(payslip, categories, worked_days, inputs)
+
+
+
+
+
\ No newline at end of file
diff --git a/l10n_us_hr_payroll/data/federal/fed_941_fit_parameters.xml b/l10n_us_hr_payroll/data/federal/fed_941_fit_parameters.xml
new file mode 100644
index 00000000..89330246
--- /dev/null
+++ b/l10n_us_hr_payroll/data/federal/fed_941_fit_parameters.xml
@@ -0,0 +1,492 @@
+
+
+
+
+
+ Federal 941 FIT Allowance
+ fed_941_fit_allowance
+
+ {
+ 'weekly': 80.80,
+ 'bi-weekly': 161.50,
+ 'semi-monthly': 175.00,
+ 'monthly': 350.00,
+ 'quarterly': 1050.00,
+ 'semi-annually': 2100.00,
+ 'annually': 4200.00,
+ }
+
+
+
+ Federal 941 FIT Allowance
+ fed_941_fit_allowance
+ {
+ 'weekly': 80.80,
+ 'bi-weekly': 161.50,
+ 'semi-monthly': 175.00,
+ 'monthly': 350.00,
+ 'quarterly': 1050.00,
+ 'semi-annually': 2100.00,
+ 'annually': 4200.00,
+ }
+
+
+
+ Federal 941 FIT Allowance
+ fed_941_fit_allowance
+
+ 4300.0
+
+
+
+
+ Federal 941 FIT NRA Additional
+ fed_941_fit_nra_additional
+
+ {
+ 'weekly': 153.80,
+ 'bi-weekly': 307.70,
+ 'semi-monthly': 333.30,
+ 'monthly': 666.70,
+ 'quarterly': 2000.00,
+ 'semi-annually': 4000.00,
+ 'annually': 8000.00,
+ }
+
+
+
+ Federal 941 FIT NRA Additional
+ fed_941_fit_nra_additional
+ {
+ 'weekly': 153.80,
+ 'bi-weekly': 307.70,
+ 'semi-monthly': 333.30,
+ 'monthly': 666.70,
+ 'quarterly': 2000.00,
+ 'semi-annually': 4000.00,
+ 'annually': 8000.00,
+ }
+
+
+
+ Federal 941 FIT NRA Additional
+ fed_941_fit_nra_additional
+ {
+ 'weekly': 238.50,
+ 'bi-weekly': 476.90,
+ 'semi-monthly': 516.70,
+ 'monthly': 1033.30,
+ 'quarterly': 3100.00,
+ 'semi-annually': 6200.00,
+ 'annually': 12400.00,
+ }
+
+
+
+
+
+ Federal 941 FIT Table Single
+ fed_941_fit_table_single
+
+
+ {
+ 'weekly': [
+ ( 73.00, 0.00, 0),
+ ( 260.00, 0.00, 10),
+ ( 832.00, 18.70, 12),
+ ( 1692.00, 87.34, 22),
+ ( 3164.00, 276.54, 24),
+ ( 3998.00, 629.82, 32),
+ ( 9887.00, 896.70, 35),
+ ( 'inf', 2957.85, 37),
+ ],
+ 'bi-weekly': [
+ ( 146.00, 0.00, 0),
+ ( 519.00, 0.00, 10),
+ ( 1664.00, 37.30, 12),
+ ( 3385.00, 174.70, 22),
+ ( 6328.00, 553.32, 24),
+ ( 7996.00, 1259.64, 32),
+ ( 19773.00, 1793.40, 35),
+ ( 'inf', 5915.35, 37),
+ ],
+ 'semi-monthly': [
+ ( 158.00, 0.00, 0),
+ ( 563.00, 0.00, 10),
+ ( 1803.00, 40.50, 12),
+ ( 3667.00, 189.30, 22),
+ ( 6855.00, 599.38, 24),
+ ( 8663.00, 1364.50, 32),
+ ( 21421.00, 1943.06, 35),
+ ( 'inf', 6408.36, 37),
+ ],
+ 'monthly': [
+ ( 317.00, 0.00, 0),
+ ( 1125.00, 0.00, 10),
+ ( 3606.00, 80.80, 12),
+ ( 7333.00, 378.52, 22),
+ ( 13710.00, 1198.46, 24),
+ ( 17325.00, 2728.94, 32),
+ ( 42842.00, 3885.74, 35),
+ ( 'inf', 12816.69, 37),
+ ],
+ 'quarterly': [
+ ( 950.00, 0.00, 0),
+ ( 3375.00, 0.00, 10),
+ ( 10819.00, 242.50, 12),
+ ( 22000.00, 1135.78, 22),
+ ( 41131.00, 3595.60, 24),
+ ( 51975.00, 8187.04, 32),
+ ( 128525.00, 11657.12, 35),
+ ( 'inf', 38449.62, 37),
+ ],
+ 'semi-annually': [
+ ( 1900.00, 0.00, 0),
+ ( 6750.00, 0.00, 10),
+ ( 21638.00, 485.00, 12),
+ ( 44000.00, 2271.56, 22),
+ ( 82263.00, 7191.20, 24),
+ ( 103950.00, 16374.32, 32),
+ ( 257050.00, 23314.16, 35),
+ ( 'inf', 76899.16, 37),
+ ],
+ 'annually': [
+ ( 3800.00, 0.00, 0),
+ ( 13500.00, 0.00, 10),
+ ( 43275.00, 970.00, 12),
+ ( 88000.00, 4543.00, 22),
+ ( 164525.00, 14382.50, 24),
+ ( 207900.00, 32748.50, 32),
+ ( 514100.00, 46628.50, 35),
+ ( 'inf', 153798.50, 37),
+ ],
+ }
+
+
+
+ Federal 941 FIT Table Single
+ fed_941_fit_table_single
+
+ {
+ 'weekly': [
+ ( 73.00, 0.00, 0),
+ ( 260.00, 0.00, 10),
+ ( 832.00, 18.70, 12),
+ ( 1692.00, 87.34, 22),
+ ( 3164.00, 276.54, 24),
+ ( 3998.00, 629.82, 32),
+ ( 9887.00, 896.70, 35),
+ ( 'inf', 2957.85, 37),
+ ],
+ 'bi-weekly': [
+ ( 146.00, 0.00, 0),
+ ( 519.00, 0.00, 10),
+ ( 1664.00, 37.30, 12),
+ ( 3385.00, 174.70, 22),
+ ( 6328.00, 553.32, 24),
+ ( 7996.00, 1259.64, 32),
+ ( 19773.00, 1793.40, 35),
+ ( 'inf', 5915.35, 37),
+ ],
+ 'semi-monthly': [
+ ( 158.00, 0.00, 0),
+ ( 563.00, 0.00, 10),
+ ( 1803.00, 40.50, 12),
+ ( 3667.00, 189.30, 22),
+ ( 6855.00, 599.38, 24),
+ ( 8663.00, 1364.50, 32),
+ ( 21421.00, 1943.06, 35),
+ ( 'inf', 6408.36, 37),
+ ],
+ 'monthly': [
+ ( 317.00, 0.00, 0),
+ ( 1125.00, 0.00, 10),
+ ( 3606.00, 80.80, 12),
+ ( 7333.00, 378.52, 22),
+ ( 13710.00, 1198.46, 24),
+ ( 17325.00, 2728.94, 32),
+ ( 42842.00, 3885.74, 35),
+ ( 'inf', 12816.69, 37),
+ ],
+ 'quarterly': [
+ ( 950.00, 0.00, 0),
+ ( 3375.00, 0.00, 10),
+ ( 10819.00, 242.50, 12),
+ ( 22000.00, 1135.78, 22),
+ ( 41131.00, 3595.60, 24),
+ ( 51975.00, 8187.04, 32),
+ ( 128525.00, 11657.12, 35),
+ ( 'inf', 38449.62, 37),
+ ],
+ 'semi-annually': [
+ ( 1900.00, 0.00, 0),
+ ( 6750.00, 0.00, 10),
+ ( 21638.00, 485.00, 12),
+ ( 44000.00, 2271.56, 22),
+ ( 82263.00, 7191.20, 24),
+ ( 103950.00, 16374.32, 32),
+ ( 257050.00, 23314.16, 35),
+ ( 'inf', 76899.16, 37),
+ ],
+ 'annually': [
+ ( 3800.00, 0.00, 0),
+ ( 13500.00, 0.00, 10),
+ ( 43275.00, 970.00, 12),
+ ( 88000.00, 4543.00, 22),
+ ( 164525.00, 14382.50, 24),
+ ( 207900.00, 32748.50, 32),
+ ( 514100.00, 46628.50, 35),
+ ( 'inf', 153798.50, 37),
+ ],
+ }
+
+
+
+ Federal 941 FIT Table Single
+ fed_941_fit_table_single
+
+
+ {
+ 'standard': [
+ ( 0.00, 0.00, 0.00),
+ ( 3800.00, 0.00, 0.10),
+ ( 13675.00, 987.50, 0.12),
+ ( 43925.00, 4617.50, 0.22),
+ ( 89325.00, 14605.50, 0.24),
+ ( 167100.00, 33271.50, 0.32),
+ ( 211150.00, 47367.50, 0.35),
+ ( 522200.00, 156235.00, 0.37),
+ ],
+ 'higher': [
+ ( 0.00, 0.00, 0.00),
+ ( 6200.00, 0.00, 0.10),
+ ( 11138.00, 493.75, 0.12),
+ ( 26263.00, 2308.75, 0.22),
+ ( 48963.00, 7302.75, 0.24),
+ ( 87850.00, 16635.75, 0.32),
+ ( 109875.00, 23683.75, 0.35),
+ ( 265400.00, 78117.50, 0.37),
+ ],
+ }
+
+
+
+
+
+ Federal 941 FIT Table Married
+ fed_941_fit_table_married
+
+
+ {
+ 'weekly': [
+ ( 227.00, 0.00, 0),
+ ( 600.00, 0.00, 10),
+ ( 1745.00, 37.30, 12),
+ ( 3465.00, 174.70, 22),
+ ( 6409.00, 553.10, 24),
+ ( 8077.00, 1259.66, 32),
+ ( 12003.00, 1793.42, 35),
+ ( 'inf', 3167.52, 37),
+ ],
+ 'bi-weekly': [
+ ( 454.00, 0.00, 0),
+ ( 1200.00, 0.00, 10),
+ ( 3490.00, 74.60, 12),
+ ( 6931.00, 349.40, 22),
+ ( 12817.00, 1106.42, 24),
+ ( 16154.00, 2519.06, 32),
+ ( 24006.00, 3586.90, 35),
+ ( 'inf', 6335.10, 37),
+ ],
+ 'semi-monthly': [
+ ( 492.00, 0.00, 0),
+ ( 1300.00, 0.00, 10),
+ ( 3781.00, 80.80, 12),
+ ( 7508.00, 378.52, 22),
+ ( 13885.00, 1198.46, 24),
+ ( 17500.00, 2728.94, 32),
+ ( 26006.00, 3885.74, 35),
+ ( 'inf', 6862.84, 37),
+ ],
+ 'monthly': [
+ ( 983.00, 0.00, 0),
+ ( 2600.00, 0.00, 10),
+ ( 7563.00, 161.70, 12),
+ ( 15017.00, 757.26, 22),
+ ( 27771.00, 2397.14, 24),
+ ( 35000.00, 5458.10, 32),
+ ( 52013.00, 7771.38, 35),
+ ( 'inf', 13725.93, 37),
+ ],
+ 'quarterly': [
+ ( 2950.00, 0.00, 0),
+ ( 7800.00, 0.00, 10),
+ ( 22688.00, 485.00, 12),
+ ( 45050.00, 2271.56, 22),
+ ( 83313.00, 7191.20, 24),
+ ( 105000.00, 16374.32, 32),
+ ( 156038.00, 23314.16, 35),
+ ( 'inf', 41177.46, 37),
+ ],
+ 'semi-annually': [
+ ( 5900.00, 0.00, 0),
+ ( 15600.00, 0.00, 10),
+ ( 45375.00, 970.00, 12),
+ ( 90100.00, 4543.00, 22),
+ ( 166625.00, 14382.50, 24),
+ ( 210000.00, 32748.50, 32),
+ ( 312075.00, 46628.50, 35),
+ ( 'inf', 82354.75, 37),
+ ],
+ 'annually': [
+ ( 11800.00, 0.00, 0),
+ ( 31200.00, 0.00, 10),
+ ( 90750.00, 1940.00, 12),
+ ( 180200.00, 9086.00, 22),
+ ( 333250.00, 28765.00, 24),
+ ( 420000.00, 65497.00, 32),
+ ( 624150.00, 93257.00, 35),
+ ( 'inf', 164709.50, 37),
+ ],
+ }
+
+
+
+ Federal 941 FIT Table Married
+ fed_941_fit_table_married
+
+ {
+ 'weekly': [
+ ( 227.00, 0.00, 0),
+ ( 600.00, 0.00, 10),
+ ( 1745.00, 37.30, 12),
+ ( 3465.00, 174.70, 22),
+ ( 6409.00, 553.10, 24),
+ ( 8077.00, 1259.66, 32),
+ ( 12003.00, 1793.42, 35),
+ ( 'inf', 3167.52, 37),
+ ],
+ 'bi-weekly': [
+ ( 454.00, 0.00, 0),
+ ( 1200.00, 0.00, 10),
+ ( 3490.00, 74.60, 12),
+ ( 6931.00, 349.40, 22),
+ ( 12817.00, 1106.42, 24),
+ ( 16154.00, 2519.06, 32),
+ ( 24006.00, 3586.90, 35),
+ ( 'inf', 6335.10, 37),
+ ],
+ 'semi-monthly': [
+ ( 492.00, 0.00, 0),
+ ( 1300.00, 0.00, 10),
+ ( 3781.00, 80.80, 12),
+ ( 7508.00, 378.52, 22),
+ ( 13885.00, 1198.46, 24),
+ ( 17500.00, 2728.94, 32),
+ ( 26006.00, 3885.74, 35),
+ ( 'inf', 6862.84, 37),
+ ],
+ 'monthly': [
+ ( 983.00, 0.00, 0),
+ ( 2600.00, 0.00, 10),
+ ( 7563.00, 161.70, 12),
+ ( 15017.00, 757.26, 22),
+ ( 27771.00, 2397.14, 24),
+ ( 35000.00, 5458.10, 32),
+ ( 52013.00, 7771.38, 35),
+ ( 'inf', 13725.93, 37),
+ ],
+ 'quarterly': [
+ ( 2950.00, 0.00, 0),
+ ( 7800.00, 0.00, 10),
+ ( 22688.00, 485.00, 12),
+ ( 45050.00, 2271.56, 22),
+ ( 83313.00, 7191.20, 24),
+ ( 105000.00, 16374.32, 32),
+ ( 156038.00, 23314.16, 35),
+ ( 'inf', 41177.46, 37),
+ ],
+ 'semi-annually': [
+ ( 5900.00, 0.00, 0),
+ ( 15600.00, 0.00, 10),
+ ( 45375.00, 970.00, 12),
+ ( 90100.00, 4543.00, 22),
+ ( 166625.00, 14382.50, 24),
+ ( 210000.00, 32748.50, 32),
+ ( 312075.00, 46628.50, 35),
+ ( 'inf', 82354.75, 37),
+ ],
+ 'annually': [
+ ( 11800.00, 0.00, 0),
+ ( 31200.00, 0.00, 10),
+ ( 90750.00, 1940.00, 12),
+ ( 180200.00, 9086.00, 22),
+ ( 333250.00, 28765.00, 24),
+ ( 420000.00, 65497.00, 32),
+ ( 624150.00, 93257.00, 35),
+ ( 'inf', 164709.50, 37),
+ ],
+ }
+
+
+
+ Federal 941 FIT Table Married
+ fed_941_fit_table_married
+
+
+ {
+ 'standard': [
+ ( 0.00, 0.00, 0.00),
+ ( 11900.00, 0.00, 0.10),
+ ( 31650.00, 1975.00, 0.12),
+ ( 92150.00, 9235.00, 0.22),
+ ( 182950.00, 29211.00, 0.24),
+ ( 338500.00, 66543.00, 0.32),
+ ( 426600.00, 94735.00, 0.35),
+ ( 633950.00, 167307.50, 0.37),
+ ],
+ 'higher': [
+ ( 0.00, 0.00, 0.00),
+ ( 12400.00, 0.00, 0.10),
+ ( 22275.00, 987.50, 0.12),
+ ( 52525.00, 4617.50, 0.22),
+ ( 97925.00, 14605.50, 0.24),
+ ( 175700.00, 33271.50, 0.32),
+ ( 219750.00, 47367.50, 0.35),
+ ( 323425.00, 83653.75, 0.37),
+ ],
+ }
+
+
+
+
+ Federal 941 FIT Table Head of Household
+ fed_941_fit_table_hh
+
+
+ {
+ 'standard': [
+ ( 0.00, 0.00, 0.00),
+ ( 10050.00, 0.00, 0.10),
+ ( 24150.00, 1410.00, 0.12),
+ ( 63750.00, 6162.00, 0.22),
+ ( 95550.00, 13158.00, 0.24),
+ ( 173350.00, 31830.00, 0.32),
+ ( 217400.00, 45926.00, 0.35),
+ ( 528450.00, 154793.50, 0.37),
+ ],
+ 'higher': [
+ ( 0.00, 0.00, 0.00),
+ ( 9325.00, 0.00, 0.10),
+ ( 16375.00, 705.00, 0.12),
+ ( 36175.00, 3081.00, 0.22),
+ ( 52075.00, 6579.00, 0.24),
+ ( 90975.00, 15915.00, 0.32),
+ ( 113000.00, 22963.00, 0.35),
+ ( 268525.00, 77396.75, 0.37),
+ ],
+ }
+
+
+
+
\ No newline at end of file
diff --git a/l10n_us_hr_payroll/data/federal/fed_941_fit_rules.xml b/l10n_us_hr_payroll/data/federal/fed_941_fit_rules.xml
new file mode 100644
index 00000000..8a4612af
--- /dev/null
+++ b/l10n_us_hr_payroll/data/federal/fed_941_fit_rules.xml
@@ -0,0 +1,28 @@
+
+
+
+
+ WAGE: Federal 941 Income Tax Exempt
+ WAGE_US_941_FIT_EXEMPT
+
+
+
+ EE: Federal 941 Income Tax Withholding
+ EE_US_941_FIT
+
+
+
+
+
+
+ EE: US Federal Income Tax Withholding
+ EE_US_941_FIT
+ python
+ result, _ = ee_us_941_fit(payslip, categories, worked_days, inputs)
+ code
+ result, result_rate = ee_us_941_fit(payslip, categories, worked_days, inputs)
+
+
+
+
+
\ No newline at end of file
diff --git a/l10n_us_hr_payroll/data/final.xml b/l10n_us_hr_payroll/data/final.xml
old mode 100755
new mode 100644
index a1ae2fd8..b2f700cc
--- a/l10n_us_hr_payroll/data/final.xml
+++ b/l10n_us_hr_payroll/data/final.xml
@@ -1,29 +1,26 @@
-
-
+
US_EMP
USA Employee
-
+
-
diff --git a/l10n_us_hr_payroll/data/integration_rules.xml b/l10n_us_hr_payroll/data/integration_rules.xml
new file mode 100644
index 00000000..9ecec93b
--- /dev/null
+++ b/l10n_us_hr_payroll/data/integration_rules.xml
@@ -0,0 +1,27 @@
+
+
+
+
+ python
+ result = inputs.COMMISSION.amount > 0.0 if inputs.COMMISSION else False
+ code
+ result = inputs.COMMISSION.amount if inputs.COMMISSION else 0
+ BASIC_COM
+
+ Commissions
+
+
+
+
+
+ python
+ result = inputs.BADGES.amount > 0.0 if inputs.BADGES else False
+ code
+ result = inputs.BADGES.amount if inputs.BADGES else 0
+ BASIC_BADGES
+
+ Badges
+
+
+
+
\ No newline at end of file
diff --git a/l10n_us_hr_payroll/data/rates.xml b/l10n_us_hr_payroll/data/rates.xml
deleted file mode 100644
index 6af3f48e..00000000
--- a/l10n_us_hr_payroll/data/rates.xml
+++ /dev/null
@@ -1,68 +0,0 @@
-
-
-
-
- US FUTA Exempt
- US_FUTA_EXEMPT
- 0.0
- 2016-01-01
-
-
-
- US FUTA Normal
- US_FUTA_NORMAL
- 0.6
- 2016-01-01
-
-
-
- US FUTA Basic
- US_FUTA_BASIC
- 6.0
- 2016-01-01
-
-
-
-
-
-
- US FICA Social Security
- US_FICA_SS
- 6.2
- 2016-01-01
- 2017-12-31
-
-
-
- US FICA Social Security
- US_FICA_SS
- 6.2
- 2018-01-01
- 2018-12-31
-
-
-
- US FICA Social Security
- US_FICA_SS
- 6.2
- 2019-01-01
- 2019-12-31
-
-
-
-
- US FICA Medicare
- US_FICA_M
- 1.45
- 2016-01-01
-
-
-
- US FICA Medicare Additional
- US_FICA_M_ADD
- 0.9
- 2016-01-01
- 200000.0
-
-
-
\ No newline at end of file
diff --git a/l10n_us_hr_payroll/data/rules.xml b/l10n_us_hr_payroll/data/rules.xml
deleted file mode 100755
index 469f62f7..00000000
--- a/l10n_us_hr_payroll/data/rules.xml
+++ /dev/null
@@ -1,1058 +0,0 @@
-
-
-
-
-
-
-
-
-
- Wage: US FICA Social Security
- WAGE_US_FICA_SS
- python
- result = not contract.fica_exempt
- code
-
-###
-year = payslip.dict.date_to.year
-ytd = payslip.sum('WAGE_US_FICA_SS', str(year) + '-01-01', str(year+1) + '-01-01')
-ytd += contract.external_wages
-rate = payslip.dict.get_rate('US_FICA_SS')
-remaining = rate.wage_limit_year - ytd
-
-if remaining <= 0.0:
- result = 0
-elif remaining < categories.BASIC:
- result = remaining
-else:
- result = categories.BASIC
-
-
-
-
-
-
- Wage: US FICA Medicare
- WAGE_US_FICA_M
- python
- result = not contract.fica_exempt
- code
- result = categories.BASIC
-
-
-
-
-
- Wage: US FICA Medicare Additional
- WAGE_US_FICA_M_ADD
- python
- result = not contract.fica_exempt
- code
-
-###
-rate = payslip.dict.get_rate('US_FICA_M_ADD')
-ADD_M = rate.wage_limit_year
-year = payslip.dict.date_to.year
-norm_med_ytd = payslip.sum('WAGE_US_FICA_M', str(year) + '-01-01', str(year+1) + '-01-01')
-norm_med_cur = categories.WAGE_US_FICA_M
-
-if ADD_M > norm_med_ytd:
- diff = ADD_M - norm_med_ytd
- if norm_med_cur > diff:
- result = norm_med_cur - diff
- else:
- result = 0 # normal condition
-else:
- result = norm_med_cur # after YTD wages have passed the max
-
-
-
-
-
-
-
- EE: US FICA Social Security
- EE_US_FICA_SS
- python
- result = not contract.fica_exempt
- code
-
-rate = payslip.dict.get_rate('US_FICA_SS')
-result_rate = -rate.rate
-result = categories.WAGE_US_FICA_SS
-
-
-
-
-
-
- EE: US FICA Medicare
- EE_US_FICA_M
- python
- result = not contract.fica_exempt
- code
-
-rate = payslip.dict.get_rate('US_FICA_M')
-result_rate = -rate.rate
-result = categories.WAGE_US_FICA_M
-
-
-
-
-
-
- EE: US FICA Medicare Additional
- EE_US_FICA_M_ADD
- python
- result = not contract.fica_exempt
- code
-
-rate = payslip.dict.get_rate('US_FICA_M_ADD')
-result_rate = -rate.rate
-result = categories.WAGE_US_FICA_M_ADD
-
-
-
-
-
-
-
-
- EE: US Federal Income Tax Withholding - Single
- EE_US_FED_INC_WITHHOLD_S
- python
- result = (contract.w4_filing_status != 'married' and contract.w4_filing_status)
- code
-
-year = payslip.dict.date_to.year
-wages = categories.GROSS
-allowances = contract.w4_allowances
-is_nra = contract.w4_is_nonresident_alien
-schedule_pay = contract.schedule_pay
-val = 0.00
-additional = contract.w4_additional_withholding
-
-if year == 2018:
- ###
- # Single WEEKLY
- ###
- if 'weekly' == schedule_pay:
- wages -= allowances * 79.80
- if is_nra:
- wages += 151.00
-
- if wages > 71 and wages <= 254:
- val = 0.00 + ((wages - 71) * 0.10)
-
- elif wages > 254 and wages <= 815:
- val = 18.30 + ((wages - 254) * 0.12)
-
- elif wages > 815 and wages <= 1658:
- val = 85.62 + ((wages - 815) * 0.22)
-
- elif wages > 1658 and wages <= 3100:
- val = 271.08 + ((wages - 1658) * 0.24)
-
- elif wages > 3100 and wages <= 3917:
- val = 617.16 + ((wages - 3100) * 0.32)
-
- elif wages > 3917 and wages <= 9687:
- val = 878.60 + ((wages - 3917) * 0.35)
-
- elif wages > 9687:
- val = 2898.10 + ((wages - 9687) * 0.37)
-
- ###
- # Single BIWEEKLY
- ###
- elif 'bi-weekly' == schedule_pay:
- wages -= allowances * 159.60
- if is_nra:
- wages += 301.90
-
- if wages > 142 and wages <= 509:
- val = 0.00 + ((wages - 142) * 0.10)
-
- elif wages > 509 and wages <= 1631:
- val = 36.70 + ((wages - 509) * 0.12)
-
- elif wages > 1631 and wages <= 3315:
- val = 171.34 + ((wages - 1631) * 0.22)
-
- elif wages > 3315 and wages <= 6200:
- val = 541.82 + ((wages - 3315) * 0.24)
-
- elif wages > 6200 and wages <= 7835:
- val = 1234.22 + ((wages - 6200) * 0.32)
-
- elif wages > 7835 and wages <= 19373:
- val = 1757.42 + ((wages - 7835) * 0.35)
-
- elif wages > 19373:
- val = 5795.72 + ((wages - 19373) * 0.37)
-
- ###
- # Single SEMIMONTHLY
- ###
- elif 'semi-monthly' == schedule_pay:
- wages -= allowances * 172.90
- if is_nra:
- wages += 327.10
-
- if wages > 154 and wages <= 551:
- val = 0.00 + ((wages - 154) * 0.10)
-
- elif wages > 551 and wages <= 1767:
- val = 39.70 + ((wages - 551) * 0.12)
-
- elif wages > 1767 and wages <= 3592:
- val = 185.62 + ((wages - 1767) * 0.22)
-
- elif wages > 3592 and wages <= 6717:
- val = 587.12 + ((wages - 3592) * 0.24)
-
- elif wages > 6717 and wages <= 8488:
- val = 1337.12 + ((wages - 6717) * 0.32)
-
- elif wages > 8488 and wages <= 20988:
- val = 1903.84 + ((wages - 8488) * 0.35)
-
- elif wages > 20988:
- val = 6278.84 + ((wages - 20988) * 0.37)
-
- ###
- # Single MONTHLY
- ###
- elif 'monthly' == schedule_pay:
- wages -= allowances * 345.80
- if is_nra:
- wages += 654.20
-
- if wages > 308 and wages <= 1102:
- val = 0.00 + ((wages - 308) * 0.10)
-
- elif wages > 1102 and wages <= 3533:
- val = 79.40 + ((wages - 1102) * 0.12)
-
- elif wages > 3533 and wages <= 7183:
- val = 371.12 + ((wages - 3533) * 0.22)
-
- elif wages > 7183 and wages <= 13433:
- val = 1174.12 + ((wages - 7183) * 0.24)
-
- elif wages > 13433 and wages <= 16975:
- val = 2674.12 + ((wages - 13433) * 0.32)
-
- elif wages > 16975 and wages <= 41975:
- val = 3807.56 + ((wages - 16975) * 0.35)
-
- elif wages > 41975:
- val = 12557.56 + ((wages - 41975) * 0.37)
-
- ###
- # Single QUARTERLY
- ###
- elif 'quarterly' == schedule_pay:
- wages -= allowances * 1037.50
- if is_nra:
- wages += 1962.50
-
- if wages > 925 and wages <= 3306:
- val = 0.00 + ((wages - 925) * 0.10)
-
- elif wages > 3306 and wages <= 10600:
- val = 238.10 + ((wages - 3306) * 0.12)
-
- elif wages > 10600 and wages <= 21550:
- val = 1113.38 + ((wages - 10600) * 0.22)
-
- elif wages > 21550 and wages <= 40300:
- val = 3522.38 + ((wages - 21550) * 0.24)
-
- elif wages > 40300 and wages <= 50925:
- val = 8022.38 + ((wages - 40300) * 0.32)
-
- elif wages > 50925 and wages <= 125925:
- val = 11422.38 + ((wages - 50925) * 0.35)
-
- elif wages > 125925:
- val = 37672.38 + ((wages - 125925) * 0.37)
-
- ###
- # Single SEMIANNUAL
- ###
- elif 'semi-annually' == schedule_pay:
- wages -= allowances * 2075.00
- if is_nra:
- wages += 3925.00
-
- if wages > 1850 and wages <= 6613:
- val = 0.00 + ((wages - 1850) * 0.10)
-
- elif wages > 6613 and wages <= 21200:
- val = 476.30 + ((wages - 6613) * 0.12)
-
- elif wages > 21200 and wages <= 43100:
- val = 2226.74 + ((wages - 21200) * 0.22)
-
- elif wages > 43100 and wages <= 80600:
- val = 7044.74 + ((wages - 43100) * 0.24)
-
- elif wages > 80600 and wages <= 101850:
- val = 16044.74 + ((wages - 80600) * 0.32)
-
- elif wages > 101850 and wages <= 251850:
- val = 22844.74 + ((wages - 101850) * 0.35)
-
- elif wages > 251850:
- val = 75344.74 + ((wages - 251850) * 0.37)
-
- ###
- # Single ANNUAL
- ###
- elif 'annually' == schedule_pay:
- wages -= allowances * 4150.00
- if is_nra:
- wages += 7850.00
-
- if wages > 3700 and wages <= 13225:
- val = 0.00 + ((wages - 3700) * 0.10)
-
- elif wages > 13225 and wages <= 42400:
- val = 952.50 + ((wages - 13225) * 0.12)
-
- elif wages > 42400 and wages <= 86200:
- val = 4453.50 + ((wages - 42400) * 0.22)
-
- elif wages > 86200 and wages <= 161200:
- val = 14089.50 + ((wages - 86200) * 0.24)
-
- elif wages > 161200 and wages <= 203700:
- val = 32089.50 + ((wages - 161200) * 0.32)
-
- elif wages > 203700 and wages <= 503700:
- val = 45689.50 + ((wages - 203700) * 0.35)
-
- elif wages > 503700:
- val = 150689.50 + ((wages - 503700) * 0.37)
-
- else:
- raise Exception('Invalid schedule_pay="' + schedule_pay + '" for W4 Allowance calculation')
-else:
- ########
- # 2019 #
- ########
- # Single WEEKLY
- ###
- if 'weekly' == schedule_pay:
- wages -= allowances * 80.80
- if is_nra:
- wages += 153.80
-
- if wages > 73 and wages <= 260:
- val = 0.00 + ((wages - 73) * 0.10)
-
- elif wages > 260 and wages <= 832:
- val = 18.70 + ((wages - 260) * 0.12)
-
- elif wages > 832 and wages <= 1692:
- val = 87.34 + ((wages - 832) * 0.22)
-
- elif wages > 1692 and wages <= 3164:
- val = 276.54 + ((wages - 1692) * 0.24)
-
- elif wages > 3164 and wages <= 3998:
- val = 629.82 + ((wages - 3164) * 0.32)
-
- elif wages > 3998 and wages <= 9887:
- val = 896.70 + ((wages - 3998) * 0.35)
-
- elif wages > 9887:
- val = 2957.85 + ((wages - 9887) * 0.37)
-
- ###
- # Single BIWEEKLY
- ###
- elif 'bi-weekly' == schedule_pay:
- wages -= allowances * 161.50
- if is_nra:
- wages += 307.70
-
- if wages > 146 and wages <= 519:
- val = 0.00 + ((wages - 146) * 0.10)
-
- elif wages > 519 and wages <= 1664:
- val = 37.30 + ((wages - 519) * 0.12)
-
- elif wages > 1664 and wages <= 3385:
- val = 174.70 + ((wages - 1664) * 0.22)
-
- elif wages > 3385 and wages <= 6328:
- val = 553.32 + ((wages - 3385) * 0.24)
-
- elif wages > 6328 and wages <= 7996:
- val = 1259.64 + ((wages - 6328) * 0.32)
-
- elif wages > 7996 and wages <= 19773:
- val = 1793.40 + ((wages - 7996) * 0.35)
-
- elif wages > 19773:
- val = 5915.35 + ((wages - 19773) * 0.37)
-
- ###
- # Single SEMIMONTHLY
- ###
- elif 'semi-monthly' == schedule_pay:
- wages -= allowances * 175.00
- if is_nra:
- wages += 333.30
-
- if wages > 158 and wages <= 563:
- val = 0.00 + ((wages - 158) * 0.10)
-
- elif wages > 563 and wages <= 1803:
- val = 40.50 + ((wages - 563) * 0.12)
-
- elif wages > 1803 and wages <= 3667:
- val = 189.30 + ((wages - 1803) * 0.22)
-
- elif wages > 3667 and wages <= 6855:
- val = 599.38 + ((wages - 3667) * 0.24)
-
- elif wages > 6855 and wages <= 8663:
- val = 1364.50 + ((wages - 6855) * 0.32)
-
- elif wages > 8663 and wages <= 21421:
- val = 1943.06 + ((wages - 8663) * 0.35)
-
- elif wages > 21421:
- val = 6408.36 + ((wages - 21421) * 0.37)
-
- ###
- # Single MONTHLY
- ###
- elif 'monthly' == schedule_pay:
- wages -= allowances * 350.00
- if is_nra:
- wages += 666.70
-
- if wages > 317 and wages <= 1125:
- val = 0.00 + ((wages - 317) * 0.10)
-
- elif wages > 1125 and wages <= 3606:
- val = 80.80 + ((wages - 1125) * 0.12)
-
- elif wages > 3606 and wages <= 7333:
- val = 378.52 + ((wages - 3606) * 0.22)
-
- elif wages > 7333 and wages <= 13710:
- val = 1198.46 + ((wages - 7333) * 0.24)
-
- elif wages > 13710 and wages <= 17325:
- val = 2728.94 + ((wages - 13710) * 0.32)
-
- elif wages > 17325 and wages <= 42842:
- val = 3885.74 + ((wages - 17325) * 0.35)
-
- elif wages > 42842:
- val = 12816.69 + ((wages - 42842) * 0.37)
-
- ###
- # Single QUARTERLY
- ###
- elif 'quarterly' == schedule_pay:
- wages -= allowances * 1050.00
- if is_nra:
- wages += 2000.0
-
- if wages > 950 and wages <= 3375:
- val = 0.00 + ((wages - 950) * 0.10)
-
- elif wages > 3375 and wages <= 10819:
- val = 242.50 + ((wages - 3375) * 0.12)
-
- elif wages > 10819 and wages <= 22000:
- val = 1135.78 + ((wages - 10819) * 0.22)
-
- elif wages > 22000 and wages <= 41131:
- val = 3595.60 + ((wages - 22000) * 0.24)
-
- elif wages > 41131 and wages <= 51975:
- val = 8187.04 + ((wages - 41131) * 0.32)
-
- elif wages > 51975 and wages <= 128525:
- val = 11657.12 + ((wages - 51975) * 0.35)
-
- elif wages > 128525:
- val = 38449.62 + ((wages - 128525) * 0.37)
-
- ###
- # Single SEMIANNUAL
- ###
- elif 'semi-annually' == schedule_pay:
- wages -= allowances * 2100.00
- if is_nra:
- wages += 4000.00
-
- if wages > 1900 and wages <= 6750:
- val = 0.00 + ((wages - 1900) * 0.10)
-
- elif wages > 6750 and wages <= 21638:
- val = 485.00 + ((wages - 6750) * 0.12)
-
- elif wages > 21638 and wages <= 44000:
- val = 2271.56 + ((wages - 21638) * 0.22)
-
- elif wages > 44000 and wages <= 82263:
- val = 7191.20 + ((wages - 44000) * 0.24)
-
- elif wages > 82263 and wages <= 103950:
- val = 16374.32 + ((wages - 82263) * 0.32)
-
- elif wages > 103950 and wages <= 257050:
- val = 23314.16 + ((wages - 103950) * 0.35)
-
- elif wages > 257050:
- val = 76899.16 + ((wages - 257050) * 0.37)
-
- ###
- # Single ANNUAL
- ###
- elif 'annually' == schedule_pay:
- wages -= allowances * 4200.00
- if is_nra:
- wages += 8000.00
-
- if wages > 3800 and wages <= 13500:
- val = 0.00 + ((wages - 3800) * 0.10)
-
- elif wages > 13500 and wages <= 43275:
- val = 970.00 + ((wages - 13500) * 0.12)
-
- elif wages > 43275 and wages <= 88000:
- val = 4543.00 + ((wages - 43275) * 0.22)
-
- elif wages > 88000 and wages <= 164525:
- val = 14382.50 + ((wages - 88000) * 0.24)
-
- elif wages > 164525 and wages <= 207900:
- val = 32748.50 + ((wages - 164525) * 0.32)
-
- elif wages > 207900 and wages <= 514100:
- val = 46628.50 + ((wages - 207900) * 0.35)
-
- elif wages > 514100:
- val = 153798.50 + ((wages - 514100) * 0.37)
-
- else:
- raise Exception('Invalid schedule_pay="' + schedule_pay + '" for W4 Allowance calculation')
-
-result = -(val + additional)
-
-
-
-
-
-
- EE: US Federal Income Tax Withholding - Married
- EE_US_FED_INC_WITHHOLD_M
- python
- result = (contract.w4_filing_status == 'married')
- code
-
-year = payslip.dict.date_to.year
-wages = categories.GROSS
-allowances = contract.w4_allowances
-is_nra = contract.w4_is_nonresident_alien
-schedule_pay = contract.schedule_pay
-val = 0.00
-additional = contract.w4_additional_withholding
-
-if year == 2018:
- ###
- # Married WEEKLY
- ###
- if 'weekly' == schedule_pay:
- wages -= allowances * 79.80
- if is_nra:
- wages += 151.00
-
- if wages > 222 and wages <= 588:
- val = 0.00 + ((wages - 222) * 0.10)
-
- elif wages > 588 and wages <= 1711:
- val = 36.60 + ((wages - 588) * 0.12)
-
- elif wages > 1711 and wages <= 3395:
- val = 171.36 + ((wages - 1711) * 0.22)
-
- elif wages > 3395 and wages <= 6280:
- val = 541.84 + ((wages - 3395) * 0.24)
-
- elif wages > 6280 and wages <= 7914:
- val = 1234.24 + ((wages - 6280) * 0.32)
-
- elif wages > 7914 and wages <= 11761:
- val = 1757.12 + ((wages - 7914) * 0.35)
-
- elif wages > 11761:
- val = 3103.57 + ((wages - 11761) * 0.37)
-
- ###
- # Married BIWEEKLY
- ###
- elif 'bi-weekly' == schedule_pay:
- wages -= allowances * 159.60
- if is_nra:
- wages += 301.90
-
- if wages > 444 and wages <= 1177:
- val = 0.00 + ((wages - 444) * 0.10)
-
- elif wages > 1177 and wages <= 3421:
- val = 73.30 + ((wages - 1177) * 0.12)
-
- elif wages > 3421 and wages <= 6790:
- val = 342.58 + ((wages - 3421) * 0.22)
-
- elif wages > 6790 and wages <= 12560:
- val = 1083.76 + ((wages - 6790) * 0.24)
-
- elif wages > 12560 and wages <= 15829:
- val = 2468.56 + ((wages - 12560) * 0.32)
-
- elif wages > 15829 and wages <= 23521:
- val = 3514.64 + ((wages - 15829) * 0.35)
-
- elif wages > 23521:
- val = 6206.84 + ((wages - 23521) * 0.37)
-
- ###
- # Married SEMIMONTHLY
- ###
- elif 'semi-monthly' == schedule_pay:
- wages -= allowances * 172.90
- if is_nra:
- wages += 327.10
-
- if wages > 481 and wages <= 1275:
- val = 0.00 + ((wages - 481) * 0.10)
-
- elif wages > 1275 and wages <= 3706:
- val = 79.40 + ((wages - 1275) * 0.12)
-
- elif wages > 3706 and wages <= 7356:
- val = 371.12 + ((wages - 3706) * 0.22)
-
- elif wages > 7356 and wages <= 13606:
- val = 1174.12 + ((wages - 7356) * 0.24)
-
- elif wages > 13606 and wages <= 17148:
- val = 2674.12 + ((wages - 13606) * 0.32)
-
- elif wages > 17148 and wages <= 25481:
- val = 3807.56 + ((wages - 17148) * 0.35)
-
- elif wages > 25481:
- val = 6724.11 + ((wages - 25481) * 0.37)
-
- ###
- # Married MONTHLY
- ###
- elif 'monthly' == schedule_pay:
- wages -= allowances * 345.80
- if is_nra:
- wages += 654.20
-
- if wages > 963 and wages <= 2550:
- val = 0.00 + ((wages - 963) * 0.10)
-
- elif wages > 2550 and wages <= 7413:
- val = 158.70 + ((wages - 2550) * 0.12)
-
- elif wages > 7413 and wages <= 14713:
- val = 742.26 + ((wages - 7413) * 0.22)
-
- elif wages > 14713 and wages <= 27213:
- val = 2348.26 + ((wages - 14713) * 0.24)
-
- elif wages > 27213 and wages <= 34296:
- val = 5348.26 + ((wages - 27213) * 0.32)
-
- elif wages > 34296 and wages <= 50963:
- val = 7614.82 + ((wages - 34296) * 0.35)
-
- elif wages > 50963:
- val = 13448.27 + ((wages - 50963) * 0.37)
-
- ###
- # Married QUARTERLY
- ###
- elif 'quarterly' == schedule_pay:
- wages -= allowances * 1037.50
- if is_nra:
- wages += 1962.50
-
- if wages > 2888 and wages <= 7650:
- val = 0.00 + ((wages - 2888) * 0.10)
-
- elif wages > 7650 and wages <= 22238:
- val = 476.20 + ((wages - 7650) * 0.12)
-
- elif wages > 22238 and wages <= 44138:
- val = 2226.76 + ((wages - 22238) * 0.22)
-
- elif wages > 44138 and wages <= 81638:
- val = 7044.76 + ((wages - 44138) * 0.24)
-
- elif wages > 81638 and wages <= 102888:
- val = 16044.76 + ((wages - 81638) * 0.32)
-
- elif wages > 102888 and wages <= 152888:
- val = 22844.76 + ((wages - 102888) * 0.35)
-
- elif wages > 152888:
- val = 40344.76 + ((wages - 152888) * 0.37)
-
- ###
- # Married SEMIANNUAL
- ###
- elif 'semi-annually' == schedule_pay:
- wages -= allowances * 2075.00
- if is_nra:
- wages += 3925.00
-
- if wages > 5775 and wages <= 15300:
- val = 0.00 + ((wages - 5775) * 0.10)
-
- elif wages > 15300 and wages <= 44475:
- val = 952.50 + ((wages - 15300) * 0.12)
-
- elif wages > 44475 and wages <= 88275:
- val = 4453.50 + ((wages - 44475) * 0.22)
-
- elif wages > 88275 and wages <= 163275:
- val = 14089.50 + ((wages - 88275) * 0.24)
-
- elif wages > 163275 and wages <= 205775:
- val = 32089.50 + ((wages - 163275) * 0.32)
-
- elif wages > 205775 and wages <= 305775:
- val = 45689.50 + ((wages - 205775) * 0.35)
-
- elif wages > 305775:
- val = 80689.50 + ((wages - 305775) * 0.37)
-
- ###
- # Married ANNUAL
- ###
- elif 'annually' == schedule_pay:
- wages -= allowances * 4150.00
- if is_nra:
- wages += 7850.00
-
- if wages > 11550 and wages <= 30600:
- val = 0.00 + ((wages - 11550) * 0.10)
-
- elif wages > 30600 and wages <= 88950:
- val = 1905.00 + ((wages - 30600) * 0.12)
-
- elif wages > 88950 and wages <= 176550:
- val = 8907.00 + ((wages - 88950) * 0.22)
-
- elif wages > 176550 and wages <= 326550:
- val = 28179.00 + ((wages - 176550) * 0.24)
-
- elif wages > 326550 and wages <= 411550:
- val = 64179.00 + ((wages - 326550) * 0.32)
-
- elif wages > 411550 and wages <= 611550:
- val = 91379.00 + ((wages - 411550) * 0.35)
-
- elif wages > 611550:
- val = 161379.00 + ((wages - 611550) * 0.37)
-
- else:
- raise Exception('Invalid schedule_pay="' + schedule_pay + '" for W4 Allowance calculation')
-else:
- ########
- # 2019 #
- ########
- # Married WEEKLY
- ###
- if 'weekly' == schedule_pay:
- wages -= allowances * 80.80
- if is_nra:
- wages += 153.80
-
- if wages > 227 and wages <= 600:
- val = 0.00 + ((wages - 227) * 0.10)
-
- elif wages > 600 and wages <= 1745:
- val = 37.30 + ((wages - 600) * 0.12)
-
- elif wages > 1745 and wages <= 3465:
- val = 174.70 + ((wages - 1745) * 0.22)
-
- elif wages > 3465 and wages <= 6409:
- val = 553.10 + ((wages - 3465) * 0.24)
-
- elif wages > 6409 and wages <= 8077:
- val = 1259.66 + ((wages - 6409) * 0.32)
-
- elif wages > 8077 and wages <= 12003:
- val = 1793.42 + ((wages - 8077) * 0.35)
-
- elif wages > 12003:
- val = 3167.52 + ((wages - 12003) * 0.37)
-
- ###
- # Married BIWEEKLY
- ###
- elif 'bi-weekly' == schedule_pay:
- wages -= allowances * 161.50
- if is_nra:
- wages += 307.70
-
- if wages > 454 and wages <= 1200:
- val = 0.00 + ((wages - 454) * 0.10)
-
- elif wages > 1200 and wages <= 3490:
- val = 74.60 + ((wages - 1200) * 0.12)
-
- elif wages > 3490 and wages <= 6931:
- val = 349.40 + ((wages - 3490) * 0.22)
-
- elif wages > 6931 and wages <= 12817:
- val = 1106.42 + ((wages - 6931) * 0.24)
-
- elif wages > 12817 and wages <= 16154:
- val = 2519.06 + ((wages - 12817) * 0.32)
-
- elif wages > 16154 and wages <= 24006:
- val = 3586.90 + ((wages - 16154) * 0.35)
-
- elif wages > 24006:
- val = 6335.10 + ((wages - 24006) * 0.37)
-
- ###
- # Married SEMIMONTHLY
- ###
- elif 'semi-monthly' == schedule_pay:
- wages -= allowances * 175.00
- if is_nra:
- wages += 333.30
-
- if wages > 492 and wages <= 1300:
- val = 0.00 + ((wages - 492) * 0.10)
-
- elif wages > 1300 and wages <= 3781:
- val = 80.80 + ((wages - 1300) * 0.12)
-
- elif wages > 3781 and wages <= 7508:
- val = 378.52 + ((wages - 3781) * 0.22)
-
- elif wages > 7508 and wages <= 13885:
- val = 1198.46 + ((wages - 7508) * 0.24)
-
- elif wages > 13885 and wages <= 17500:
- val = 2728.94 + ((wages - 13885) * 0.32)
-
- elif wages > 17500 and wages <= 26006:
- val = 3885.74 + ((wages - 17500) * 0.35)
-
- elif wages > 26006:
- val = 6862.84 + ((wages - 26006) * 0.37)
-
- ###
- # Married MONTHLY
- ###
- elif 'monthly' == schedule_pay:
- wages -= allowances * 350.00
- if is_nra:
- wages += 666.70
-
- if wages > 983 and wages <= 2600:
- val = 0.00 + ((wages - 983) * 0.10)
-
- elif wages > 2600 and wages <= 7563:
- val = 161.70 + ((wages - 2600) * 0.12)
-
- elif wages > 7563 and wages <= 15017:
- val = 757.26 + ((wages - 7563) * 0.22)
-
- elif wages > 15017 and wages <= 27771:
- val = 2397.14 + ((wages - 15017) * 0.24)
-
- elif wages > 27771 and wages <= 35000:
- val = 5458.10 + ((wages - 27771) * 0.32)
-
- elif wages > 35000 and wages <= 50963:
- val = 7771.38 + ((wages - 35000) * 0.35)
-
- elif wages > 52013:
- val = 13725.93 + ((wages - 52013) * 0.37)
-
- ###
- # Married QUARTERLY
- ###
- elif 'quarterly' == schedule_pay:
- wages -= allowances * 1050.00
- if is_nra:
- wages += 2000.00
-
- if wages > 2950 and wages <= 7800:
- val = 0.00 + ((wages - 2950) * 0.10)
-
- elif wages > 7800 and wages <= 22688:
- val = 485.00 + ((wages - 7800) * 0.12)
-
- elif wages > 22688 and wages <= 45050:
- val = 2271.56 + ((wages - 22688) * 0.22)
-
- elif wages > 45050 and wages <= 83313:
- val = 7191.20 + ((wages - 45050) * 0.24)
-
- elif wages > 83313 and wages <= 105000:
- val = 16374.32 + ((wages - 83313) * 0.32)
-
- elif wages > 105000 and wages <= 156038:
- val = 23314.16 + ((wages - 105000) * 0.35)
-
- elif wages > 156038:
- val = 41177.46 + ((wages - 156038) * 0.37)
-
- ###
- # Married SEMIANNUAL
- ###
- elif 'semi-annually' == schedule_pay:
- wages -= allowances * 2100.00
- if is_nra:
- wages += 4000.00
-
- if wages > 5900 and wages <= 15600:
- val = 0.00 + ((wages - 5900) * 0.10)
-
- elif wages > 15600 and wages <= 45375:
- val = 970.00 + ((wages - 15600) * 0.12)
-
- elif wages > 45375 and wages <= 90100:
- val = 4543.00 + ((wages - 45375) * 0.22)
-
- elif wages > 90100 and wages <= 166625:
- val = 14382.50 + ((wages - 90100) * 0.24)
-
- elif wages > 166625 and wages <= 210000:
- val = 32748.50 + ((wages - 166625) * 0.32)
-
- elif wages > 210000 and wages <= 312075:
- val = 46628.50 + ((wages - 210000) * 0.35)
-
- elif wages > 312075:
- val = 82354.75 + ((wages - 312075) * 0.37)
-
- ###
- # Married ANNUAL
- ###
- elif 'annually' == schedule_pay:
- wages -= allowances * 4200.00
- if is_nra:
- wages += 8000.00
-
- if wages > 11800 and wages <= 31200:
- val = 0.00 + ((wages - 11800) * 0.10)
-
- elif wages > 31200 and wages <= 90750:
- val = 1940.00 + ((wages - 31200) * 0.12)
-
- elif wages > 90750 and wages <= 180200:
- val = 9086.00 + ((wages - 90750) * 0.22)
-
- elif wages > 180200 and wages <= 333250:
- val = 28765.00 + ((wages - 180200) * 0.24)
-
- elif wages > 333250 and wages <= 420000:
- val = 65497.00 + ((wages - 333250) * 0.32)
-
- elif wages > 420000 and wages <= 624150:
- val = 93257.00 + ((wages - 420000) * 0.35)
-
- elif wages > 624150:
- val = 164709.50 + ((wages - 624150) * 0.37)
-
- else:
- raise Exception('Invalid schedule_pay="' + schedule_pay + '" for W4 Allowance calculation')
-
-result = -(val + additional)
-
-
-
-
-
-
-
- Wage: US FUTA Federal Unemployment
- WAGE_US_FUTA
- python
- result = (contract.futa_type != contract.FUTA_TYPE_EXEMPT)
- code
-
-###
-rate = payslip.dict.get_futa_rate(contract)
-year = payslip.dict.date_to.year
-ytd = payslip.sum('WAGE_US_FUTA', str(year) + '-01-01', str(year+1) + '-01-01')
-ytd += contract.external_wages
-remaining = rate.wage_limit_year - ytd
-if remaining <= 0.0:
- result = 0
-elif remaining < categories.BASIC:
- result = remaining
-else:
- result = categories.BASIC
-
-
-
-
-
-
- ER: US FUTA Federal Unemployment
- ER_US_FUTA
- python
- result = (contract.futa_type != contract.FUTA_TYPE_EXEMPT)
- code
-
-year = payslip.dict.date_to.year
-rate = payslip.dict.get_futa_rate(contract)
-result_rate = -(rate.rate)
-result = categories.WAGE_US_FUTA
-
-
-
-
-
-
-
-
-
- ER: US FICA Social Security
- ER_US_FICA_SS
- none
- code
- result = categories.EE_US_FICA_SS
-
-
-
-
-
-
- ER: US FICA Medicare
- ER_US_FICA_M
- none
- code
- result = categories.EE_US_FICA_M
-
-
-
-
-
-
diff --git a/l10n_us_hr_payroll/models/__init__.py b/l10n_us_hr_payroll/models/__init__.py
index ff687165..c6d607ff 100644
--- a/l10n_us_hr_payroll/models/__init__.py
+++ b/l10n_us_hr_payroll/models/__init__.py
@@ -1 +1,5 @@
-from . import l10n_us_hr_payroll
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from . import hr_contract
+from . import hr_payslip
+from . import us_payroll_config
diff --git a/l10n_us_hr_payroll/models/federal/__init__.py b/l10n_us_hr_payroll/models/federal/__init__.py
new file mode 100644
index 00000000..0358305d
--- /dev/null
+++ b/l10n_us_hr_payroll/models/federal/__init__.py
@@ -0,0 +1 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
diff --git a/l10n_us_hr_payroll/models/federal/fed_940.py b/l10n_us_hr_payroll/models/federal/fed_940.py
new file mode 100644
index 00000000..ed475a75
--- /dev/null
+++ b/l10n_us_hr_payroll/models/federal/fed_940.py
@@ -0,0 +1,37 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+def er_us_940_futa(payslip, categories, worked_days, inputs):
+ """
+ Returns FUTA eligible wage and rate.
+ WAGE = GROSS - WAGE_US_940_FUTA_EXEMPT
+ :return: result, result_rate (wage, percent)
+ """
+
+ # Determine Rate.
+ if payslip.dict.contract_id.futa_type == payslip.dict.contract_id.FUTA_TYPE_EXEMPT:
+ # Exit early
+ return 0.0, 0.0
+ elif payslip.dict.contract_id.futa_type == payslip.dict.contract_id.FUTA_TYPE_BASIC:
+ result_rate = -payslip.dict.rule_parameter('fed_940_futa_rate_basic')
+ else:
+ result_rate = -payslip.dict.rule_parameter('fed_940_futa_rate_normal')
+
+ # Determine Wage
+ year = payslip.dict.get_year()
+ ytd_wage = payslip.sum_category('GROSS', str(year) + '-01-01', str(year+1) + '-01-01')
+ ytd_wage -= payslip.sum_category('WAGE_US_940_FUTA_EXEMPT', str(year) + '-01-01', str(year+1) + '-01-01')
+ ytd_wage += payslip.dict.contract_id.external_wages
+
+ wage_base = payslip.dict.rule_parameter('fed_940_futa_wage_base')
+ remaining = wage_base - ytd_wage
+
+ wage = categories.GROSS - categories.WAGE_US_940_FUTA_EXEMPT
+
+ if remaining < 0.0:
+ result = 0.0
+ elif remaining < wage:
+ result = remaining
+ else:
+ result = wage
+
+ return result, result_rate
diff --git a/l10n_us_hr_payroll/models/federal/fed_941.py b/l10n_us_hr_payroll/models/federal/fed_941.py
new file mode 100644
index 00000000..7a0916ec
--- /dev/null
+++ b/l10n_us_hr_payroll/models/federal/fed_941.py
@@ -0,0 +1,239 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+# import logging
+# _logger = logging.getLogger(__name__)
+
+
+def ee_us_941_fica_ss(payslip, categories, worked_days, inputs):
+ """
+ Returns FICA Social Security eligible wage and rate.
+ WAGE = GROSS - WAGE_US_941_FICA_EXEMPT
+ :return: result, result_rate (wage, percent)
+ """
+ exempt = payslip.dict.contract_id.us_payroll_config_value('fed_941_fica_exempt')
+ if exempt:
+ return 0.0, 0.0
+
+ # Determine Rate.
+ result_rate = -payslip.dict.rule_parameter('fed_941_fica_ss_rate')
+
+ # Determine Wage
+ year = payslip.dict.get_year()
+ ytd_wage = payslip.sum_category('GROSS', str(year) + '-01-01', str(year+1) + '-01-01')
+ ytd_wage -= payslip.sum_category('WAGE_US_941_FICA_EXEMPT', str(year) + '-01-01', str(year+1) + '-01-01')
+ ytd_wage += payslip.dict.contract_id.external_wages
+
+ wage_base = payslip.dict.rule_parameter('fed_941_fica_ss_wage_base')
+ remaining = wage_base - ytd_wage
+
+ wage = categories.GROSS - categories.WAGE_US_941_FICA_EXEMPT
+
+ if remaining < 0.0:
+ result = 0.0
+ elif remaining < wage:
+ result = remaining
+ else:
+ result = wage
+
+ return result, result_rate
+
+
+er_us_941_fica_ss = ee_us_941_fica_ss
+
+
+def ee_us_941_fica_m(payslip, categories, worked_days, inputs):
+ """
+ Returns FICA Medicare eligible wage and rate.
+ WAGE = GROSS - WAGE_US_941_FICA_EXEMPT
+ :return: result, result_rate (wage, percent)
+ """
+ exempt = payslip.dict.contract_id.us_payroll_config_value('fed_941_fica_exempt')
+ if exempt:
+ return 0.0, 0.0
+
+ # Determine Rate.
+ result_rate = -payslip.dict.rule_parameter('fed_941_fica_m_rate')
+
+ # Determine Wage
+ year = payslip.dict.get_year()
+ ytd_wage = payslip.sum_category('GROSS', str(year) + '-01-01', str(year+1) + '-01-01')
+ ytd_wage -= payslip.sum_category('WAGE_US_941_FICA_EXEMPT', str(year) + '-01-01', str(year+1) + '-01-01')
+ ytd_wage += payslip.dict.contract_id.external_wages
+
+ wage_base = float(payslip.dict.rule_parameter('fed_941_fica_m_wage_base')) # inf
+ remaining = wage_base - ytd_wage
+
+ wage = categories.GROSS - categories.WAGE_US_941_FICA_EXEMPT
+
+ if remaining < 0.0:
+ result = 0.0
+ elif remaining < wage:
+ result = remaining
+ else:
+ result = wage
+
+ return result, result_rate
+
+
+er_us_941_fica_m = ee_us_941_fica_m
+
+
+def ee_us_941_fica_m_add(payslip, categories, worked_days, inputs):
+ """
+ Returns FICA Medicare Additional eligible wage and rate.
+ Note that this wage is not capped like the above rules.
+ WAGE = GROSS - WAGE_FICA_EXEMPT
+ :return: result, result_rate (wage, percent)
+ """
+ exempt = payslip.dict.contract_id.us_payroll_config_value('fed_941_fica_exempt')
+ if exempt:
+ return 0.0, 0.0
+
+ # Determine Rate.
+ result_rate = -payslip.dict.rule_parameter('fed_941_fica_m_add_rate')
+
+ # Determine Wage
+ year = payslip.dict.get_year()
+ ytd_wage = payslip.sum_category('GROSS', str(year) + '-01-01', str(year+1) + '-01-01')
+ ytd_wage -= payslip.sum_category('WAGE_US_941_FICA_EXEMPT', str(year) + '-01-01', str(year+1) + '-01-01')
+ ytd_wage += payslip.dict.contract_id.external_wages
+
+ wage_start = payslip.dict.rule_parameter('fed_941_fica_m_add_wage_start')
+ existing_wage = ytd_wage - wage_start
+
+ wage = categories.GROSS - categories.WAGE_US_941_FICA_EXEMPT
+
+ if existing_wage >= 0.0:
+ result = wage
+ elif wage + existing_wage > 0.0:
+ result = wage + existing_wage
+ else:
+ result = 0.0
+
+ return result, result_rate
+
+
+# Federal Income Tax
+def ee_us_941_fit(payslip, categories, worked_days, inputs):
+ """
+ Returns Wage and rate that is computed given the amount to withhold.
+ WAGE = GROSS - WAGE_US_941_FIT_EXEMPT
+ :return: result, result_rate (wage, percent)
+ """
+ filing_status = payslip.dict.contract_id.us_payroll_config_value('fed_941_fit_w4_filing_status')
+ if not filing_status:
+ return 0.0, 0.0
+
+ schedule_pay = payslip.dict.contract_id.schedule_pay
+ wage = categories.GROSS - categories.WAGE_US_941_FIT_EXEMPT
+ #_logger.warn('initial gross wage: ' + str(wage))
+ year = payslip.dict.get_year()
+ if year >= 2020:
+ # Large changes in Federal Income Tax in 2020 and the W4
+ # We will assume that your W4 is the 2020 version
+ # Steps are from IRS Publication 15-T
+ #
+ # Step 1
+ working_wage = wage
+ is_nra = payslip.dict.contract_id.us_payroll_config_value('fed_941_fit_w4_is_nonresident_alien')
+ if is_nra:
+ nra_table = payslip.dict.rule_parameter('fed_941_fit_nra_additional')
+ working_wage += nra_table.get(schedule_pay, 0.0)
+ #_logger.warn(' is_nrm after wage: ' + str(working_wage))
+
+ pay_periods = payslip.dict.get_pay_periods_in_year()
+ wage_annual = pay_periods * working_wage
+ #_logger.warn('annual wage: ' + str(wage_annual))
+ wage_annual += payslip.dict.contract_id.us_payroll_config_value('fed_941_fit_w4_other_income')
+ #_logger.warn(' after other income: ' + str(wage_annual))
+
+ deductions = payslip.dict.contract_id.us_payroll_config_value('fed_941_fit_w4_deductions')
+ #_logger.warn('deductions from W4: ' + str(deductions))
+
+ higher_rate_type = payslip.dict.contract_id.us_payroll_config_value('fed_941_fit_w4_multiple_jobs_higher')
+ if not higher_rate_type:
+ deductions += 12900.0 if filing_status == 'married' else 8600.0
+ #_logger.warn(' deductions after standard deduction: ' + str(deductions))
+
+ adjusted_wage_annual = wage_annual - deductions
+ if adjusted_wage_annual < 0.0:
+ adjusted_wage_annual = 0.0
+ #_logger.warn('adusted annual wage: ' + str(adjusted_wage_annual))
+
+ # Step 2
+ if filing_status == 'single':
+ tax_tables = payslip.dict.rule_parameter('fed_941_fit_table_single')
+ elif filing_status == 'married':
+ tax_tables = payslip.dict.rule_parameter('fed_941_fit_table_married')
+ else:
+ # married_as_single for historic reasons
+ tax_tables = payslip.dict.rule_parameter('fed_941_fit_table_hh')
+
+ if higher_rate_type:
+ tax_table = tax_tables['higher']
+ else:
+ tax_table = tax_tables['standard']
+
+ selected_row = None
+ for row in tax_table:
+ if row[0] <= adjusted_wage_annual:
+ selected_row = row
+ else:
+ # First row where wage is higher than adjusted_wage_annual
+ break
+
+ wage_threshold, base_withholding_amount, marginal_rate = selected_row
+ #_logger.warn(' selected row: ' + str(selected_row))
+ working_wage = adjusted_wage_annual - wage_threshold
+ tentative_withholding_amount = (working_wage * marginal_rate) + base_withholding_amount
+ tentative_withholding_amount = tentative_withholding_amount / pay_periods
+ #_logger.warn('tenative withholding amount: ' + str(tentative_withholding_amount))
+
+ # Step 3
+ dependent_credit = payslip.dict.contract_id.us_payroll_config_value('fed_941_fit_w4_dependent_credit')
+ dependent_credit = dependent_credit / pay_periods
+ #_logger.warn('dependent credit (per period): ' + str(dependent_credit))
+ tentative_withholding_amount -= dependent_credit
+ if tentative_withholding_amount < 0.0:
+ tentative_withholding_amount = 0.0
+
+ # Step 4
+ withholding_amount = tentative_withholding_amount + payslip.dict.contract_id.us_payroll_config_value('fed_941_fit_w4_additional_withholding')
+ #_logger.warn('final withholding amount: ' + str(withholding_amount))
+ # Ideally we would set the 'taxable wage' as the result and compute the percentage tax.
+ # This is off by 1 penny across our tests, but I feel like it is worth it for the added reporting.
+ # - Jared Kipe 2019 during Odoo 13.0 rewrite.
+ #
+ # return -withholding_amount, 100.0
+ return wage, -(withholding_amount / wage * 100.0)
+ else:
+ working_wage = wage
+ is_nra = payslip.dict.contract_id.us_payroll_config_value('fed_941_fit_w4_is_nonresident_alien')
+ if is_nra:
+ nra_table = payslip.dict.rule_parameter('fed_941_fit_nra_additional')
+ working_wage += nra_table[schedule_pay]
+
+ allowance_table = payslip.dict.rule_parameter('fed_941_fit_allowance')
+ allowances = payslip.dict.contract_id.us_payroll_config_value('fed_941_fit_w4_allowances')
+ working_wage -= allowance_table[schedule_pay] * allowances
+ tax = 0.0
+ last_limit = 0.0
+ if filing_status == 'married':
+ tax_table = payslip.dict.rule_parameter('fed_941_fit_table_married')
+ else:
+ tax_table = payslip.dict.rule_parameter('fed_941_fit_table_single')
+ for row in tax_table[schedule_pay]:
+ limit, base, percent = row
+ limit = float(limit) # 'inf'
+ if working_wage <= limit:
+ tax = base + ((working_wage - last_limit) * (percent / 100.0))
+ break
+ last_limit = limit
+
+ tax += payslip.dict.contract_id.us_payroll_config_value('fed_941_fit_w4_additional_withholding')
+ # Ideally we would set the 'taxable wage' as the result and compute the percentage tax.
+ # This is off by 1 penny across our tests, but I feel like it is worth it for the added reporting.
+ # - Jared Kipe 2019 during Odoo 13.0 rewrite.
+ #
+ # return -tax, 100.0
+ return wage, -(tax / wage * 100.0)
diff --git a/l10n_us_hr_payroll/models/hr_contract.py b/l10n_us_hr_payroll/models/hr_contract.py
new file mode 100644
index 00000000..ce3e6de9
--- /dev/null
+++ b/l10n_us_hr_payroll/models/hr_contract.py
@@ -0,0 +1,24 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from odoo import api, fields, models
+from .us_payroll_config import FUTA_TYPE_NORMAL, \
+ FUTA_TYPE_BASIC, \
+ FUTA_TYPE_EXEMPT
+
+
+class USHRContract(models.Model):
+ _inherit = 'hr.contract'
+
+ FUTA_TYPE_NORMAL = FUTA_TYPE_NORMAL
+ FUTA_TYPE_BASIC = FUTA_TYPE_BASIC
+ FUTA_TYPE_EXEMPT = FUTA_TYPE_EXEMPT
+
+ schedule_pay = fields.Selection(selection_add=[('semi-monthly', 'Semi-monthly')])
+ us_payroll_config_id = fields.Many2one('hr.contract.us_payroll_config', 'Payroll Forms')
+ external_wages = fields.Float(string='External Existing Wages')
+
+ # Simplified fields for easier rules, state code will exempt based on contract's futa_type
+ futa_type = fields.Selection(related='us_payroll_config_id.fed_940_type')
+
+ def us_payroll_config_value(self, name):
+ return self.us_payroll_config_id[name]
diff --git a/l10n_us_hr_payroll/models/hr_payslip.py b/l10n_us_hr_payroll/models/hr_payslip.py
new file mode 100644
index 00000000..5a335a65
--- /dev/null
+++ b/l10n_us_hr_payroll/models/hr_payslip.py
@@ -0,0 +1,213 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from odoo import api, fields, models
+
+from .federal.fed_940 import er_us_940_futa
+from .federal.fed_941 import ee_us_941_fica_ss, \
+ ee_us_941_fica_m, \
+ ee_us_941_fica_m_add,\
+ er_us_941_fica_ss, \
+ er_us_941_fica_m, \
+ ee_us_941_fit
+
+
+class HRPayslip(models.Model):
+ _inherit = 'hr.payslip'
+
+ # From IRS Publication 15-T or logically (annually, bi-monthly)
+ PAY_PERIODS_IN_YEAR = {
+ 'annually': 1,
+ 'semi-annually': 2,
+ 'quarterly': 4,
+ 'bi-monthly': 6,
+ 'monthly': 12,
+ 'semi-monthly': 24,
+ 'bi-weekly': 26,
+ 'weekly': 52,
+ 'daily': 260,
+ }
+
+ def _get_base_local_dict(self):
+ # back port for US Payroll
+ #res = super()._get_base_local_dict()
+ return {
+ 'er_us_940_futa': er_us_940_futa,
+ 'ee_us_941_fica_ss': ee_us_941_fica_ss,
+ 'ee_us_941_fica_m': ee_us_941_fica_m,
+ 'ee_us_941_fica_m_add': ee_us_941_fica_m_add,
+ 'er_us_941_fica_ss': er_us_941_fica_ss,
+ 'er_us_941_fica_m': er_us_941_fica_m,
+ 'ee_us_941_fit': ee_us_941_fit,
+ }
+
+ def get_year(self):
+ # Helper method to get the year (normalized between Odoo Versions)
+ return self.date_to.year
+
+ def get_pay_periods_in_year(self):
+ return self.PAY_PERIODS_IN_YEAR.get(self.contract_id.schedule_pay, 0)
+
+ @api.model
+ def _get_payslip_lines(self, contract_ids, payslip_id):
+ def _sum_salary_rule_category(localdict, category, amount):
+ if category.parent_id:
+ localdict = _sum_salary_rule_category(localdict, category.parent_id, amount)
+ localdict['categories'].dict[category.code] = category.code in localdict['categories'].dict and localdict['categories'].dict[category.code] + amount or amount
+ return localdict
+
+ class BrowsableObject(object):
+ def __init__(self, employee_id, dict, env):
+ self.employee_id = employee_id
+ self.dict = dict
+ self.env = env
+
+ def __getattr__(self, attr):
+ return attr in self.dict and self.dict.__getitem__(attr) or 0.0
+
+ class InputLine(BrowsableObject):
+ """a class that will be used into the python code, mainly for usability purposes"""
+ def sum(self, code, from_date, to_date=None):
+ if to_date is None:
+ to_date = fields.Date.today()
+ self.env.cr.execute("""
+ SELECT sum(amount) as sum
+ FROM hr_payslip as hp, hr_payslip_input as pi
+ WHERE hp.employee_id = %s AND hp.state = 'done'
+ AND hp.date_from >= %s AND hp.date_to <= %s AND hp.id = pi.payslip_id AND pi.code = %s""",
+ (self.employee_id, from_date, to_date, code))
+ return self.env.cr.fetchone()[0] or 0.0
+
+ class WorkedDays(BrowsableObject):
+ """a class that will be used into the python code, mainly for usability purposes"""
+ def _sum(self, code, from_date, to_date=None):
+ if to_date is None:
+ to_date = fields.Date.today()
+ self.env.cr.execute("""
+ SELECT sum(number_of_days) as number_of_days, sum(number_of_hours) as number_of_hours
+ FROM hr_payslip as hp, hr_payslip_worked_days as pi
+ WHERE hp.employee_id = %s AND hp.state = 'done'
+ AND hp.date_from >= %s AND hp.date_to <= %s AND hp.id = pi.payslip_id AND pi.code = %s""",
+ (self.employee_id, from_date, to_date, code))
+ return self.env.cr.fetchone()
+
+ def sum(self, code, from_date, to_date=None):
+ res = self._sum(code, from_date, to_date)
+ return res and res[0] or 0.0
+
+ def sum_hours(self, code, from_date, to_date=None):
+ res = self._sum(code, from_date, to_date)
+ return res and res[1] or 0.0
+
+ class Payslips(BrowsableObject):
+ """a class that will be used into the python code, mainly for usability purposes"""
+
+ def sum(self, code, from_date, to_date=None):
+ if to_date is None:
+ to_date = fields.Date.today()
+ self.env.cr.execute("""SELECT sum(case when hp.credit_note = False then (pl.total) else (-pl.total) end)
+ FROM hr_payslip as hp, hr_payslip_line as pl
+ WHERE hp.employee_id = %s AND hp.state = 'done'
+ AND hp.date_from >= %s AND hp.date_to <= %s AND hp.id = pl.slip_id AND pl.code = %s""",
+ (self.employee_id, from_date, to_date, code))
+ res = self.env.cr.fetchone()
+ return res and res[0] or 0.0
+
+ def sum_category(self, code, from_date, to_date=None):
+ # Hibou Backport
+ if to_date is None:
+ to_date = fields.Date.today()
+
+ self.env.cr.execute("""SELECT sum(case when hp.credit_note is not True then (pl.total) else (-pl.total) end)
+ FROM hr_payslip as hp, hr_payslip_line as pl, hr_salary_rule_category as rc
+ WHERE hp.employee_id = %s AND hp.state = 'done'
+ AND hp.date_from >= %s AND hp.date_to <= %s AND hp.id = pl.slip_id
+ AND rc.id = pl.category_id AND rc.code = %s""",
+ (self.employee_id, from_date, to_date, code))
+ res = self.env.cr.fetchone()
+ return res and res[0] or 0.0
+
+ #we keep a dict with the result because a value can be overwritten by another rule with the same code
+ result_dict = {}
+ rules_dict = {}
+ worked_days_dict = {}
+ inputs_dict = {}
+ blacklist = []
+ payslip = self.env['hr.payslip'].browse(payslip_id)
+ for worked_days_line in payslip.worked_days_line_ids:
+ worked_days_dict[worked_days_line.code] = worked_days_line
+ for input_line in payslip.input_line_ids:
+ inputs_dict[input_line.code] = input_line
+
+ categories = BrowsableObject(payslip.employee_id.id, {}, self.env)
+ inputs = InputLine(payslip.employee_id.id, inputs_dict, self.env)
+ worked_days = WorkedDays(payslip.employee_id.id, worked_days_dict, self.env)
+ payslips = Payslips(payslip.employee_id.id, payslip, self.env)
+ rules = BrowsableObject(payslip.employee_id.id, rules_dict, self.env)
+
+ baselocaldict = {'categories': categories, 'rules': rules, 'payslip': payslips, 'worked_days': worked_days, 'inputs': inputs}
+
+ # Hibou Backport
+ baselocaldict.update(self._get_base_local_dict())
+
+ #get the ids of the structures on the contracts and their parent id as well
+ contracts = self.env['hr.contract'].browse(contract_ids)
+ if len(contracts) == 1 and payslip.struct_id:
+ structure_ids = list(set(payslip.struct_id._get_parent_structure().ids))
+ else:
+ structure_ids = contracts.get_all_structures()
+ #get the rules of the structure and thier children
+ rule_ids = self.env['hr.payroll.structure'].browse(structure_ids).get_all_rules()
+ #run the rules by sequence
+ sorted_rule_ids = [id for id, sequence in sorted(rule_ids, key=lambda x:x[1])]
+ sorted_rules = self.env['hr.salary.rule'].browse(sorted_rule_ids)
+
+ for contract in contracts:
+ employee = contract.employee_id
+ localdict = dict(baselocaldict, employee=employee, contract=contract)
+ for rule in sorted_rules:
+ key = rule.code + '-' + str(contract.id)
+ localdict['result'] = None
+ localdict['result_qty'] = 1.0
+ localdict['result_rate'] = 100
+ #check if the rule can be applied
+ if rule._satisfy_condition(localdict) and rule.id not in blacklist:
+ #compute the amount of the rule
+ amount, qty, rate = rule._compute_rule(localdict)
+ #check if there is already a rule computed with that code
+ previous_amount = rule.code in localdict and localdict[rule.code] or 0.0
+ #set/overwrite the amount computed for this rule in the localdict
+ tot_rule = amount * qty * rate / 100.0
+ localdict[rule.code] = tot_rule
+ rules_dict[rule.code] = rule
+ #sum the amount for its salary category
+ localdict = _sum_salary_rule_category(localdict, rule.category_id, tot_rule - previous_amount)
+ #create/overwrite the rule in the temporary results
+ result_dict[key] = {
+ 'salary_rule_id': rule.id,
+ 'contract_id': contract.id,
+ 'name': rule.name,
+ 'code': rule.code,
+ 'category_id': rule.category_id.id,
+ 'sequence': rule.sequence,
+ 'appears_on_payslip': rule.appears_on_payslip,
+ 'condition_select': rule.condition_select,
+ 'condition_python': rule.condition_python,
+ 'condition_range': rule.condition_range,
+ 'condition_range_min': rule.condition_range_min,
+ 'condition_range_max': rule.condition_range_max,
+ 'amount_select': rule.amount_select,
+ 'amount_fix': rule.amount_fix,
+ 'amount_python_compute': rule.amount_python_compute,
+ 'amount_percentage': rule.amount_percentage,
+ 'amount_percentage_base': rule.amount_percentage_base,
+ 'register_id': rule.register_id.id,
+ 'amount': amount,
+ 'employee_id': contract.employee_id.id,
+ 'quantity': qty,
+ 'rate': rate,
+ }
+ else:
+ #blacklist this rule and its children
+ blacklist += [id for id, seq in rule._recursive_search_of_rules()]
+
+ return list(result_dict.values())
diff --git a/l10n_us_hr_payroll/models/l10n_us_hr_payroll.py b/l10n_us_hr_payroll/models/l10n_us_hr_payroll.py
deleted file mode 100755
index d7d4b4f6..00000000
--- a/l10n_us_hr_payroll/models/l10n_us_hr_payroll.py
+++ /dev/null
@@ -1,44 +0,0 @@
-from odoo import models, fields, api
-
-
-class Payslip(models.Model):
- _inherit = 'hr.payslip'
-
- def get_futa_rate(self, contract):
- self.ensure_one()
- if contract.futa_type == USHrContract.FUTA_TYPE_EXEMPT:
- rate = self.get_rate('US_FUTA_EXEMPT')
- elif contract.futa_type == USHrContract.FUTA_TYPE_NORMAL:
- rate = self.get_rate('US_FUTA_NORMAL')
- else:
- rate = self.get_rate('US_FUTA_BASIC')
- return rate
-
-
-class USHrContract(models.Model):
- FUTA_TYPE_EXEMPT = 'exempt'
- FUTA_TYPE_BASIC = 'basic'
- FUTA_TYPE_NORMAL = 'normal'
-
- _inherit = 'hr.contract'
-
- schedule_pay = fields.Selection(selection_add=[('semi-monthly', 'Semi-monthly')])
- w4_allowances = fields.Integer(string='Federal W4 Allowances', default=0)
- w4_filing_status = fields.Selection([
- ('', 'Exempt'),
- ('single', 'Single'),
- ('married', 'Married'),
- ('married_as_single', 'Married but at Single Rate'),
- ], string='Federal W4 Filing Status', default='single')
- w4_is_nonresident_alien = fields.Boolean(string="Federal W4 Is Nonresident Alien", default=False)
- w4_additional_withholding = fields.Float(string="Federal W4 Additional Withholding", default=0.0)
-
- external_wages = fields.Float(string='External Existing Wages', default=0.0)
-
- fica_exempt = fields.Boolean(string='FICA Exempt', help="Exempt from Social Security and "
- "Medicare e.g. F1 Student Visa")
- futa_type = fields.Selection([
- (FUTA_TYPE_EXEMPT, 'Exempt (0%)'),
- (FUTA_TYPE_NORMAL, 'Normal Net Rate (0.6%)'),
- (FUTA_TYPE_BASIC, 'Basic Rate (6%)'),
- ], string="Federal Unemployment Tax Type (FUTA)", default='normal')
diff --git a/l10n_us_hr_payroll/models/us_payroll_config.py b/l10n_us_hr_payroll/models/us_payroll_config.py
new file mode 100644
index 00000000..ed0e6d73
--- /dev/null
+++ b/l10n_us_hr_payroll/models/us_payroll_config.py
@@ -0,0 +1,45 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from odoo import api, fields, models
+
+FUTA_TYPE_EXEMPT = 'exempt'
+FUTA_TYPE_BASIC = 'basic'
+FUTA_TYPE_NORMAL = 'normal'
+
+
+class HRContractUSPayrollConfig(models.Model):
+ _name = 'hr.contract.us_payroll_config'
+ _description = 'Contract US Payroll Forms'
+
+ name = fields.Char(string="Description")
+ employee_id = fields.Many2one('hr.employee', string="Employee", required=True)
+ state_id = fields.Many2one('res.country.state', string="Applied State")
+
+ fed_940_type = fields.Selection([
+ (FUTA_TYPE_EXEMPT, 'Exempt (0%)'),
+ (FUTA_TYPE_NORMAL, 'Normal Net Rate (0.6%)'),
+ (FUTA_TYPE_BASIC, 'Basic Rate (6%)'),
+ ], string="Federal Unemployment Tax Type (FUTA)", default='normal')
+
+ fed_941_fica_exempt = fields.Boolean(string='FICA Exempt', help="Exempt from Social Security and "
+ "Medicare e.g. F1 Student Visa")
+
+ fed_941_fit_w4_filing_status = fields.Selection([
+ ('', 'Exempt'),
+ ('single', 'Single or Married filing separately'),
+ ('married', 'Married filing jointly'),
+ ('married_as_single', 'Head of Household'),
+ ], string='Federal W4 Filing Status [1(c)]', default='single')
+ fed_941_fit_w4_allowances = fields.Integer(string='Federal W4 Allowances (before 2020)')
+ fed_941_fit_w4_is_nonresident_alien = fields.Boolean(string='Federal W4 Is Nonresident Alien')
+ fed_941_fit_w4_multiple_jobs_higher = fields.Boolean(string='Federal W4 Multiple Jobs Higher [2(c)]',
+ help='Form W4 (2020+) 2(c) Checkbox. '
+ 'Uses Higher Withholding tables.')
+ fed_941_fit_w4_dependent_credit = fields.Float(string='Federal W4 Dependent Credit [3]',
+ help='Form W4 (2020+) Line 3')
+ fed_941_fit_w4_other_income = fields.Float(string='Federal W4 Other Income [4(a)]',
+ help='Form W4 (2020+) 4(a)')
+ fed_941_fit_w4_deductions = fields.Float(string='Federal W4 Deductions [4(b)]',
+ help='Form W4 (2020+) 4(b)')
+ fed_941_fit_w4_additional_withholding = fields.Float(string='Federal W4 Additional Withholding [4(c)]',
+ help='Form W4 (2020+) 4(c)')
diff --git a/l10n_us_hr_payroll/security/ir.model.access.csv b/l10n_us_hr_payroll/security/ir.model.access.csv
new file mode 100644
index 00000000..67a8fa2a
--- /dev/null
+++ b/l10n_us_hr_payroll/security/ir.model.access.csv
@@ -0,0 +1,2 @@
+id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
+access_hr_contract_us_payroll_config,hr.contract.us_payroll_config,model_hr_contract_us_payroll_config,hr_payroll.group_hr_payroll_manager,1,1,1,1
diff --git a/l10n_us_hr_payroll/static/description/icon.png b/l10n_us_hr_payroll/static/description/icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..2a58a38132bd0b9a86324caa86d0cc6fc145fd4c
GIT binary patch
literal 8776
zcmai4XH-+$wx)Nb3DQeYst|e!z1K(=klqOd2qg3>M3AafrFRgJ04g9IK|p%%(gXyg
z_bR-2&bjy8JMMk&{n*J~Ykuq7-<)&JxyBx2N9jCPy+cGtgn@x^M_o-x5B;z6`y#+Y
z|HhLaR$yS@yn`4RBaFdXAX|4=K5IL78+$%qR~XukfgvU93$wO$u}84j*gHbpqyc-4
ztpFB?oixBm1k4YHDcUb6YyB9(l06^PW{vt;}9R6l^ga73|IuO9$7N7thKk#4S_P!7YW#fO!v4y~_`J7?E
z-_!qrwe~{0{g>{4WBw-^jrb4t|7Xd+ib2QtzoUJvVgI294F-e%Z@8=Lzo~&El)cf>
z`fncm;{k93KbSpG&mQjX}~IcVEaSi@8E&dU9BDMwd~y-5l+$oaej1ie+T|=
zlM%%3KP|t>pkBXIX6Ob%KVI5LYx7{p^3y`?uNTznRf-1)<3y4t`432z#_%egO$y0Rdiq5d#5n
z5Wg@;n4gy)eehcy9oSCAndK0u0urL4yn=iJ7#N%h
zUI{}yO1k&DW$_Z2)D`2pF|1hn8?wAGmzB
zYMh_-%9AejE`s%`T$=@=lEG#
zoW>{R(K#H!mO$7$d&NuT-E=xv)0=ZO_49sG1Qu0IyJB>4M!Ri0WqGl~Q7WuFF$zeO
zL*TRJDZfl_sx5vEh0iW)XPg)dlc3nG-5ZdU58v4#42><~wFgj~t!LGiqp$kG+Un5z
z?j0<>oUBc;LJDtKf=GH9zG#Osndq{&o^gaE+V>dRO@1x-pp;I+KA(?LJ1P8G4(r?CkME}LzMVl)(7*9^I8pyQyq29DMkz%{&-;YjrC;hes
z^Vc1xn1j_~W(V-1x{80ru=ljdvJt29;|iYMLF
zUG(p(;&?Sv-%ZTVKAr!0vU3&SQ*p5(Q`5jZL*WbA%nTeV9jn}{xSTKB`(-#$8GKUC
zWBjY_wkxS$k=!vjrzIHWLR@*vy+$RUW4_YYLw=db%|v`?rZ613NQC1Rky?|~e^0$I
zMomr^r!T-%_ul88QsMY5F@Xh!qLPBBf)&ED;yZGjcKqXuU5GL{Zqq~Da-zN~(|xI)
zo=xFw4stow0Llumb^PN838{fK{^o)@`wz@3;;s+&n!@WAtEgFV)YONEFTOU49UYu4
zs&P8?y9^E(ksfd`w}#tG^WKPXZSKV%)oU;GfHUxdO7$p+=mM{Qi5({RxU=WFusl>C
zaBB?~3AvW^86Kg?s6?)-?>A`*LI~8>Gy<>k?$hK9=0b?7osbHpz1lf4$UK)fsfwEL
zWQ~c{wgfJ2kr<^ixkuCr8fwKcfm@%m7$hWAzYY^bV%_5rVOj{r=<|k|y`1#Ley*1?
zkKe8C5M2njpG~(#G_AN@0xt7)m-ND{POyU~w&*;!ei%0G4aQxc)YS@kLh(Y^nk%lC
zyWLiL)>V9+u7V3WqLA2uKW|Ji&M#&6a?MbAZ;8&ny^o(mnGRUqaGr?ZH`#jwlI@lpyW;1iW<522u4|0WE-$c}@3V;=x>tXx~7MXi=Ra43H
z(7feOOaufpbl5=^3?NY4)+ZL6?Ddhw39WkPWq&4AvRZdRkwAOz!V_V`&dltUbH~=O
z9TVU0qvP_Qf8rMwa|!jru^=B4V~NyZKv(~3Ev7P4&z`s(bCaGHi_*G8!E
zSR}{8D;0JDl92bCJ5U>YOZuTb`i>K+!ehb~w_h0e@mfj!sr-l(o0-B7Prvtlh}^6x
ze|<``@rh~1j27u$9C*Gk5DPAn5EmqDi?-kSJ(N0Mm5GlX&@EMKUuN-ft0#G|9dvPT
z#*7eY)sZf<8#L#y()~kRAREJK=BLGO$dt(SBEFC3VJUVp=DlY&8#ws>1S?CtRM?f2
zVH2ck=K3ZhQySd*3Y<4@L}jw_N?DO)VTjr!NW@Pf$;lUJh+Q@uolGWY<}>Y8vQWr^M+clXt+oB-1B`GK#R2KXXU
z>e>O6IU!4Y+2@@Ei~jq5dOcFhPA?W4_VC)}vu-cNUcz8@6nWggGwiv@NPjUp71I7S
zfr~6`LUI0jbFU?dS*kXYS*G@Ny;63B@teiQ5aN`wPNSBCHlfaggi#lyQVXESBd9eK>JM;lO{@%Y#4qS}#N60}ER)Lk^T-C=5XjDWU~Kv+
zwaYV|FAex+ukPKfLAa?X^9UB|?HS!0z2CIngv_PA(ZEhLpMJCq921^t)qG3~SMwvIbKyv->5+tVh#sfYYL_IP!Owh0r)L*rKE;
zZ2zmUBx$CSMii>_o7a*3MtW~8BfbDHeyCT6v{}d+hHT*VLy9y2#?ArW0{kjv8w5*i
z2IgP|DqimLzP6R*dwG5R%3TO6V~ThEaM`WpcG@Bgu6`pW^0V;+^1U!g(_<$!f(^Q@
zh=8xFJktSYa`~J@*v%ElaLgOmt6=CHCLv>m03ZF&9|;hU%=DpASVWM
z!-P>W9aKvoZDjm_4(AjBE<$gHBD`OzulQAuA4|mCU&Ys06iDj8Eu({mrLa1%o1rp~y3CU2J%awa!
zg@gu3|3MPh=+sO`rs7(@SMsVT#hwbv=wW)aV+7BRZ1k382hFYm-
zwfq_VE8;X!Rc8Tq0jQNcJEC#b<6o?fbaKu@|L50KB?}9_Xzh@
zTQBY^$Y%j>e`#}3a}yXYFU(CIZS76B<^0-Vi)G5d#4LZ}eQ<%l`yG9RtbOJf!`7gjT|xJJxDmDdyCR=g%EbCOr?-C1$z7~4fB&H>BH8ZHo@urv
z$9ARX0%f0mSJIoA^{q>CdCLW>`OA@N38p|#=F$d2#d)@0OG#HW5&~O|4bMC$R!g%A
z#-WkvknrO^W}2*ghgtMVNykW9sn>~am?*Iyy#X!0#d`$edx=Yk?B{;CEDs4hZVtVC
z6^7T|TnAf+Jswi9$fx7%(th|Fy04ocypqRfh`WG6zy%x`xdJ?MD0#1G9;r!ZGvvyA
z<#Q*HkxZ7@>|hV4<*rk79?*-C5AtHu{bs3&ZUdL3Kc4!VRp=6wqC!XJrNJwOgMsw1
z$*AKFxi5YUA$q!18FW=oGe$J1uzE=HcVB#>@mMgK_Ld$^;oj7^(|z;;kK}2)ybUI{
zGJzSF0OS2WFApiwsU%;$LEyk+O<7kC9)(5$@W!
zyt0oGiXV~TocBxfex>6yWIr%3O?+fj-7IwF5K%TtHav0>63T+8JalPYaq@rj}g0Gvg>gX(@gU)(Ps*|D0=qm&P7%851t;Os}!FZCFC~?
zLc;o~QX9Bqa(mz;!y2ZUZK{j#kyrS+C_0wolAQc&=G)j92);(lV(>2>)LYmuywygx
zAjO6s^mwfNyA5e_+{8o8Upgt`9&AIcb-`L(v_ia@Mo)&1$ZEr3W|r7U*~j@VYCB%B
z;d+-2(JKI{A)f#)q781nvB9615oC{uX>j-wUzbC`caCn=xYXBTV!l!q$0hRQ4>}C9
zJB(16D0Si}FLeUM=PSg67(cm!eXIH3-v41r9v_p)lUI1OVLSE~ZlY8g_G(F4@l%$E
zrCR`{1-w5*JrvuH>iMqmYCgZK(%P3&zuMmD_T;=PKIth%@EZT!Y?dm=dpoO-@|oBO
zmT2g*f4K9+~&V_q8k?0;t
zkD^ezP?!8~vuZW0v5dlG6vyf6RXXh3>c=U~##5H@bECeZQQ1lvTDdz?vC=Y%BDwl;
z%2n8xO_@3^TEZ!8iClw8T;X^>iYE*fYspJmDWUj#>y5D1gCNdqN5f-wMIJ^FUTAQ4
z+(t*=udhcd`btdK5|;<17qpe*vtPE$WCVkDlAmV4PCCsED@~Nf{JwvnO%J)D=$%8g
zyF_RAuml}`yMH&y6Z)P$lB?zm%*$1tX&4;i#I3#6V*-lL1Q>9#Ia7ajJS%V@N?r^Q
zI+YXiG$C~mNB+8Q5z8o;LS9zOP2ESL$S=QjKX1q<70KeboaY#M37(igezoJ`xScN2
zkJ>GnrBN<(R3-OK*Wjhj8>i?fGx@@vRQ2A`f;a_AezZhbj0Nhk&oZEAbL=B`T)j7!
zAwb`*^j`&LGdG{>c3n%xE}
zg1)Xybs<-$)eQO{Yc%O9rnX39aU{pX3(w~Ud9f@0)AHfO&g
zS=ddj!{n(K1u?F!;j`qDwPXo#fj+ZIOAk&|cl0`G^>(Lj@}=F1cb)F_KI61v^H`gs
z7^3d>$aK(*v3Fq;iQ$H$kXLg0C>?T?EIk#^rrM+H6uTi{=pxt%jpg#4h4#WI`7qFhgqebk+{YKirM;Fp5&UE^c4f)REn#Q6sEf*M0jGD#`!31HWrV1_XX@u=a8x75W435G34ZJW7)Yo=%52Go5wiY}BmIX2u
zBg4@(I$3Go==&Ucfk!jCg~&w6(fEe&-qX5RaNkpOUeX4rgB*F5p4PUeP8iTBUt=Ui=zh$t{xuYcE=<|JLY;%`8mt~73>wnle8d*3!H7jq!=
z*%48>#fu98&GD=pg*HS>zv`{Wl?f)n!?C=lVu2#cX?1x;7(O>Is^lvAr1(>oNn6ht
z^y@n#sy4;%xlCe7w6GWjJnvy!gV2lMWp-F!Ov+&bLfP5mjhBuL&F)vo&nT`QEo(Q=rTnNnyB=y+N
z-UYLU16$YWP!;}P4uTuLBk;I5+NrGFDuC^^
zmzZrM(@GS{j2&~I~>HL!=h6jlgFsg4X5I@
zE-UW*97T%+7%xz-H&8K}Ne*M4m&=TKiwkBgZD?VAkSXrs`0?W5pi}PsJGP`JfVhYH
zO@2=8p4ZU?@~%fu6<+QsRrtJi0%@;raP=P0I6P3Rc7A^~jl)4no9gog5`WGyIo2si
z*~3t(Kse{|O6~*uxv|-LxzOE#hV~tStS77<&lKu=QEV$(4+M|vIw8^Ln)O81kZ=)FLK1*N-p3fcjS(;p00eRw@c=%IA?JmaM8C(h3(8JUOyjbJbkQ
zmonAD!p?QjFAbhfGL=ssnQ9bQA?Rqn1r$11wzPX~^|6<_7`V3*F+>gvU6H5IMQ@F^
zIwt?o&|4}euH=9<8k(6x-!t2zMm!qGcy9Y
zOEXtQRrB0d{YS_q8c~W}wm8|6FF+acZ`kidJ#xwZ7RN}W2dt^ecIk!`JL1|I5y;XIWv)0qq^Lf*gV?-wb(`62~f=D-CM;_5_}KI?*?_y
zLhpI|nC5S&NKghJz6FE8kpHwP&Yf;BePq|8l2&Y5qW;BwfIPRUs8z)_Yt^X@>
z?F&6r{tRDSJ%xc*Qv|=85%DREd6Fvp(|~{l51!&rx|#u(>eoIL(*ek@2V6FrSQ_<2
zsn}%^_esAiEBGGZZBt3V$#}9{E0$BEoa;dBq+i+u$|!9z!O0?}vh(wGI
z&nyZnOWpLb(V8qC9hPNW%^FYyK|#35!8JcxgPq!tpbXT8Dm%=SPxj0$J%6cIbh%bE
zD`?NF-6eKOM5Ssd$BSHPLWw>@K1*W|OOsl-07G3$g3;eUhi3ul+HRJowHtVVY{eQ^
zq7Fi|Qx{7Ut}gE5o3SJ>xSBFrwR(GY3EzF$n%5qa!aW9uP;4?3;kI;55nD@aa1;--
z|Db5|-K(u=eTUU*EiP>+6SMN9gH-xR6My$jDwy3UQ+BX{Y*UN8&--l}^>g_4;%t0}
z7N7$dFsjgVk6@cl-uRRT_Wd>YHQtKwGi^-2omJsy_f>(Ps#ZQd7?ogm)GK`d+;3_O
zB0Sl8nODZ0F#O$>cql0|T|Gfu{O;O9Pq(r@XjRCW!d}lYP)s7BxIbH)KR+)O9$FR(xyk$-{gU7~l>A>ir
z>4W1he6KvIrzaX+w-WJ4_&$2KVy>px#qU!#UUR-Xk~5^T86k^6k>AA?`1qljt)VzC
zYsH__ij0Vkra?tfSok(5Vk)ePQ9<$){pulQB^x|sqSrsw;4?Sb(YA>0XJJFkhmBwT
zy4V$`Hg=rtb1TE+YuORziW*UnnRSLKv#Abmv)c@k29*#W`ab_zlgY8_uR_0^hgCTU
zc6|x0?w$+4j%zfVE_~NqZui;>G>SPDwRcs(S&N`ygA^V9&?lrWJciC%L-w4>)WExD
zQ*0SPf!F<)-IkbO{ixrsj%2vy?C+I=KVe%xHC$3{Ydv+FYgpAyjUm{cuNA?*b~N*8
z3_eaKk@-Mcgjhx&w%%&?hxat794JgOaYs%K#{g*qNrO~9KQv=@v+5E%i4r2STq|+Nlv!RvvqFv_~7S`(a8j=nSoZ=qJm&4AVpd`pY!{p
zFsY$4@tmy%p1f5}WcD<8|3XnUSz(jM_b?A#7nJuGKGxw?&P4Y#%Cd!$MZX`6iIB0b
zF!i%s%igkekI=Vf%8m?%M(?ix`!~ATTY1ZV$bR?l4ybcA7tC37Sq>}s$gy`53&8fT
zB#B(G^jr7Z6RCU}lyd)0b1mpb@D;^F{dYJyvY&*`w{P}J>%X@y`;Ik}-VQy?fmjk(
zYUC#C!5oGRD>&}R63OvAgoEKkb(3aikB5@Bq_u08#4(XN!v<#`p(eMIw-Q8LpFIqb
zuweszf`pNK_C`AUYN`{#qN9rm*wDf4+N_%`Og#b*U2=cwXL=gAIh@$C&a4DD)DH@>
z5pqY-YjP+1J*d&rdasGiRbGn+G)ZFbLQHB!Oh!rH8vP_`z=C>s7AIsyPCAu=sJCCp
z(r-az$!0%-o&g>wIZP$hvrPQ#dAA5hl4&yM5Ai4fey-w%?uXs`Zum9HaO~~%c4tb;
z4$$quVe~a^Jf$umPbt?1EOPTh8pt^^tYE7RN{WKrlngC8y?lJ^|NPJ&Nb&Pgt(c|C
zfWI(e?2;=GOFPMFVhn;+eqw-Erl+xG!*XC|dPQ8L=eY~~tS0B*8U2GDey7W7@x>fj
zk9yx-+)4|0n~ca5m#gWsVF2@Mp^UpOS-ga{pKb$ERDBl6n|2QwEm|0a&zN^e=RXD?
zG7OoVH^orI5ZqJn)zhF1>95{jJcsNMd#YgHk<(AlFWP7pqe}MeFPEt<$2X_Uqo3l)
z+B7hRycqZG+i|zQy#(
X4vqDeFsl6h0f@TtV
+
+
+
+ hr.contract.form.inherit
+ hr.contract
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/l10n_us_hr_payroll/views/l10n_us_hr_payroll_view.xml b/l10n_us_hr_payroll/views/l10n_us_hr_payroll_view.xml
deleted file mode 100755
index 257508bc..00000000
--- a/l10n_us_hr_payroll/views/l10n_us_hr_payroll_view.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
- hr.contract.form.inherit
- hr.contract
- 20
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/l10n_us_hr_payroll/views/us_payroll_config_views.xml b/l10n_us_hr_payroll/views/us_payroll_config_views.xml
new file mode 100644
index 00000000..5b6d0dea
--- /dev/null
+++ b/l10n_us_hr_payroll/views/us_payroll_config_views.xml
@@ -0,0 +1,75 @@
+
+
+
+ hr.contract.us_payroll_config.tree
+ hr.contract.us_payroll_config
+
+
+
+
+
+
+
+
+
+
+
+
+ hr.contract.us_payroll_config.form
+ hr.contract.us_payroll_config
+
+
+
+ Form 941 / W4 - Federal Income Tax
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ hr.contract.us_payroll_config.search
+ hr.contract.us_payroll_config
+
+
+
+
+
+
+
+
+
+
+ Employee Payroll Forms
+ hr.contract.us_payroll_config
+ tree,form
+
+
+ No Forms
+
+
+
+
+
+