MIG l10n_us_hr_payroll Major refactor and Federal 2020 Rules (new W4 Form), Migration scripts and states deprecated

States:
- FL Florida
- PA Pennsylvania
- MT Montana
- OH Ohio
- WA Washington
- TX Texas
- VA Virginia
- GA Georgia
- MS Mississippi
This commit is contained in:
Jared Kipe
2020-01-10 07:04:55 -08:00
parent bdc69006ee
commit 6a392f033e
95 changed files with 6117 additions and 892 deletions

29
LICENSE_PROFESSIONAL Normal file
View File

@@ -0,0 +1,29 @@
Odoo Proprietary License v1.0
This software and associated files (the "Software") may only be used
(executed, modified, executed after modifications) if you have purchased
a valid license from the authors, typically via Odoo Apps, or if you
have received a written agreement from the authors of the Software
(see the COPYRIGHT file).
You may develop Odoo modules that use the Software as a library
(typically by depending on it, importing it and using its resources),
but without copying any source code or material from the Software.
You may distribute those modules under the license of your choice,
provided that this license is compatible with the terms of the Odoo
Proprietary License (For example: LGPL, MIT, or proprietary licenses
similar to this one).
It is forbidden to publish, distribute, sublicense, or sell copies
of the Software or modified copies of the Software.
The above copyright notice and this permission notice must be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -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)

View File

@@ -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',

View File

@@ -27,6 +27,7 @@
<field name="name"/>
<field name="code"/>
<field name="rate"/>
<field name="parameter_value"/>
</group>
<group>
<field name="date_from"/>

View File

@@ -24,5 +24,5 @@ USA::Arkansas Payroll Rules.
'data/rules.xml',
'data/final.xml',
],
'installable': True
'installable': False
}

View File

@@ -28,5 +28,5 @@ USA::California Payroll Rules.
'data/rules.xml',
'data/final.xml',
],
'installable': True
'installable': False
}

View File

@@ -24,5 +24,5 @@ USA::Florida Payroll Rules.
'data/rules.xml',
'data/final.xml',
],
'installable': True
'installable': False
}

View File

@@ -1,3 +1,3 @@
# -*- coding: utf-8 -*-
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
from . import models

View File

@@ -2,10 +2,9 @@
{
'name': 'USA - Payroll',
'author': 'Hibou Corp. <hello@hibou.io>',
'license': 'AGPL-3',
'category': 'Localization',
'depends': ['hr_payroll', 'hr_payroll_rate'],
'version': '11.0.2018.1.0',
'version': '11.0.2020.1.0',
'description': """
USA Payroll Rules.
==================
@@ -21,11 +20,28 @@ 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/state/fl_florida.xml',
'data/state/ga_georgia.xml',
'data/state/ms_mississippi.xml',
'data/state/mt_montana.xml',
'data/state/oh_ohio.xml',
'data/state/pa_pennsylvania.xml',
'data/state/tx_texas.xml',
'data/state/va_virginia.xml',
'data/state/wa_washington.xml',
'data/final.xml',
'views/hr_contract_views.xml',
'views/us_payroll_config_views.xml',
],
'installable': True
'installable': True,
'license': 'OPL-1',
}

115
l10n_us_hr_payroll/data/base.xml Executable file → Normal file
View File

@@ -1,83 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<data>
<!-- CONTRIBUTION REGISTERS -->
<record id="res_partner_eftps_941" model="res.partner">
<field name="name">EFTPS - Form 941</field>
<field name="supplier">1</field>
<field eval="0" name="customer"/>
</record>
<record id="res_partner_eftps_940" model="res.partner">
<field name="name">EFTPS - Form 940</field>
<field name="supplier">1</field>
<field eval="0" name="customer"/>
</record>
<record id="contrib_register_eftps_941" model="hr.contribution.register">
<field name="name">EFTPS - 941 (FICA + Federal Witholding)</field>
<field name="note">Electronic Federal Tax Payment System - Form 941</field>
<field name="partner_id" ref="res_partner_eftps_941"/>
</record>
<record id="contrib_register_eftps_940" model="hr.contribution.register">
<field name="name">EFTPS - 940 (FUTA)</field>
<field name="note">Electronic Federal Tax Payment System - Form 940</field>
<field name="partner_id" ref="res_partner_eftps_940"/>
</record>
<!-- HR SALARY RULE CATEGORIES-->
<record id="hr_payroll_fica_emp_ss_wages" model="hr.salary.rule.category">
<field name="name">Wage: US FICA Social Security</field>
<field name="code">WAGE_US_FICA_SS</field>
</record>
<record id="hr_payroll_fica_emp_m_wages" model="hr.salary.rule.category">
<field name="name">Wage: US FICA Medicare</field>
<field name="code">WAGE_US_FICA_M</field>
</record>
<record id="hr_payroll_fica_emp_m_add_wages" model="hr.salary.rule.category">
<field name="name">Wage: US FICA Medicare Additional</field>
<field name="code">WAGE_US_FICA_M_ADD</field>
</record>
<record id="hr_payroll_futa_wages" model="hr.salary.rule.category">
<field name="name">Wage: US FUTA Federal Unemployment</field>
<field name="code">WAGE_US_FUTA</field>
</record>
<record id="hr_payroll_fica_emp_ss" model="hr.salary.rule.category">
<field name="name">EE: US FICA Social Security</field>
<field name="code">EE_US_FICA_SS</field>
<!-- State Unemployment -->
<record id="hr_payroll_category_ee_us_suta" model="hr.salary.rule.category">
<field name="name">EE: State Unemployment SUTA</field>
<field name="code">EE_US_SUTA</field>
<field name="parent_id" ref="hr_payroll.DED"/>
</record>
<record id="hr_payroll_fica_emp_m" model="hr.salary.rule.category">
<field name="name">EE: US FICA Medicare</field>
<field name="code">EE_US_FICA_M</field>
<field name="parent_id" ref="hr_payroll.DED"/>
</record>
<record id="hr_payroll_fica_emp_m_add" model="hr.salary.rule.category">
<field name="name">EE: US FICA Medicare Additional</field>
<field name="code">EE_US_FICA_M_ADD</field>
<field name="parent_id" ref="hr_payroll.DED"/>
</record>
<record id="hr_payroll_fed_income_withhold" model="hr.salary.rule.category">
<field name="name">EE: US Federal Income Tax Withholding</field>
<field name="code">EE_US_FED_INC_WITHHOLD</field>
<field name="parent_id" ref="hr_payroll.DED"/>
</record>
<record id="hr_payroll_fica_comp_ss" model="hr.salary.rule.category">
<field name="name">ER: US FICA Social Security</field>
<field name="code">ER_US_FICA_SS</field>
<record id="hr_payroll_category_er_us_suta" model="hr.salary.rule.category">
<field name="name">ER: State Unemployment SUTA</field>
<field name="code">ER_US_SUTA</field>
<field name="parent_id" ref="hr_payroll.COMP"/>
</record>
<record id="hr_payroll_fica_comp_m" model="hr.salary.rule.category">
<field name="name">ER: US FICA Medicare</field>
<field name="code">ER_US_FICA_M</field>
<field name="parent_id" ref="hr_payroll.COMP"/>
<!-- State Income Tax -->
<record id="hr_payroll_category_ee_us_sit" model="hr.salary.rule.category">
<field name="name">EE: State Income Tax Withholding</field>
<field name="code">EE_US_SIT</field>
<field name="parent_id" ref="hr_payroll.DED"/>
</record>
<record id="hr_payroll_futa" model="hr.salary.rule.category">
<field name="name">ER: US FUTA Federal Unemployment</field>
<field name="code">ER_US_FUTA</field>
<field name="parent_id" ref="hr_payroll.COMP"/>
<!-- Tax Exempt Deductions -->
<!-- Deductions that reduce the wage for Federal Income Tax (e.g. 401k) -->
<record id="hr_payroll_category_ded_fit_exempt" model="hr.salary.rule.category">
<field name="name">Deduction: Federal Income Tax Exempt</field>
<field name="code">DED_US_FIT_EXEMPT</field>
</record>
</data>
</odoo>
<!-- Generally speaking, deductions to FICA and FUTA should probably reduce GROSS itself, there may be special or rare cases -->
<!-- Deductions that reduce the wage for FICA -->
<record id="hr_payroll_category_ded_fica_exempt" model="hr.salary.rule.category">
<field name="name">Deduction: FICA Exempt</field>
<field name="code">DED_FICA_EXEMPT</field>
<field name="parent_id" ref="hr_payroll.DED"/>
</record>
<!-- Deductions that reduce the wage for Unemployment Insurance/Tax -->
<record id="hr_payroll_category_ded_futa_exempt" model="hr.salary.rule.category">
<field name="name">Deduction: FUTA Exempt</field>
<field name="code">DED_FUTA_EXEMPT</field>
<field name="parent_id" ref="hr_payroll.DED"/>
</record>
</odoo>

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<!-- Wage Base -->
<record id="rule_parameter_940_futa_wage_base_2016" model="hr.payroll.rate">
<field name="name">Federal 940 FUTA Wage Base</field>
<field name="code">fed_940_futa_wage_base</field>
<field name="parameter_value">7000.00</field>
<field name="date_from" eval="datetime(2016, 1, 1).date()"/>
</record>
<!-- Rate -->
<record id="rule_parameter_940_futa_rate_basic_2016" model="hr.payroll.rate">
<field name="name">Federal 940 FUTA Rate Basic</field>
<field name="code">fed_940_futa_rate_basic</field>
<field name="parameter_value">6.0</field>
<field name="date_from" eval="datetime(2016, 1, 1).date()"/>
</record>
<record id="rule_parameter_940_futa_rate_normal_2016" model="hr.payroll.rate">
<field name="name">Federal 940 FUTA Rate Normal</field>
<field name="code">fed_940_futa_rate_normal</field>
<field name="parameter_value">0.6</field>
<field name="date_from" eval="datetime(2016, 1, 1).date()"/>
</record>
</odoo>

View File

@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="res_partner_eftps_940" model="res.partner">
<field name="name">US Federal 940 - EFTPS</field>
</record>
<record id="contrib_register_eftps_940" model="hr.contribution.register">
<field name="name">EFTPS - 940 (FUTA)</field>
<field name="note">Electronic Federal Tax Payment System - Form 940</field>
<field name="partner_id" ref="res_partner_eftps_940"/>
</record>
<record id="hr_payroll_category_er_fed_940" model="hr.salary.rule.category">
<field name="name">ER: Federal 940 FUTA</field>
<field name="code">ER_US_940_FUTA</field>
<field name="parent_id" ref="hr_payroll.COMP"/>
</record>
<record id="hr_payroll_rule_er_fed_940" model="hr.salary.rule">
<field name="sequence" eval="440"/>
<field name="category_id" ref="hr_payroll_category_er_fed_940"/>
<field name="name">ER: US FUTA Federal Unemployment</field>
<field name="code">ER_US_940_FUTA</field>
<field name="condition_select">python</field>
<field name="condition_python">result, _ = er_us_940_futa(payslip, categories, worked_days, inputs)</field>
<field name="amount_select">code</field>
<field name="amount_python_compute">result, result_rate = er_us_940_futa(payslip, categories, worked_days, inputs)</field>
<field name="register_id" ref="contrib_register_eftps_940"/>
<field name="appears_on_payslip" eval="False"/>
</record>
</odoo>

View File

@@ -0,0 +1,66 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<!-- Social Security -->
<!-- Wage Base -->
<record id="rule_parameter_941_fica_ss_wage_base_2016" model="hr.payroll.rate">
<field name="name">Federal 941 FICA Social Security Wage Base</field>
<field name="code">fed_941_fica_ss_wage_base</field>
<field name="parameter_value">128400.0</field>
<field name="date_from" eval="datetime(2016, 1, 1).date()"/>
</record>
<record id="rule_parameter_941_fica_ss_wage_base_2019" model="hr.payroll.rate">
<field name="name">Federal 941 FICA Social Security Wage Base</field>
<field name="code">fed_941_fica_ss_wage_base</field>
<field name="parameter_value">132900.0</field>
<field name="date_from" eval="datetime(2019, 1, 1).date()"/>
</record>
<record id="rule_parameter_941_fica_ss_wage_base_2020" model="hr.payroll.rate">
<field name="name">Federal 941 FICA Social Security Wage Base</field>
<field name="code">fed_941_fica_ss_wage_base</field>
<field name="parameter_value">137700.0</field>
<field name="date_from" eval="datetime(2020, 1, 1).date()"/>
</record>
<!-- Rate -->
<record id="rule_parameter_941_fica_ss_rate_2016" model="hr.payroll.rate">
<field name="name">Federal 941 FICA Rate</field>
<field name="code">fed_941_fica_ss_rate</field>
<field name="parameter_value">6.2</field>
<field name="date_from" eval="datetime(2016, 1, 1).date()"/>
</record>
<!-- Medicare -->
<!-- Wage Base -->
<record id="rule_parameter_941_fica_m_wage_base_2016" model="hr.payroll.rate">
<field name="name">Federal 941 FICA Medicare Wage Base</field>
<field name="code">fed_941_fica_m_wage_base</field>
<field name="parameter_value">"inf"</field>
<field name="date_from" eval="datetime(2016, 1, 1).date()"/>
</record>
<!-- Rate -->
<record id="rule_parameter_941_fica_m_rate_2016" model="hr.payroll.rate">
<field name="name">Federal 941 FICA Rate</field>
<field name="code">fed_941_fica_m_rate</field>
<field name="parameter_value">1.45</field>
<field name="date_from" eval="datetime(2016, 1, 1).date()"/>
</record>
<!-- Medicare Additional -->
<!-- Wage Base -->
<record id="rule_parameter_941_fica_m_add_wage_start_2016" model="hr.payroll.rate">
<field name="name">Federal 941 FICA Medicare Additional Wage Start</field>
<field name="code">fed_941_fica_m_add_wage_start</field>
<field name="parameter_value">200000.0</field>
<field name="date_from" eval="datetime(2016, 1, 1).date()"/>
</record>
<!-- Rate -->
<record id="rule_parameter_941_fica_m_add_rate_2016" model="hr.payroll.rate">
<field name="name">Federal 941 FICA Medicare Additional Rate</field>
<field name="code">fed_941_fica_m_add_rate</field>
<field name="parameter_value">0.9</field>
<field name="date_from" eval="datetime(2016, 1, 1).date()"/>
</record>
</odoo>

View File

@@ -0,0 +1,95 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="res_partner_eftps_941" model="res.partner">
<field name="name">US Federal 941 - EFTPS</field>
</record>
<record id="contrib_register_eftps_941" model="hr.contribution.register">
<field name="name">EFTPS - 941 (FICA + Federal Witholding)</field>
<field name="note">Electronic Federal Tax Payment System - Form 941</field>
<field name="partner_id" ref="res_partner_eftps_941"/>
</record>
<record id="hr_payroll_category_ee_fed_941" model="hr.salary.rule.category">
<field name="name">EE: Federal 941 FICA</field>
<field name="code">EE_US_941_FICA</field>
<field name="parent_id" ref="hr_payroll.DED"/>
</record>
<record id="hr_payroll_category_er_fed_941" model="hr.salary.rule.category">
<field name="name">ER: Federal 941 FICA</field>
<field name="code">ER_US_941_FICA</field>
<field name="parent_id" ref="hr_payroll.COMP"/>
</record>
<!-- Social Security -->
<record id="hr_payroll_rule_ee_fed_941_ss" model="hr.salary.rule">
<field name="sequence" eval="190"/>
<field name="category_id" ref="hr_payroll_category_ee_fed_941"/>
<field name="name">EE: US FICA Social Security</field>
<field name="code">EE_US_941_FICA_SS</field>
<field name="condition_select">python</field>
<field name="condition_python">result, _ = ee_us_941_fica_ss(payslip, categories, worked_days, inputs)</field>
<field name="amount_select">code</field>
<field name="amount_python_compute">result, result_rate = ee_us_941_fica_ss(payslip, categories, worked_days, inputs)</field>
<field name="register_id" ref="contrib_register_eftps_941"/>
<field name="appears_on_payslip" eval="True"/>
</record>
<record id="hr_payroll_rule_er_fed_941_ss" model="hr.salary.rule">
<field name="sequence" eval="440"/>
<field name="category_id" ref="hr_payroll_category_er_fed_941"/>
<field name="name">ER: US FICA Social Security</field>
<field name="code">ER_US_941_FICA_SS</field>
<field name="condition_select">python</field>
<field name="condition_python">result, _ = er_us_941_fica_ss(payslip, categories, worked_days, inputs)</field>
<field name="amount_select">code</field>
<field name="amount_python_compute">result, result_rate = er_us_941_fica_ss(payslip, categories, worked_days, inputs)</field>
<field name="register_id" ref="contrib_register_eftps_941"/>
<field name="appears_on_payslip" eval="False"/>
</record>
<!-- Medicare -->
<record id="hr_payroll_rule_ee_fed_941_m" model="hr.salary.rule">
<field name="sequence" eval="190"/>
<field name="category_id" ref="hr_payroll_category_ee_fed_941"/>
<field name="name">EE: US FICA Medicare</field>
<field name="code">EE_US_941_FICA_M</field>
<field name="condition_select">python</field>
<field name="condition_python">result, _ = ee_us_941_fica_m(payslip, categories, worked_days, inputs)</field>
<field name="amount_select">code</field>
<field name="amount_python_compute">result, result_rate = ee_us_941_fica_m(payslip, categories, worked_days, inputs)</field>
<field name="register_id" ref="contrib_register_eftps_941"/>
<field name="appears_on_payslip" eval="True"/>
</record>
<record id="hr_payroll_rule_er_fed_941_m" model="hr.salary.rule">
<field name="sequence" eval="440"/>
<field name="category_id" ref="hr_payroll_category_er_fed_941"/>
<field name="name">ER: US FICA Medicare</field>
<field name="code">ER_US_941_FICA_M</field>
<field name="condition_select">python</field>
<field name="condition_python">result, _ = er_us_941_fica_m(payslip, categories, worked_days, inputs)</field>
<field name="amount_select">code</field>
<field name="amount_python_compute">result, result_rate = er_us_941_fica_m(payslip, categories, worked_days, inputs)</field>
<field name="register_id" ref="contrib_register_eftps_941"/>
<field name="appears_on_payslip" eval="False"/>
</record>
<!-- Medicare Additional -->
<record id="hr_payroll_rule_ee_fed_941_m_add" model="hr.salary.rule">
<field name="sequence" eval="190"/>
<field name="category_id" ref="hr_payroll_category_ee_fed_941"/>
<field name="name">EE: US FICA Medicare Additional</field>
<field name="code">EE_US_941_FICA_M_ADD</field>
<field name="condition_select">python</field>
<field name="condition_python">result, _ = ee_us_941_fica_m_add(payslip, categories, worked_days, inputs)</field>
<field name="amount_select">code</field>
<field name="amount_python_compute">result, result_rate = ee_us_941_fica_m_add(payslip, categories, worked_days, inputs)</field>
<field name="register_id" ref="contrib_register_eftps_941"/>
<field name="appears_on_payslip" eval="True"/>
</record>
</odoo>

View File

@@ -0,0 +1,492 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<!-- Shared -->
<record id="rule_parameter_941_fit_allowance_2018" model="hr.payroll.rate">
<field name="name">Federal 941 FIT Allowance</field>
<field name="code">fed_941_fit_allowance</field>
<!-- Bogus 2018 -->
<field name="parameter_value">{
'weekly': 80.80,
'bi-weekly': 161.50,
'semi-monthly': 175.00,
'monthly': 350.00,
'quarterly': 1050.00,
'semi-annually': 2100.00,
'annually': 4200.00,
}</field>
<field name="date_from" eval="datetime(2018, 1, 1).date()"/>
</record>
<record id="rule_parameter_941_fit_allowance_2019" model="hr.payroll.rate">
<field name="name">Federal 941 FIT Allowance</field>
<field name="code">fed_941_fit_allowance</field>
<field name="parameter_value">{
'weekly': 80.80,
'bi-weekly': 161.50,
'semi-monthly': 175.00,
'monthly': 350.00,
'quarterly': 1050.00,
'semi-annually': 2100.00,
'annually': 4200.00,
}</field>
<field name="date_from" eval="datetime(2019, 1, 1).date()"/>
</record>
<record id="rule_parameter_941_fit_allowance_2020" model="hr.payroll.rate">
<field name="name">Federal 941 FIT Allowance</field>
<field name="code">fed_941_fit_allowance</field>
<!-- Warning, major change to allowance in 2020 -->
<field name="parameter_value">4300.0</field>
<field name="date_from" eval="datetime(2020, 1, 1).date()"/>
</record>
<record id="rule_parameter_941_fit_nra_additional_2018" model="hr.payroll.rate">
<field name="name">Federal 941 FIT NRA Additional</field>
<field name="code">fed_941_fit_nra_additional</field>
<!-- Bogus from 2018 -->
<field name="parameter_value">{
'weekly': 153.80,
'bi-weekly': 307.70,
'semi-monthly': 333.30,
'monthly': 666.70,
'quarterly': 2000.00,
'semi-annually': 4000.00,
'annually': 8000.00,
}</field>
<field name="date_from" eval="datetime(2018, 1, 1).date()"/>
</record>
<record id="rule_parameter_941_fit_nra_additional_2019" model="hr.payroll.rate">
<field name="name">Federal 941 FIT NRA Additional</field>
<field name="code">fed_941_fit_nra_additional</field>
<field name="parameter_value">{
'weekly': 153.80,
'bi-weekly': 307.70,
'semi-monthly': 333.30,
'monthly': 666.70,
'quarterly': 2000.00,
'semi-annually': 4000.00,
'annually': 8000.00,
}</field>
<field name="date_from" eval="datetime(2019, 1, 1).date()"/>
</record>
<record id="rule_parameter_941_fit_nra_additional_2020" model="hr.payroll.rate">
<field name="name">Federal 941 FIT NRA Additional</field>
<field name="code">fed_941_fit_nra_additional</field>
<field name="parameter_value">{
'weekly': 238.50,
'bi-weekly': 476.90,
'semi-monthly': 516.70,
'monthly': 1033.30,
'quarterly': 3100.00,
'semi-annually': 6200.00,
'annually': 12400.00,
}</field>
<field name="date_from" eval="datetime(2020, 1, 1).date()"/>
</record>
<!-- Single and Married Single Rate -->
<record id="rule_parameter_941_fit_table_single_2018" model="hr.payroll.rate">
<field name="name">Federal 941 FIT Table Single</field>
<field name="code">fed_941_fit_table_single</field>
<!-- Bogus 2018 -->
<!-- But-not-over, $, % -->
<field name="parameter_value">{
'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),
],
}</field>
<field name="date_from" eval="datetime(2018, 1, 1).date()"/>
</record>
<record id="rule_parameter_941_fit_table_single_2019" model="hr.payroll.rate">
<field name="name">Federal 941 FIT Table Single</field>
<field name="code">fed_941_fit_table_single</field>
<!-- But-not-over, $, % -->
<field name="parameter_value">{
'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),
],
}</field>
<field name="date_from" eval="datetime(2019, 1, 1).date()"/>
</record>
<record id="rule_parameter_941_fit_table_single_2020" model="hr.payroll.rate">
<field name="name">Federal 941 FIT Table Single</field>
<field name="code">fed_941_fit_table_single</field>
<!-- Major changes in 2020 -->
<!-- Wage Threshold, Base Withholding Amount, Marginal Rate Over Threshold -->
<field name="parameter_value">{
'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),
],
}</field>
<field name="date_from" eval="datetime(2020, 1, 1).date()"/>
</record>
<!-- Married -->
<record id="rule_parameter_941_fit_table_married_2018" model="hr.payroll.rate">
<field name="name">Federal 941 FIT Table Married</field>
<field name="code">fed_941_fit_table_married</field>
<!-- Bogus 2018 -->
<!-- But-not-over, $, % -->
<field name="parameter_value">{
'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),
],
}</field>
<field name="date_from" eval="datetime(2018, 1, 1).date()"/>
</record>
<record id="rule_parameter_941_fit_table_married_2019" model="hr.payroll.rate">
<field name="name">Federal 941 FIT Table Married</field>
<field name="code">fed_941_fit_table_married</field>
<!-- But-not-over, $, % -->
<field name="parameter_value">{
'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),
],
}</field>
<field name="date_from" eval="datetime(2019, 1, 1).date()"/>
</record>
<record id="rule_parameter_941_fit_table_married_2020" model="hr.payroll.rate">
<field name="name">Federal 941 FIT Table Married</field>
<field name="code">fed_941_fit_table_married</field>
<!-- Major changes in 2020 -->
<!-- Wage Threshold, Base Withholding Amount, Marginal Rate Over Threshold -->
<field name="parameter_value">{
'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),
],
}</field>
<field name="date_from" eval="datetime(2020, 1, 1).date()"/>
</record>
<record id="rule_parameter_941_fit_table_hh_2020" model="hr.payroll.rate">
<field name="name">Federal 941 FIT Table Head of Household</field>
<field name="code">fed_941_fit_table_hh</field>
<!-- Major changes in 2020 -->
<!-- Wage Threshold, Base Withholding Amount, Marginal Rate Over Threshold -->
<field name="parameter_value">{
'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),
],
}</field>
<field name="date_from" eval="datetime(2020, 1, 1).date()"/>
</record>
</odoo>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="hr_payroll_category_ee_fed_941_fit" model="hr.salary.rule.category">
<field name="name">EE: Federal 941 Income Tax Withholding</field>
<field name="code">EE_US_941_FIT</field>
<field name="parent_id" ref="hr_payroll.DED"/>
</record>
<record id="hr_payroll_rule_ee_fed_941_fit" model="hr.salary.rule">
<field name="sequence" eval="190"/>
<field name="category_id" ref="hr_payroll_category_ee_fed_941_fit"/>
<field name="name">EE: US Federal Income Tax Withholding</field>
<field name="code">EE_US_941_FIT</field>
<field name="condition_select">python</field>
<field name="condition_python">result, _ = ee_us_941_fit(payslip, categories, worked_days, inputs)</field>
<field name="amount_select">code</field>
<field name="amount_python_compute">result, result_rate = ee_us_941_fit(payslip, categories, worked_days, inputs)</field>
<field name="register_id" ref="contrib_register_eftps_941"/>
<field name="appears_on_payslip" eval="True"/>
</record>
</odoo>

61
l10n_us_hr_payroll/data/final.xml Executable file → Normal file
View File

@@ -1,29 +1,58 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<!-- HR PAYROLL STRUCTURE -->
<record id="hr_payroll_salary_structure_us_employee" model="hr.payroll.structure">
<record id="structure_type_employee" model="hr.payroll.structure">
<field name="code">US_EMP</field>
<field name="name">USA Employee</field>
<field eval="[(6, 0, [
ref('hr_payroll_rules_fica_comp_ss'),
ref('hr_payroll_rules_fica_comp_m'),
ref('hr_payroll_rule_er_fed_940'),
ref('hr_payroll_rules_fica_emp_ss_wages_2018'),
ref('hr_payroll_rules_fica_emp_m_wages_2018'),
ref('hr_payroll_rules_fica_emp_m_add_wages_2018'),
ref('hr_payroll_rules_fica_emp_ss_2018'),
ref('hr_payroll_rules_fica_emp_m_2018'),
ref('hr_payroll_rules_fica_emp_m_add_2018'),
ref('hr_payroll_rules_futa_wages_2018'),
ref('hr_payroll_rules_futa_2018'),
ref('hr_payroll_rules_fed_inc_withhold_2018_single'),
ref('hr_payroll_rules_fed_inc_withhold_2018_married'),
ref('hr_payroll_rule_ee_fed_941_ss'),
ref('hr_payroll_rule_er_fed_941_ss'),
ref('hr_payroll_rule_ee_fed_941_m'),
ref('hr_payroll_rule_er_fed_941_m'),
ref('hr_payroll_rule_ee_fed_941_m_add'),
ref('hr_payroll_rule_ee_fed_941_fit'),
ref('hr_payroll_rule_er_us_fl_suta'),
ref('hr_payroll_rule_er_us_ga_suta'),
ref('hr_payroll_rule_ee_us_ga_sit'),
ref('hr_payroll_rule_er_us_ms_suta'),
ref('hr_payroll_rule_ee_us_ms_sit'),
ref('hr_payroll_rule_er_us_mt_suta'),
ref('hr_payroll_rule_er_us_mt_suta_aft'),
ref('hr_payroll_rule_ee_us_mt_sit'),
ref('hr_payroll_rule_er_us_oh_suta'),
ref('hr_payroll_rule_ee_us_oh_sit'),
ref('hr_payroll_rule_er_us_pa_suta'),
ref('hr_payroll_rule_ee_us_pa_suta'),
ref('hr_payroll_rule_ee_us_pa_sit'),
ref('hr_payroll_rule_er_us_tx_suta'),
ref('hr_payroll_rule_er_us_tx_suta_oa'),
ref('hr_payroll_rule_er_us_tx_suta_etia'),
ref('hr_payroll_rule_er_us_va_suta'),
ref('hr_payroll_rule_ee_us_va_sit'),
ref('hr_payroll_rule_er_us_wa_suta'),
ref('hr_payroll_rule_er_us_wa_fml'),
ref('hr_payroll_rule_ee_us_wa_fml'),
ref('hr_payroll_rule_er_us_wa_lni'),
ref('hr_payroll_rule_ee_us_wa_lni'),
ref('hr_salary_rule_commission'),
ref('hr_salary_rule_gamification'),
])]" name="rule_ids"/>
<field name="company_id" ref="base.main_company"/>
<field name="company_id" ref="base.main_company"/>
<field name="parent_id" ref="hr_payroll.structure_base"/>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<!-- Commissions from hr_payroll_commission -->
<record id="hr_salary_rule_commission" model="hr.salary.rule">
<field name="condition_select">python</field>
<field name="condition_python">result = inputs.COMMISSION.amount > 0.0 if inputs.COMMISSION else False</field>
<field name="amount_select">code</field>
<field name="amount_python_compute">result = inputs.COMMISSION.amount if inputs.COMMISSION else 0</field>
<field name="code">BASIC_COM</field>
<field name="category_id" ref="hr_payroll.BASIC"/>
<field name="name">Commissions</field>
<field name="sequence" eval="20"/>
</record>
<!-- Badges from hr_payroll_gamification -->
<record id="hr_salary_rule_gamification" model="hr.salary.rule">
<field name="condition_select">python</field>
<field name="condition_python">result = inputs.BADGES.amount > 0.0 if inputs.BADGES else False</field>
<field name="amount_select">code</field>
<field name="amount_python_compute">result = inputs.BADGES.amount if inputs.BADGES else 0</field>
<field name="code">BASIC_BADGES</field>
<field name="category_id" ref="hr_payroll.BASIC"/>
<field name="name">Badges</field>
<field name="sequence" eval="20"/>
</record>
</odoo>

View File

@@ -1,68 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<!-- FUTA -->
<record id="hr_payroll_rates_futa_exempt" model="hr.payroll.rate">
<field name="name">US FUTA Exempt</field>
<field name="code">US_FUTA_EXEMPT</field>
<field name="rate">0.0</field>
<field name="date_from">2016-01-01</field>
<field name="wage_limit_year" eval="7000"/>
</record>
<record id="hr_payroll_rates_futa_normal" model="hr.payroll.rate">
<field name="name">US FUTA Normal</field>
<field name="code">US_FUTA_NORMAL</field>
<field name="rate">0.6</field>
<field name="date_from">2016-01-01</field>
<field name="wage_limit_year" eval="7000"/>
</record>
<record id="hr_payroll_rates_futa_basic" model="hr.payroll.rate">
<field name="name">US FUTA Basic</field>
<field name="code">US_FUTA_BASIC</field>
<field name="rate">6.0</field>
<field name="date_from">2016-01-01</field>
<field name="wage_limit_year" eval="7000"/>
</record>
<!-- FICA -->
<!-- Social Security -->
<record id="hr_payroll_rates_fica_ss_old" model="hr.payroll.rate">
<field name="name">US FICA Social Security</field>
<field name="code">US_FICA_SS</field>
<field name="rate">6.2</field>
<field name="date_from">2016-01-01</field>
<field name="date_to">2017-12-31</field>
<field name="wage_limit_year" eval="128400.0"/>
</record>
<record id="hr_payroll_rates_fica_ss_2018" model="hr.payroll.rate">
<field name="name">US FICA Social Security</field>
<field name="code">US_FICA_SS</field>
<field name="rate">6.2</field>
<field name="date_from">2018-01-01</field>
<field name="date_to">2018-12-31</field>
<field name="wage_limit_year" eval="128400.0"/>
</record>
<record id="hr_payroll_rates_fica_ss_2019" model="hr.payroll.rate">
<field name="name">US FICA Social Security</field>
<field name="code">US_FICA_SS</field>
<field name="rate">6.2</field>
<field name="date_from">2019-01-01</field>
<field name="date_to">2019-12-31</field>
<field name="wage_limit_year" eval="132900.0"/>
</record>
<!-- Medicare -->
<record id="hr_payroll_rates_fica_m" model="hr.payroll.rate">
<field name="name">US FICA Medicare</field>
<field name="code">US_FICA_M</field>
<field name="rate">1.45</field>
<field name="date_from">2016-01-01</field>
</record>
<!-- Medicare Additional -->
<record id="hr_payroll_rates_fica_m_add" model="hr.payroll.rate">
<field name="name">US FICA Medicare Additional</field>
<field name="code">US_FICA_M_ADD</field>
<field name="rate">0.9</field>
<field name="date_from">2016-01-01</field>
<field name="wage_limit_year">200000.0</field>
</record>
</odoo>

View File

@@ -0,0 +1,61 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<!-- Wage Base -->
<data noupdate="1">
<record id="rule_parameter_us_fl_suta_wage_base_2019" model="hr.payroll.rate">
<field name="name">US FL Florida SUTA Wage Base</field>
<field name="code">us_fl_suta_wage_base</field>
<field name="parameter_value">7000.00</field>
<field name="date_from" eval="datetime(2019, 1, 1).date()"/>
</record>
<record id="rule_parameter_us_fl_suta_wage_base_2020" model="hr.payroll.rate">
<field name="name">US FL Florida SUTA Wage Base</field>
<field name="code">us_fl_suta_wage_base</field>
<field name="parameter_value">7000.00</field>
<field name="date_from" eval="datetime(2020, 1, 1).date()"/>
</record>
</data>
<!-- Rate -->
<data noupdate="1">
<record id="rule_parameter_us_fl_suta_rate_2019" model="hr.payroll.rate">
<field name="name">US FL Florida SUTA Rate</field>
<field name="code">us_fl_suta_rate</field>
<field name="parameter_value">2.7</field>
<field name="date_from" eval="datetime(2019, 1, 1).date()"/>
</record>
<record id="rule_parameter_us_fl_suta_rate_2020" model="hr.payroll.rate">
<field name="name">US FL Florida SUTA Rate</field>
<field name="code">us_fl_suta_rate</field>
<field name="parameter_value">2.7</field>
<field name="date_from" eval="datetime(2020, 1, 1).date()"/>
</record>
</data>
<!-- Partners and Contribution Registers -->
<record id="res_partner_us_fl_dor" model="res.partner">
<field name="name">US Florida - Department of Revenue</field>
</record>
<record id="contrib_register_us_fl_dor" model="hr.contribution.register">
<field name="name">US Florida - Department of Revenue (RT-6)</field>
<field name="partner_id" ref="res_partner_us_fl_dor"/>
</record>
<!-- Categories -->
<!-- Rules -->
<record id="hr_payroll_rule_er_us_fl_suta" model="hr.salary.rule">
<field name="sequence" eval="450"/>
<field name="category_id" ref="hr_payroll_category_er_us_suta"/>
<field name="name">ER: US FL Florida State Unemployment (RT-6)</field>
<field name="code">ER_US_FL_SUTA</field>
<field name="condition_select">python</field>
<field name="condition_python">result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_fl_suta_wage_base', rate='us_fl_suta_rate', state_code='FL')</field>
<field name="amount_select">code</field>
<field name="amount_python_compute">result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_fl_suta_wage_base', rate='us_fl_suta_rate', state_code='FL')</field>
<field name="register_id" ref="contrib_register_us_fl_dor"/>
<field name="appears_on_payslip" eval="False"/>
</record>
</odoo>

View File

@@ -0,0 +1,265 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<!-- Wage Base -->
<data noupdate="1">
<record id="rule_parameter_us_ga_suta_wage_base_2019" model="hr.payroll.rate">
<field name="name">US GA Georgia SUTA Wage Base</field>
<field name="code">us_ga_suta_wage_base</field>
<field name="parameter_value">9500.00</field>
<field name="date_from" eval="datetime(2019, 1, 1).date()"/>
</record>
<record id="rule_parameter_us_ga_suta_wage_base_2020" model="hr.payroll.rate">
<field name="name">US GA Georgia SUTA Wage Base</field>
<field name="code">us_ga_suta_wage_base</field>
<field name="parameter_value">9500.00</field>
<field name="date_from" eval="datetime(2020, 1, 1).date()"/>
</record>
</data>
<!-- Rate -->
<data noupdate="1">
<record id="rule_parameter_us_ga_suta_rate_2019" model="hr.payroll.rate">
<field name="name">US GA Georgia SUTA Rate</field>
<field name="code">us_ga_suta_rate</field>
<field name="parameter_value">2.7</field>
<field name="date_from" eval="datetime(2019, 1, 1).date()"/>
</record>
<record id="rule_parameter_us_ga_suta_rate_2020" model="hr.payroll.rate">
<field name="name">US GA Georgia SUTA Rate</field>
<field name="code">us_ga_suta_rate</field>
<field name="parameter_value">2.7</field>
<field name="date_from" eval="datetime(2020, 1, 1).date()"/>
</record>
</data>
<data noupdate="1">
<record id="rule_parameter_us_ga_sit_rate_2019" model="hr.payroll.rate">
<field name="name">US GA Georgia SIT Rate Table</field>
<field name="code">us_ga_sit_rate</field>
<field name="parameter_value">{
'married filing joint, both spouses working': {
'weekly': ((9.50, 0.00, 1.00), (29.00, .10, 2.00), (48.00, .48, 3.00), (67.50, 1.06, 4.00), (96.00, 1.83, 5.00), ('inf', 3.27, 5.75)),
'bi-weekly': ((19.00, 0.00, 1.00), (57.50, .19, 2.00), (96.00, .96, 3.00), (135.00, 2.12, 4.00), (192.00, 3.65, 5.00), ('inf', 6.54, 5.75)),
'semi-monthly': ((21.00, 0.00, 1.00), (62.50, .21, 2.00), (104.00, 1.04, 3.00), (146.00, 2.29, 4.00), (208.00, 3.96, 5.00), ('inf', 7.08, 5.75)),
'monthly': ((41.50, 0.00, 1.00), (125.50, .42, 2.00), (208.00, 2.08, 3.00), (292.00, 4.58, 4.00), (417.00, 7.92, 5.00), ('inf', 14.17, 5.75)),
'quarterly': ((125.00, 0.00, 1.00), (375.00, 1.25, 2.00), (625.00, 6.25, 3.00), (875.00, 13.75, 4.00), (1250.00, 23.75, 5.00), ('inf', 42.50, 5.75)),
'semi-annual': ((250.00, 0.00, 1.00), (750.00, 2.50, 2.00), (1250.00, 12.50, 3.00), (1750.00, 27.50, 4.00), (2500.00, 47.50, 5.00), ('inf', 85.00, 5.75)),
'annual': ((500.00, 0.00, 1.00), (1500.00, 5.00, 2.00), (2500.00, 25.00, 3.00), (3500.00, 55.00, 4.00), (5000.00, 95.00, 5.00), ('inf', 170.00, 5.75)),
},
'married filing joint, one spouse working': {
'weekly': ((19.00, 0.00, 1.00), (57.50, .19, 2.00), (96.00, .96, 3.00), (135.00, 2.12, 4.00), (192.50, 3.65, 5.00), ('inf', 6.54, 5.75)),
'bi-weekly': ((38.50, 0.00, 1.00), (115.00, .38, 2.00), (192.00, 1.92, 3.00), (269.00, 4.23, 4.00), (385.00, 7.31, 5.00), ('inf', 13.08, 5.75)),
'semi-monthly': ((41.50, 0.00, 1.00), (125.00, .42, 2.00), (208.00, 2.08, 3.00), (292.00, 4.58, 4.00), (417.00, 7.92, 5.00), ('inf', 14.17, 5.75)),
'monthly': ((83.00, 0.00, 1.00), (250.00, .83, 2.00), (417.00, 4.17, 3.00), (583.00, 9.17, 4.00), (833.00, 15.83, 5.00), ('inf', 28.33, 5.75)),
'quarterly': ((250.00, 0.00, 1.00), (750.00, 2.50, 2.00), (1250.00, 12.50, 3.00), (1750.00, 27.50, 4.00), (2500.00, 47.50, 5.00), ('inf', 85.00, 5.75)),
'semi-annual': ((500.00, 0.00, 1.00), (1500.00, 5.00, 2.00), (2500.00, 25.00, 3.00), (3500.00, 55.00, 4.00), (5000.00, 95.00, 5.00), ('inf', 170.00, 5.75)),
'annual': ((1000.00, 0.00, 1.00), (3000.00, 10.00, 2.00), (5000.00, 50.00, 3.00), (7000.00, 110.00, 4.00), (10000.00, 190.00, 5.00), ('inf', 340.00, 5.75)),
},
'single': {
'weekly': ((14.50, 0.00, 1.00), (43.50, .14, 2.00), (72.00, .72, 3.00), (101.00, 1.59, 4.00), (135.00, 2.74, 5.00), ('inf', 4.42, 5.75)),
'bi-weekly': ((29.00, 0.00, 1.00), (86.50, .29, 2.00), (144.00, 1.44, 3.00), (202.00, 3.17, 4.00), (269.00, 5.48, 5.00), ('inf', 8.85, 5.75)),
'semi-monthly': ((31.00, 0.00, 1.00), (93.50, .31, 2.00), (156.00, 1.56, 3.00), (219.00, 3.34, 4.00), (292.00, 5.94, 5.00), ('inf', 9.58, 5.75)),
'monthly': ((62.50, 0.00, 1.00), (187.00, .62, 2.00), (312.00, 3.12, 3.00), (437.00, 6.87, 4.00), (583.00, 11.87, 5.00), ('inf', 19.17, 5.75)),
'quarterly': ((187.50, 0.00, 1.00), (562.50, 1.88, 2.00), (937.50, 9.38, 3.00), (1312.00, 20.63, 4.00), (1750.00, 35.63, 5.00), ('inf', 57.50, 5.75)),
'semi-annual': ((375.00, 0.00, 1.00), (1125.00, 3.75, 2.00), (1875.00, 18.75, 3.00), (2625.00, 41.25, 4.00), (3500.00, 71.25, 5.00), ('inf', 115.00, 5.75)),
'annual': ((750.00, 0.00, 1.00), (2250.00, 7.50, 2.00), (3750.00, 37.50, 3.00), (5250.00, 82.50, 4.00), (7000.00, 142.50, 5.00), ('inf', 230.00, 5.75)),
},
'head of household': {
'weekly': ((19.00, 0.00, 1.00), (57.50, .19, 2.00), (96.00, .96, 3.00), (135.00, 2.12, 4.00), (192.50, 3.65, 5.00), ('inf', 6.54, 5.75)),
'bi-weekly': ((38.50, 0.00, 1.00), (115.00, .38, 2.00), (192.00, 1.92, 3.00), (269.00, 4.23, 4.00), (385.00, 7.31, 5.00), ('inf', 13.08, 5.75)),
'semi-monthly': ((41.50, 0.00, 1.00), (125.00, .42, 2.00), (208.00, 2.08, 3.00), (292.00, 4.58, 4.00), (417.00, 7.92, 5.00), ('inf', 14.17, 5.75)),
'monthly': ((83.00, 0.00, 1.00), (250.00, .83, 2.00), (417.00, 4.17, 3.00), (583.00, 9.17, 4.00), (833.00, 15.83, 5.00), ('inf', 28.33, 5.75)),
'quarterly': ((250.00, 0.00, 1.00), (750.00, 2.50, 2.00), (1250.00, 12.50, 3.00), (1750.00, 27.50, 4.00), (2500.00, 47.50, 5.00), ('inf', 85.00, 5.75)),
'semi-annual': ((500.00, 0.00, 1.00), (1500.00, 5.00, 2.00), (2500.00, 25.00, 3.00), (3500.00, 55.00, 4.00), (5000.00, 95.00, 5.00), ('inf', 170.00, 5.75)),
'annual': ((1000.00, 0.00, 1.00), (3000.00, 10.00, 2.00), (5000.00, 50.00, 3.00), (7000.00, 110.00, 4.00), (10000.00, 190.00, 5.00), ('inf', 340.00, 5.75)),
},
'married filing separate': {
'weekly': ((9.50, 0.00, 1.00), (29.00, .10, 2.00), (48.00, .48, 3.00), (67.50, 1.06, 4.00), (96.00, 1.83, 5.00), ('inf', 3.27, 5.75)),
'bi-weekly': ((19.00, 0.00, 1.00), (57.50, .19, 2.00), (96.00, .96, 3.00), (135.00, 2.12, 4.00), (192.00, 3.65, 5.00), ('inf', 6.54, 5.75)),
'semi-monthly': ((21.00, 0.00, 1.00), (62.50, .21, 2.00), (104.00, 1.04, 3.00), (146.00, 2.29, 4.00), (208.00, 3.96, 5.00), ('inf', 7.08, 5.75)),
'monthly': ((41.50, 0.00, 1.00), (125.50, .42, 2.00), (208.00, 2.08, 3.00), (292.00, 4.58, 4.00), (417.00, 7.92, 5.00), ('inf', 14.17, 5.75)),
'quarterly': ((125.00, 0.00, 1.00), (375.00, 1.25, 2.00), (625.00, 6.25, 3.00), (875.00, 13.75, 4.00), (1250.00, 23.75, 5.00), ('inf', 42.50, 5.75)),
'semi-annual': ((250.00, 0.00, 1.00), (750.00, 2.50, 2.00), (1250.00, 12.50, 3.00), (1750.00, 27.50, 4.00), (2500.00, 47.50, 5.00), ('inf', 85.00, 5.75)),
'annual': ((500.00, 0.00, 1.00), (1500.00, 5.00, 2.00), (2500.00, 25.00, 3.00), (3500.00, 55.00, 4.00), (5000.00, 95.00, 5.00), ('inf', 170.00, 5.75)),
},
}</field>
<field name="date_from" eval="datetime(2019, 1, 1).date()"/>
</record>
</data>
<data noupdate="1">
<record id="rule_parameter_us_ga_sit_personal_allowance_2019" model="hr.payroll.rate">
<field name="name">US GA Georgia SIT Personal Allowance</field>
<field name="code">us_ga_sit_personal_allowance</field>
<field name="parameter_value">{
'married filing joint, both spouses working': {
'weekly': 142.30,
'bi-weekly': 284.62,
'semi-monthly': 308.33,
'monthly': 616.67,
'quarterly': 1850.00,
'semi-annual': 3700.00,
'annual': 7400.00,
},
'married filing joint, one spouse working': {
'weekly': 142.30,
'bi-weekly': 284.62,
'semi-monthly': 308.33,
'monthly': 616.67,
'quarterly': 1850.00,
'semi-annual': 3700.00,
'annual': 7400.00,
},
'single': {
'weekly': 51.92,
'bi-weekly': 103.85,
'semi-monthly': 112.50,
'monthly': 225.00,
'quarterly': 675.00,
'semi-annual': 1350.00,
'annual': 2700.00,
},
'head of household': {
'weekly': 51.92,
'bi-weekly': 103.85,
'semi-monthly': 112.50,
'monthly': 225.00,
'quarterly': 675.00,
'semi-annual': 1350.00,
'annual': 2700.00,
},
'married filing separate': {
'weekly': 71.15,
'bi-weekly': 142.30,
'semi-monthly': 154.16,
'monthly': 308.33,
'quarterly': 925.00,
'semi-annual': 1850.00,
'annual': 3700.00,
},
}</field>
<field name="date_from" eval="datetime(2019, 1, 1).date()"/>
</record>
</data>
<data noupdate="1">
<record id="rule_parameter_us_ga_sit_dependent_allowance_rate_2019" model="hr.payroll.rate">
<field name="name">US GA Georgia SIT Dependent Allowance Rate</field>
<field name="code">us_ga_sit_dependent_allowance_rate</field>
<field name="parameter_value">{
'weekly': 57.50,
'bi-weekly': 115.00,
'semi-monthly': 125.00,
'monthly': 250.00,
'quarterly': 750.00,
'semi-annual': 1500.00,
'annual': 3000.00,
}</field>
<field name="date_from" eval="datetime(2019, 1, 1).date()"/>
</record>
</data>
<data noupdate="1">
<record id="rule_parameter_us_ga_sit_deduction_2019" model="hr.payroll.rate">
<field name="name">US GA Georgia SIT Deduction</field>
<field name="code">us_ga_sit_deduction</field>
<field name="parameter_value">{
'married filing joint, both spouses working': {
'weekly': 115.50,
'bi-weekly': 230.75,
'semi-monthly': 250.00,
'monthly': 500.00,
'quarterly': 1500.00,
'semi-annual': 3000.00,
'annual': 6000.00,
},
'married filing joint, one spouse working': {
'weekly': 115.50,
'bi-weekly': 230.75,
'semi-monthly': 250.00,
'monthly': 500.00,
'quarterly': 1500.00,
'semi-annual': 3000.00,
'annual': 6000.00,
},
'single': {
'weekly': 88.50,
'bi-weekly': 177.00,
'semi-monthly': 191.75,
'monthly': 383.50,
'quarterly': 1150.00,
'semi-annual': 2300.00,
'annual': 4600.00,
},
'head of household': {
'weekly': 88.50,
'bi-weekly': 177.00,
'semi-monthly': 191.75,
'monthly': 383.50,
'quarterly': 1150.00,
'semi-annual': 2300.00,
'annual': 4600.00,
},
'married filing separate': {
'weekly': 57.75,
'bi-weekly': 115.50,
'semi-monthly': 125.00,
'monthly': 250.00,
'quarterly': 750.00,
'semi-annual': 1500.00,
'annual': 3000.00,
},
}</field>
<field name="date_from" eval="datetime(2019, 1, 1).date()"/>
</record>
</data>
<!-- Partners and Contribution Registers -->
<record id="res_partner_us_ga_dor" model="res.partner">
<field name="name">US Georgia - Department of Taxation - Unemployment Tax</field>
</record>
<record id="contrib_register_us_ga_dor" model="hr.contribution.register">
<field name="name">US Pennsylvania - Department of Revenue - Unemployment Tax</field>
<field name="partner_id" ref="res_partner_us_ga_dor"/>
</record>
<record id="res_partner_us_ga_dor_sit" model="res.partner">
<field name="name">US Georgia - Department of Taxation - Income Tax</field>
</record>
<record id="contrib_register_us_ga_dor_sit" model="hr.contribution.register">
<field name="name">US Pennsylvania - Department of Revenue - Unemployment Tax</field>
<field name="partner_id" ref="res_partner_us_ga_dor_sit"/>
</record>
<!-- Categories -->
<!-- Rules -->
<record id="hr_payroll_rule_er_us_ga_suta" model="hr.salary.rule">
<field name="sequence" eval="450"/>
<field name="category_id" ref="hr_payroll_category_er_us_suta"/>
<field name="name">ER: US GA Georgia State Unemployment</field>
<field name="code">ER_US_GA_SUTA</field>
<field name="condition_select">python</field>
<field name="condition_python">result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_ga_suta_wage_base', rate='us_ga_suta_rate', state_code='GA')</field>
<field name="amount_select">code</field>
<field name="amount_python_compute">result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_ga_suta_wage_base', rate='us_ga_suta_rate', state_code='GA')</field>
<field name="register_id" ref="contrib_register_us_ga_dor"/>
<field name="appears_on_payslip" eval="False"/>
</record>
<record id="hr_payroll_rule_ee_us_ga_sit" model="hr.salary.rule">
<field name="sequence" eval="195"/>
<field name="category_id" ref="hr_payroll_category_ee_us_sit"/>
<field name="name">EE: US GA Georgia State Income Tax Withholding</field>
<field name="code">EE_US_GA_SIT</field>
<field name="condition_select">python</field>
<field name="condition_python">result, _ = ga_georgia_state_income_withholding(payslip, categories, worked_days, inputs)</field>
<field name="amount_select">code</field>
<field name="amount_python_compute">result, result_rate = ga_georgia_state_income_withholding(payslip, categories, worked_days, inputs)</field>
<field name="register_id" ref="contrib_register_us_ga_dor_sit"/>
<field name="appears_on_payslip" eval="True"/>
</record>
</odoo>

View File

@@ -0,0 +1,120 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<!-- Wage Base -->
<data noupdate="1">
<record id="rule_parameter_us_ms_suta_wage_base_2019" model="hr.payroll.rate">
<field name="name">US MS Mississippi SUTA Wage Base</field>
<field name="code">us_ms_suta_wage_base</field>
<field name="parameter_value">14000.0</field>
<field name="date_from" eval="datetime(2019, 1, 1).date()"/>
</record>
<record id="rule_parameter_us_ms_suta_wage_base_2020" model="hr.payroll.rate">
<field name="name">US MS Mississippi SUTA Wage Base</field>
<field name="code">us_ms_suta_wage_base</field>
<field name="parameter_value">14000.0</field>
<field name="date_from" eval="datetime(2020, 1, 1).date()"/>
</record>
</data>
<!-- Rate -->
<data noupdate="1">
<record id="rule_parameter_us_ms_suta_rate_2019" model="hr.payroll.rate">
<field name="name">US MS Mississippi SUTA Rate</field>
<field name="code">us_ms_suta_rate</field>
<field name="parameter_value">1.2</field>
<field name="date_from" eval="datetime(2019, 1, 1).date()"/>
</record>
<record id="rule_parameter_us_ms_suta_rate_2020" model="hr.payroll.rate">
<field name="name">US MS Mississippi SUTA Rate</field>
<field name="code">us_ms_suta_rate</field>
<field name="parameter_value">1.2</field>
<field name="date_from" eval="datetime(2020, 1, 1).date()"/>
</record>
</data>
<data noupdate="1">
<record id="rule_parameter_us_ms_sit_rate_2019" model="hr.payroll.rate">
<field name="name">US MS Mississippi SIT Rate Table</field>
<field name="code">us_ms_sit_rate</field>
<field name="parameter_value">[
( 10000.00, 290.0, 0.05),
( 5000.00, 90.0, 0.04),
( 2000.00, 0.0, 0.03),
]</field>
<field name="date_from" eval="datetime(2019, 1, 1).date()"/>
</record>
<record id="rule_parameter_us_ms_sit_rate_2020" model="hr.payroll.rate">
<field name="name">US MS Mississippi SIT Rate Table</field>
<field name="code">us_ms_sit_rate</field>
<field name="parameter_value">[
( 10000.00, 260.0, 0.05),
( 5000.00, 60.0, 0.04),
( 3000.00, 0.0, 0.03),
]</field>
<field name="date_from" eval="datetime(2020, 1, 1).date()"/>
</record>
</data>
<data noupdate="1">
<record id="rule_parameter_us_ms_sit_deduction_2019" model="hr.payroll.rate">
<field name="name">US MS Mississippi SIT Deduction</field>
<field name="code">us_ms_sit_deduction</field>
<field name="parameter_value">{
'single': 2300.0,
'head_of_household': 3400.0,
'married_dual': 2300.0,
'married': 4600.0,
}</field>
<field name="date_from" eval="datetime(2019, 1, 1).date()"/>
</record>
</data>
<!-- Partners and Contribution Registers -->
<record id="res_partner_us_ms_dor" model="res.partner">
<field name="name">US Mississippi - Department of Employment Security (Unemployment)</field>
</record>
<record id="contrib_register_us_ms_dor" model="hr.contribution.register">
<field name="name">US Mississippi - Department of Employment Security (Unemployment)</field>
<field name="partner_id" ref="res_partner_us_ms_dor"/>
</record>
<record id="res_partner_us_ms_dor_sit" model="res.partner">
<field name="name">US Mississippi - Mississippi Department of Revenue (Income Tax)</field>
</record>
<record id="contrib_register_us_ms_dor_sit" model="hr.contribution.register">
<field name="name">US Mississippi - Mississippi Department of Revenue (Income Tax)</field>
<field name="partner_id" ref="res_partner_us_ms_dor_sit"/>
</record>
<!-- Categories -->
<!-- Rules -->
<record id="hr_payroll_rule_er_us_ms_suta" model="hr.salary.rule">
<field name="sequence" eval="450"/>
<field name="category_id" ref="hr_payroll_category_er_us_suta"/>
<field name="name">ER: US MS Mississippi State Unemployment</field>
<field name="code">ER_US_MS_SUTA</field>
<field name="condition_select">python</field>
<field name="condition_python">result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_ms_suta_wage_base', rate='us_ms_suta_rate', state_code='MS')</field>
<field name="amount_select">code</field>
<field name="amount_python_compute">result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_ms_suta_wage_base', rate='us_ms_suta_rate', state_code='MS')</field>
<field name="register_id" ref="contrib_register_us_ms_dor"/>
<field name="appears_on_payslip" eval="False"/>
</record>
<record id="hr_payroll_rule_ee_us_ms_sit" model="hr.salary.rule">
<field name="sequence" eval="195"/>
<field name="category_id" ref="hr_payroll_category_ee_us_sit"/>
<field name="name">EE: US MS Mississippi State Income Tax Withholding</field>
<field name="code">EE_US_MS_SIT</field>
<field name="condition_select">python</field>
<field name="condition_python">result, _ = ms_mississippi_state_income_withholding(payslip, categories, worked_days, inputs)</field>
<field name="amount_select">code</field>
<field name="amount_python_compute">result, result_rate = ms_mississippi_state_income_withholding(payslip, categories, worked_days, inputs)</field>
<field name="register_id" ref="contrib_register_us_ms_dor_sit"/>
<field name="appears_on_payslip" eval="True"/>
</record>
</odoo>

View File

@@ -0,0 +1,166 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<!-- Wage Base -->
<data noupdate="1">
<record id="rule_parameter_us_mt_suta_wage_base_2019" model="hr.payroll.rate">
<field name="name">US MT Montana SUTA Wage Base</field>
<field name="code">us_mt_suta_wage_base</field>
<field name="parameter_value">33000.00</field>
<field name="date_from" eval="datetime(2019, 1, 1).date()"/>
</record>
<record id="rule_parameter_us_mt_suta_wage_base_2020" model="hr.payroll.rate">
<field name="name">US MT Montana SUTA Wage Base</field>
<field name="code">us_mt_suta_wage_base</field>
<field name="parameter_value">34100.00</field>
<field name="date_from" eval="datetime(2020, 1, 1).date()"/>
</record>
</data>
<!-- Rate -->
<data noupdate="1">
<record id="rule_parameter_us_mt_suta_rate_2019" model="hr.payroll.rate">
<field name="name">US MT Montana SUTA Rate (UI)</field>
<field name="code">us_mt_suta_rate</field>
<field name="parameter_value">1.18</field>
<field name="date_from" eval="datetime(2019, 1, 1).date()"/>
</record>
<record id="rule_parameter_us_mt_suta_rate_2020" model="hr.payroll.rate">
<field name="name">US MT Montana SUTA Rate (UI)</field>
<field name="code">us_mt_suta_rate</field>
<field name="parameter_value">1.18</field>
<field name="date_from" eval="datetime(2020, 1, 1).date()"/>
</record>
</data>
<data noupdate="1">
<record id="rule_parameter_us_mt_suta_aft_rate_2019" model="hr.payroll.rate">
<field name="name">US MT Montana SUTA Administrative Fund Tax Rate</field>
<field name="code">us_mt_suta_aft_rate</field>
<field name="parameter_value">0.13</field>
<field name="date_from" eval="datetime(2019, 1, 1).date()"/>
</record>
<record id="rule_parameter_us_mt_suta_aft_rate_2020" model="hr.payroll.rate">
<field name="name">US MT Montana SUTA Administrative Fund Tax Rate</field>
<field name="code">us_mt_suta_aft_rate</field>
<field name="parameter_value">0.13</field>
<field name="date_from" eval="datetime(2020, 1, 1).date()"/>
</record>
</data>
<data noupdate="1">
<record id="rule_parameter_us_mt_sit_rate_2019" model="hr.payroll.rate">
<field name="name">US MT Montana SIT Rate Table</field>
<field name="code">us_mt_sit_rate</field>
<field name="parameter_value">{
'weekly': [
( 135.00, 0.0, 1.80),
( 288.00, 2.0, 4.40),
( 2308.00, 9.0, 6.00),
( 'inf', 130.0, 6.60),
],
'bi-weekly': [
( 269.00, 0.0, 1.80),
( 577.00, 5.0, 4.40),
( 4615.00, 18.0, 6.00),
( 'inf', 261.0, 6.60),
],
'semi-monthly': [
( 292.00, 0.0, 1.80),
( 625.00, 5.0, 4.40),
( 5000.00, 20.0, 6.00),
( 'inf', 282.0, 6.60),
],
'monthly': [
( 583.00, 0.0, 1.80),
( 1250.00, 11.0, 4.40),
( 10000.00, 40.0, 6.00),
( 'inf', 565.0, 6.60),
],
'annually': [
( 7000.00, 0.0, 1.80),
( 15000.00, 126.0, 4.40),
( 120000.00, 478.0, 6.00),
( 'inf', 6778.0, 6.60),
],
}</field>
<field name="date_from" eval="datetime(2019, 1, 1).date()"/>
</record>
</data>
<data noupdate="1">
<record id="rule_parameter_us_mt_sit_exemption_rate_2019" model="hr.payroll.rate">
<field name="name">US MT Montana SIT Exemption Rate Table</field>
<field name="code">us_mt_sit_exemption_rate</field>
<field name="parameter_value">{
'weekly': 37.0,
'bi-weekly': 73.0,
'semi-monthly': 79.0,
'monthly': 158.0,
'annually': 1900.0,
}</field>
<field name="date_from" eval="datetime(2019, 1, 1).date()"/>
</record>
</data>
<!-- Partners and Contribution Registers -->
<record id="res_partner_us_mt_dor" model="res.partner">
<field name="name">US Montana - Department of Labor &amp; Industries</field>
</record>
<record id="contrib_register_us_mt_dor" model="hr.contribution.register">
<field name="name">US Montana - Department of Labor &amp; Industries</field>
<field name="partner_id" ref="res_partner_us_mt_dor"/>
</record>
<record id="res_partner_us_mt_dor_sit" model="res.partner">
<field name="name">US Montana - Department of Revenue - Income Tax</field>
</record>
<record id="contrib_register_us_mt_dor_sit" model="hr.contribution.register">
<field name="name">US Montana - Department of Revenue - Income Tax</field>
<field name="partner_id" ref="res_partner_us_mt_dor_sit"/>
</record>
<!-- Categories -->
<!-- Rules -->
<record id="hr_payroll_rule_er_us_mt_suta" model="hr.salary.rule">
<field name="sequence" eval="450"/>
<field name="category_id" ref="hr_payroll_category_er_us_suta"/>
<field name="name">ER: US MT Montana State Unemployment (UI-5)</field>
<field name="code">ER_US_MT_SUTA</field>
<field name="condition_select">python</field>
<field name="condition_python">result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_mt_suta_wage_base', rate='us_mt_suta_rate', state_code='MT')</field>
<field name="amount_select">code</field>
<field name="amount_python_compute">result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_mt_suta_wage_base', rate='us_mt_suta_rate', state_code='MT')</field>
<field name="register_id" ref="contrib_register_us_mt_dor"/>
<field name="appears_on_payslip" eval="False"/>
</record>
<record id="hr_payroll_rule_er_us_mt_suta_aft" model="hr.salary.rule">
<field name="sequence" eval="451"/>
<field name="category_id" ref="hr_payroll_category_er_us_suta"/>
<field name="name">ER: US MT Montana State Unemployment Administrative Fund Tax (AFT) (UI-5)</field>
<field name="code">ER_US_MT_SUTA_AFT</field>
<field name="condition_select">python</field>
<field name="condition_python">result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_mt_suta_wage_base', rate='us_mt_suta_aft_rate', state_code='MT')</field>
<field name="amount_select">code</field>
<field name="amount_python_compute">result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_mt_suta_wage_base', rate='us_mt_suta_aft_rate', state_code='MT')</field>
<field name="register_id" ref="contrib_register_us_mt_dor"/>
<field name="appears_on_payslip" eval="False"/>
</record>
<record id="hr_payroll_rule_ee_us_mt_sit" model="hr.salary.rule">
<field name="sequence" eval="195"/>
<field name="category_id" ref="hr_payroll_category_ee_us_sit"/>
<field name="name">EE: US MT Montana State Income Tax Withholding (MW-3)</field>
<field name="code">EE_US_MT_SIT</field>
<field name="condition_select">python</field>
<field name="condition_python">result, _ = mt_montana_state_income_withholding(payslip, categories, worked_days, inputs)</field>
<field name="amount_select">code</field>
<field name="amount_python_compute">result, result_rate = mt_montana_state_income_withholding(payslip, categories, worked_days, inputs)</field>
<field name="register_id" ref="contrib_register_us_mt_dor_sit"/>
<field name="appears_on_payslip" eval="True"/>
</record>
</odoo>

View File

@@ -0,0 +1,150 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<!-- Wage Base -->
<data noupdate="1">
<record id="rule_parameter_us_oh_suta_wage_base_2019" model="hr.payroll.rate">
<field name="name">US OH Ohio SUTA Wage Base</field>
<field name="code">us_oh_suta_wage_base</field>
<field name="parameter_value">9500.00</field>
<field name="date_from" eval="datetime(2019, 1, 1).date()"/>
</record>
<record id="rule_parameter_us_oh_suta_wage_base_2020" model="hr.payroll.rate">
<field name="name">US OH Ohio SUTA Wage Base</field>
<field name="code">us_oh_suta_wage_base</field>
<field name="parameter_value">9000.00</field>
<field name="date_from" eval="datetime(2020, 1, 1).date()"/>
</record>
</data>
<!-- Rate -->
<data noupdate="1">
<record id="rule_parameter_us_oh_suta_rate_2019" model="hr.payroll.rate">
<field name="name">US OH Ohio SUTA Rate</field>
<field name="code">us_oh_suta_rate</field>
<field name="parameter_value">2.7</field>
<field name="date_from" eval="datetime(2019, 1, 1).date()"/>
</record>
<record id="rule_parameter_us_oh_suta_rate_2020" model="hr.payroll.rate">
<field name="name">US OH Ohio SUTA Rate</field>
<field name="code">us_oh_suta_rate</field>
<field name="parameter_value">2.7</field>
<field name="date_from" eval="datetime(2020, 1, 1).date()"/>
</record>
</data>
<data noupdate="1">
<record id="rule_parameter_us_oh_sit_rate_2019" model="hr.payroll.rate">
<field name="name">US OH Ohio SIT Rate Table</field>
<field name="code">us_oh_sit_rate</field>
<!-- https://www.tax.ohio.gov/Portals/0/employer_withholding/2019%20tables/WTH_OptionalComputerFormula_2019.pdf -->
<!-- wage_less_than, base_amount, rate_over -->
<field name="parameter_value">[
( 5000.00, 0.0, 0.005),
( 10000.00, 25.0, 0.010),
( 15000.00, 75.0, 0.020),
( 20000.00, 175.0, 0.025),
( 40000.00, 300.0, 0.030),
( 80000.00, 900.0, 0.035),
( 100000.00, 2300.0, 0.040),
( 'inf', 3100.0, 0.050),
]</field>
<field name="date_from" eval="datetime(2019, 1, 1).date()"/>
</record>
<record id="rule_parameter_us_oh_sit_rate_2020" model="hr.payroll.rate">
<field name="name">US OH Ohio SIT Rate Table</field>
<field name="code">us_oh_sit_rate</field>
<!-- https://www.tax.ohio.gov/Portals/0/employer_withholding/2020%20tables/WTH_OptionalComputerFormula_2020.pdf -->
<!-- wage_less_than, base_amount, rate_over -->
<field name="parameter_value">[
( 5000.00, 0.0, 0.005),
( 10000.00, 25.0, 0.010),
( 15000.00, 75.0, 0.020),
( 20000.00, 175.0, 0.025),
( 40000.00, 300.0, 0.030),
( 80000.00, 900.0, 0.035),
( 100000.00, 2300.0, 0.040),
( 'inf', 3100.0, 0.050),
]</field>
<field name="date_from" eval="datetime(2020, 1, 1).date()"/>
</record>
</data>
<data noupdate="1">
<record id="rule_parameter_us_oh_sit_exemption_rate_2019" model="hr.payroll.rate">
<field name="name">US OH Ohio SIT Exemption Rate</field>
<field name="code">us_oh_sit_exemption_rate</field>
<field name="parameter_value">650.0</field>
<field name="date_from" eval="datetime(2019, 1, 1).date()"/>
</record>
<record id="rule_parameter_us_oh_sit_exemption_rate_2020" model="hr.payroll.rate">
<field name="name">US OH Ohio SIT Exemption Rate</field>
<field name="code">us_oh_sit_exemption_rate</field>
<field name="parameter_value">650.0</field>
<field name="date_from" eval="datetime(2020, 1, 1).date()"/>
</record>
</data>
<data noupdate="1">
<record id="rule_parameter_us_oh_sit_multiplier_2019" model="hr.payroll.rate">
<field name="name">US OH Ohio SIT Multiplier Value</field>
<field name="code">us_oh_sit_multiplier</field>
<field name="parameter_value">1.075</field>
<field name="date_from" eval="datetime(2019, 1, 1).date()"/>
</record>
<record id="rule_parameter_us_oh_sit_multiplier_2020" model="hr.payroll.rate">
<field name="name">US OH Ohio SIT Multiplier Value</field>
<field name="code">us_oh_sit_multiplier</field>
<field name="parameter_value">1.032</field>
<field name="date_from" eval="datetime(2020, 1, 1).date()"/>
</record>
</data>
<!-- Partners and Contribution Registers -->
<record id="res_partner_us_oh_dor" model="res.partner">
<field name="name">US Ohio - OBG - Unemployment</field>
</record>
<record id="contrib_register_us_oh_dor" model="hr.contribution.register">
<field name="name">US Ohio - OBG - Unemployment</field>
<field name="partner_id" ref="res_partner_us_oh_dor"/>
</record>
<record id="res_partner_us_oh_dor_sit" model="res.partner">
<field name="name">US Ohio - OBG - Income Withholding</field>
</record>
<record id="contrib_register_us_oh_dor_sit" model="hr.contribution.register">
<field name="name">US Ohio - OBG - Income Withholding</field>
<field name="partner_id" ref="res_partner_us_oh_dor_sit"/>
</record>
<!-- Categories -->
<!-- Rules -->
<record id="hr_payroll_rule_er_us_oh_suta" model="hr.salary.rule">
<field name="sequence" eval="450"/>
<field name="category_id" ref="hr_payroll_category_er_us_suta"/>
<field name="name">ER: US OH Ohio State Unemployment (JFS-20125)</field>
<field name="code">ER_US_OH_SUTA</field>
<field name="condition_select">python</field>
<field name="condition_python">result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_oh_suta_wage_base', rate='us_oh_suta_rate', state_code='OH')</field>
<field name="amount_select">code</field>
<field name="amount_python_compute">result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_oh_suta_wage_base', rate='us_oh_suta_rate', state_code='OH')</field>
<field name="register_id" ref="contrib_register_us_oh_dor"/>
<field name="appears_on_payslip" eval="False"/>
</record>
<record id="hr_payroll_rule_ee_us_oh_sit" model="hr.salary.rule">
<field name="sequence" eval="195"/>
<field name="category_id" ref="hr_payroll_category_ee_us_sit"/>
<field name="name">EE: US OH Ohio State Income Tax Withholding (IT 501)</field>
<field name="code">EE_US_OH_SIT</field>
<field name="condition_select">python</field>
<field name="condition_python">result, _ = oh_ohio_state_income_withholding(payslip, categories, worked_days, inputs)</field>
<field name="amount_select">code</field>
<field name="amount_python_compute">result, result_rate = oh_ohio_state_income_withholding(payslip, categories, worked_days, inputs)</field>
<field name="register_id" ref="contrib_register_us_oh_dor_sit"/>
<field name="appears_on_payslip" eval="True"/>
</record>
</odoo>

View File

@@ -0,0 +1,126 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<!-- Wage Base -->
<data noupdate="1">
<record id="rule_parameter_us_pa_suta_wage_base_2019" model="hr.payroll.rate">
<field name="name">US PA Pennsylvania SUTA Wage Base (ER)</field>
<field name="code">us_pa_suta_wage_base</field>
<field name="parameter_value">10000.00</field>
<field name="date_from" eval="datetime(2019, 1, 1).date()"/>
</record>
<record id="rule_parameter_us_pa_suta_wage_base_2020" model="hr.payroll.rate">
<field name="name">US PA Pennsylvania SUTA Wage Base (ER)</field>
<field name="code">us_pa_suta_wage_base</field>
<field name="parameter_value">10000.00</field>
<field name="date_from" eval="datetime(2020, 1, 1).date()"/>
</record>
</data>
<!-- Rate -->
<data noupdate="1">
<record id="rule_parameter_us_pa_suta_rate_2019" model="hr.payroll.rate">
<field name="name">US PA Pennsylvania SUTA Rate</field>
<field name="code">us_pa_suta_rate</field>
<field name="parameter_value">3.6890</field>
<field name="date_from" eval="datetime(2019, 1, 1).date()"/>
</record>
<record id="rule_parameter_us_pa_suta_rate_2020" model="hr.payroll.rate">
<field name="name">US PA Pennsylvania SUTA Rate</field>
<field name="code">us_pa_suta_rate</field>
<field name="parameter_value">3.6890</field>
<field name="date_from" eval="datetime(2020, 1, 1).date()"/>
</record>
</data>
<data noupdate="1">
<record id="rule_parameter_us_pa_suta_ee_rate_2019" model="hr.payroll.rate">
<field name="name">US PA Pennsylvania SUTA Employee Rate</field>
<field name="code">us_pa_suta_ee_rate</field>
<field name="parameter_value">0.06</field>
<field name="date_from" eval="datetime(2019, 1, 1).date()"/>
</record>
<record id="rule_parameter_us_pa_suta_ee_rate_2020" model="hr.payroll.rate">
<field name="name">US PA Pennsylvania SUTA Employee Rate</field>
<field name="code">us_pa_suta_ee_rate</field>
<field name="parameter_value">0.06</field>
<field name="date_from" eval="datetime(2020, 1, 1).date()"/>
</record>
</data>
<data noupdate="1">
<record id="rule_parameter_us_pa_sit_rate_2019" model="hr.payroll.rate">
<field name="name">US PA Pennsylvania SIT Rate</field>
<field name="code">us_pa_sit_rate</field>
<field name="parameter_value">3.07</field>
<field name="date_from" eval="datetime(2019, 1, 1).date()"/>
</record>
<record id="rule_parameter_us_pa_sit_rate_2020" model="hr.payroll.rate">
<field name="name">US PA Pennsylvania SIT Rate</field>
<field name="code">us_pa_sit_rate</field>
<field name="parameter_value">3.07</field>
<field name="date_from" eval="datetime(2020, 1, 1).date()"/>
</record>
</data>
<!-- Partners and Contribution Registers -->
<record id="res_partner_us_pa_dor" model="res.partner">
<field name="name">US Pennsylvania - Department of Revenue - Unemployment Tax</field>
</record>
<record id="contrib_register_us_pa_dor" model="hr.contribution.register">
<field name="name">US Pennsylvania - Department of Revenue - Unemployment Tax</field>
<field name="partner_id" ref="res_partner_us_pa_dor"/>
</record>
<record id="res_partner_us_pa_dor_sit" model="res.partner">
<field name="name">US Pennsylvania - Department of Revenue - Income Tax</field>
</record>
<record id="contrib_register_us_pa_dor_sit" model="hr.contribution.register">
<field name="name">US Pennsylvania - Department of Revenue - Income Tax</field>
<field name="partner_id" ref="res_partner_us_pa_dor_sit"/>
</record>
<!-- Categories -->
<!-- Rules -->
<record id="hr_payroll_rule_er_us_pa_suta" model="hr.salary.rule">
<field name="sequence" eval="450"/>
<field name="category_id" ref="hr_payroll_category_er_us_suta"/>
<field name="name">ER: US PA Pennsylvania State Unemployment (UC-2)</field>
<field name="code">ER_US_PA_SUTA</field>
<field name="condition_select">python</field>
<field name="condition_python">result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_pa_suta_wage_base', rate='us_pa_suta_rate', state_code='PA')</field>
<field name="amount_select">code</field>
<field name="amount_python_compute">result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_pa_suta_wage_base', rate='us_pa_suta_rate', state_code='PA')</field>
<field name="register_id" ref="contrib_register_us_pa_dor"/>
<field name="appears_on_payslip" eval="False"/>
</record>
<record id="hr_payroll_rule_ee_us_pa_suta" model="hr.salary.rule">
<field name="sequence" eval="195"/>
<field name="category_id" ref="hr_payroll_category_ee_us_suta"/>
<field name="name">EE: US PA Pennsylvania State Unemployment (UC-2)</field>
<field name="code">EE_US_PA_SUTA</field>
<field name="condition_select">python</field>
<field name="condition_python">result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, rate='us_pa_suta_ee_rate', state_code='PA')</field>
<field name="amount_select">code</field>
<field name="amount_python_compute">result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, rate='us_pa_suta_ee_rate', state_code='PA')</field>
<field name="register_id" ref="contrib_register_us_pa_dor"/>
<field name="appears_on_payslip" eval="True"/>
</record>
<record id="hr_payroll_rule_ee_us_pa_sit" model="hr.salary.rule">
<field name="sequence" eval="195"/>
<field name="category_id" ref="hr_payroll_category_ee_us_sit"/>
<field name="name">EE: US PA Pennsylvania State Income Tax Withholding (PA-501)</field>
<field name="code">EE_US_PA_SIT</field>
<field name="condition_select">python</field>
<field name="condition_python">result, _ = general_state_income_withholding(payslip, categories, worked_days, inputs, rate='us_pa_sit_rate', state_code='PA')</field>
<field name="amount_select">code</field>
<field name="amount_python_compute">result, result_rate = general_state_income_withholding(payslip, categories, worked_days, inputs, rate='us_pa_sit_rate', state_code='PA')</field>
<field name="register_id" ref="contrib_register_us_pa_dor_sit"/>
<field name="appears_on_payslip" eval="True"/>
</record>
</odoo>

View File

@@ -0,0 +1,117 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<!-- Wage Base -->
<data noupdate="1">
<record id="rule_parameter_us_tx_suta_wage_base_2019" model="hr.payroll.rate">
<field name="name">US TX Texas SUTA Wage Base</field>
<field name="code">us_tx_suta_wage_base</field>
<field name="parameter_value">9000.0</field>
<field name="date_from" eval="datetime(2019, 1, 1).date()"/>
</record>
<record id="rule_parameter_us_tx_suta_wage_base_2020" model="hr.payroll.rate">
<field name="name">US TX Texas SUTA Wage Base</field>
<field name="code">us_tx_suta_wage_base</field>
<field name="parameter_value">9000.0</field>
<field name="date_from" eval="datetime(2020, 1, 1).date()"/>
</record>
</data>
<!-- Rate -->
<data noupdate="1">
<record id="rule_parameter_us_tx_suta_rate_2019" model="hr.payroll.rate">
<field name="name">US TX Texas SUTA Rate</field>
<field name="code">us_tx_suta_rate</field>
<field name="parameter_value">2.7</field>
<field name="date_from" eval="datetime(2019, 1, 1).date()"/>
</record>
<record id="rule_parameter_us_tx_suta_rate_2020" model="hr.payroll.rate">
<field name="name">US TX Texas SUTA Rate</field>
<field name="code">us_tx_suta_rate</field>
<field name="parameter_value">2.7</field>
<field name="date_from" eval="datetime(2020, 1, 1).date()"/>
</record>
</data>
<data noupdate="1">
<record id="rule_parameter_us_tx_suta_oa_rate_2019" model="hr.payroll.rate">
<field name="name">US TX Texas Obligation Assessment Rate</field>
<field name="code">us_tx_suta_oa_rate</field>
<field name="parameter_value">0.0</field>
<field name="date_from" eval="datetime(2019, 1, 1).date()"/>
</record>
<record id="rule_parameter_us_tx_suta_oa_rate_2020" model="hr.payroll.rate">
<field name="name">US TX Texas Obligation Assessment Rate</field>
<field name="code">us_tx_suta_oa_rate</field>
<field name="parameter_value">0.0</field>
<field name="date_from" eval="datetime(2020, 1, 1).date()"/>
</record>
</data>
<data noupdate="1">
<record id="rule_parameter_us_tx_suta_etia_rate_2019" model="hr.payroll.rate">
<field name="name">US TX Texas Employment &amp; Training Investment Assessment Rate</field>
<field name="code">us_tx_suta_etia_rate</field>
<field name="parameter_value">0.1</field>
<field name="date_from" eval="datetime(2019, 1, 1).date()"/>
</record>
<record id="rule_parameter_us_tx_suta_etia_rate_2020" model="hr.payroll.rate">
<field name="name">US TX Texas Employment &amp; Training Investment Assessment Rate</field>
<field name="code">us_tx_suta_etia_rate</field>
<field name="parameter_value">0.1</field>
<field name="date_from" eval="datetime(2020, 1, 1).date()"/>
</record>
</data>
<!-- Partners and Contribution Registers -->
<record id="res_partner_us_tx_dor" model="res.partner">
<field name="name">US Texas - Workforce Commission (Unemployment)</field>
</record>
<record id="contrib_register_us_tx_dor" model="hr.contribution.register">
<field name="name">US Texas - Workforce Commission (Unemployment)</field>
<field name="partner_id" ref="res_partner_us_tx_dor"/>
</record>
<!-- Categories -->
<!-- Rules -->
<record id="hr_payroll_rule_er_us_tx_suta" model="hr.salary.rule">
<field name="sequence" eval="450"/>
<field name="category_id" ref="hr_payroll_category_er_us_suta"/>
<field name="name">ER: US TX Texas State Unemployment (C-3)</field>
<field name="code">ER_US_TX_SUTA</field>
<field name="condition_select">python</field>
<field name="condition_python">result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_tx_suta_wage_base', rate='us_tx_suta_rate', state_code='TX')</field>
<field name="amount_select">code</field>
<field name="amount_python_compute">result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_tx_suta_wage_base', rate='us_tx_suta_rate', state_code='TX')</field>
<field name="register_id" ref="contrib_register_us_tx_dor"/>
<field name="appears_on_payslip" eval="False"/>
</record>
<record id="hr_payroll_rule_er_us_tx_suta_oa" model="hr.salary.rule">
<field name="sequence" eval="451"/>
<field name="category_id" ref="hr_payroll_category_er_us_suta"/>
<field name="name">ER: US TX Texas Obligation Assessment (C-3)</field>
<field name="code">ER_US_TX_SUTA_OA</field>
<field name="condition_select">python</field>
<field name="condition_python">result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_tx_suta_wage_base', rate='us_tx_suta_oa_rate', state_code='TX')</field>
<field name="amount_select">code</field>
<field name="amount_python_compute">result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_tx_suta_wage_base', rate='us_tx_suta_oa_rate', state_code='TX')</field>
<field name="register_id" ref="contrib_register_us_tx_dor"/>
<field name="appears_on_payslip" eval="False"/>
</record>
<record id="hr_payroll_rule_er_us_tx_suta_etia" model="hr.salary.rule">
<field name="sequence" eval="451"/>
<field name="category_id" ref="hr_payroll_category_er_us_suta"/>
<field name="name">ER: US TX Texas Employment &amp; Training Investment Assessment (C-3)</field>
<field name="code">ER_US_TX_SUTA_ETIA</field>
<field name="condition_select">python</field>
<field name="condition_python">result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_tx_suta_wage_base', rate='us_tx_suta_etia_rate', state_code='TX')</field>
<field name="amount_select">code</field>
<field name="amount_python_compute">result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_tx_suta_wage_base', rate='us_tx_suta_etia_rate', state_code='TX')</field>
<field name="register_id" ref="contrib_register_us_tx_dor"/>
<field name="appears_on_payslip" eval="False"/>
</record>
</odoo>

View File

@@ -0,0 +1,124 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<!-- Wage Base -->
<data noupdate="1">
<record id="rule_parameter_us_va_suta_wage_base_2019" model="hr.payroll.rate">
<field name="name">US VA Virginia SUTA Wage Base</field>
<field name="code">us_va_suta_wage_base</field>
<field name="parameter_value">8000.0</field>
<field name="date_from" eval="datetime(2019, 1, 1).date()"/>
</record>
<record id="rule_parameter_us_va_suta_wage_base_2020" model="hr.payroll.rate">
<field name="name">US VA Virginia SUTA Wage Base</field>
<field name="code">us_va_suta_wage_base</field>
<field name="parameter_value">8000.0</field>
<field name="date_from" eval="datetime(2020, 1, 1).date()"/>
</record>
</data>
<!-- Rate -->
<data noupdate="1">
<record id="rule_parameter_us_va_suta_rate_2019" model="hr.payroll.rate">
<field name="name">US VA Virginia SUTA Rate</field>
<field name="code">us_va_suta_rate</field>
<field name="parameter_value">2.51</field>
<field name="date_from" eval="datetime(2019, 1, 1).date()"/>
</record>
<record id="rule_parameter_us_va_suta_rate_2020" model="hr.payroll.rate">
<field name="name">US VA Virginia SUTA Rate</field>
<field name="code">us_va_suta_rate</field>
<field name="parameter_value">2.51</field>
<field name="date_from" eval="datetime(2020, 1, 1).date()"/>
</record>
</data>
<data noupdate="1">
<record id="rule_parameter_us_va_sit_rate_2019" model="hr.payroll.rate">
<field name="name">US VA Virginia SIT Rate Table</field>
<field name="code">us_va_sit_rate</field>
<field name="parameter_value">[
( 0.00, 0.0, 2.00),
( 3000.00, 60.0, 3.00),
( 5000.00, 120.0, 5.00),
( 17000.00, 720.0, 5.75),
]</field>
<field name="date_from" eval="datetime(2019, 1, 1).date()"/>
</record>
</data>
<data noupdate="1">
<record id="rule_parameter_us_va_sit_exemption_rate_2019" model="hr.payroll.rate">
<field name="name">US VA Virginia SIT Exemption Rate Table</field>
<field name="code">us_va_sit_exemption_rate</field>
<field name="parameter_value">930.0</field>
<field name="date_from" eval="datetime(2019, 1, 1).date()"/>
</record>
</data>
<data noupdate="1">
<record id="rule_parameter_us_va_sit_other_exemption_rate_2019" model="hr.payroll.rate">
<field name="name">US VA Virginia SIT Other Exemption Rate Table</field>
<field name="code">us_va_sit_other_exemption_rate</field>
<field name="parameter_value">800.0</field>
<field name="date_from" eval="datetime(2019, 1, 1).date()"/>
</record>
</data>
<data noupdate="1">
<record id="rule_parameter_us_va_sit_deduction_2019" model="hr.payroll.rate">
<field name="name">US VA Virginia SIT Deduction</field>
<field name="code">us_va_sit_deduction</field>
<field name="parameter_value">4500.0</field>
<field name="date_from" eval="datetime(2019, 1, 1).date()"/>
</record>
</data>
<!-- Partners and Contribution Registers -->
<record id="res_partner_us_va_dor" model="res.partner">
<field name="name">US Virginia - Department of Taxation - Unemployment Tax</field>
</record>
<record id="contrib_register_us_va_dor" model="hr.contribution.register">
<field name="name">US Virginia - Department of Taxation - Unemployment Tax</field>
<field name="partner_id" ref="res_partner_us_va_dor"/>
</record>
<record id="res_partner_us_va_dor_sit" model="res.partner">
<field name="name">US Virginia - Department of Taxation - Income Tax</field>
</record>
<record id="contrib_register_us_va_dor_sit" model="hr.contribution.register">
<field name="name">US Virginia - Department of Taxation - Income Tax</field>
<field name="partner_id" ref="res_partner_us_va_dor_sit"/>
</record>
<!-- Categories -->
<!-- Rules -->
<record id="hr_payroll_rule_er_us_va_suta" model="hr.salary.rule">
<field name="sequence" eval="450"/>
<field name="category_id" ref="hr_payroll_category_er_us_suta"/>
<field name="name">ER: US VA Virginia State Unemployment</field>
<field name="code">ER_US_VA_SUTA</field>
<field name="condition_select">python</field>
<field name="condition_python">result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_va_suta_wage_base', rate='us_va_suta_rate', state_code='VA')</field>
<field name="amount_select">code</field>
<field name="amount_python_compute">result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_va_suta_wage_base', rate='us_va_suta_rate', state_code='VA')</field>
<field name="register_id" ref="contrib_register_us_va_dor"/>
<field name="appears_on_payslip" eval="False"/>
</record>
<record id="hr_payroll_rule_ee_us_va_sit" model="hr.salary.rule">
<field name="sequence" eval="195"/>
<field name="category_id" ref="hr_payroll_category_ee_us_sit"/>
<field name="name">EE: US VA Virginia State Income Tax Withholding</field>
<field name="code">EE_US_VA_SIT</field>
<field name="condition_select">python</field>
<field name="condition_python">result, _ = va_virginia_state_income_withholding(payslip, categories, worked_days, inputs)</field>
<field name="amount_select">code</field>
<field name="amount_python_compute">result, result_rate = va_virginia_state_income_withholding(payslip, categories, worked_days, inputs)</field>
<field name="register_id" ref="contrib_register_us_va_dor_sit"/>
<field name="appears_on_payslip" eval="True"/>
</record>
</odoo>

View File

@@ -0,0 +1,203 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<!-- Wage Base -->
<data noupdate="1">
<record id="rule_parameter_us_wa_suta_wage_base_2019" model="hr.payroll.rate">
<field name="name">US WA Washington SUTA Wage Base</field>
<field name="code">us_wa_suta_wage_base</field>
<field name="parameter_value">49800.0</field>
<field name="date_from" eval="datetime(2019, 1, 1).date()"/>
</record>
<record id="rule_parameter_us_wa_suta_wage_base_2020" model="hr.payroll.rate">
<field name="name">US WA Washington SUTA Wage Base</field>
<field name="code">us_wa_suta_wage_base</field>
<field name="parameter_value">52700.00</field>
<field name="date_from" eval="datetime(2020, 1, 1).date()"/>
</record>
</data>
<data noupdate="1">
<record id="rule_parameter_us_wa_fml_wage_base_2019" model="hr.payroll.rate">
<field name="name">US WA Washington FML Wage Base</field>
<field name="code">us_wa_fml_wage_base</field>
<field name="parameter_value">132900.00</field>
<field name="date_from" eval="datetime(2019, 1, 1).date()"/>
</record>
<record id="rule_parameter_us_wa_fml_wage_base_2020" model="hr.payroll.rate">
<field name="name">US WA Washington FML Wage Base</field>
<field name="code">us_wa_fml_wage_base</field>
<field name="parameter_value">137700.00</field>
<field name="date_from" eval="datetime(2020, 1, 1).date()"/>
</record>
</data>
<!-- Rate -->
<data noupdate="1">
<record id="rule_parameter_us_wa_suta_rate_2019" model="hr.payroll.rate">
<field name="name">US WA Washington SUTA Rate</field>
<field name="code">us_wa_suta_rate</field>
<field name="parameter_value">1.18</field>
<field name="date_from" eval="datetime(2019, 1, 1).date()"/>
</record>
<record id="rule_parameter_us_wa_suta_rate_2020" model="hr.payroll.rate">
<field name="name">US WA Washington SUTA Rate</field>
<field name="code">us_wa_suta_rate</field>
<field name="parameter_value">1.0</field>
<field name="date_from" eval="datetime(2020, 1, 1).date()"/>
</record>
</data>
<data noupdate="1">
<record id="rule_parameter_us_wa_fml_rate_2019" model="hr.payroll.rate">
<field name="name">US WA Washington FML Rate (Total)</field>
<field name="code">us_wa_fml_rate</field>
<field name="parameter_value">0.4</field>
<field name="date_from" eval="datetime(2019, 1, 1).date()"/>
</record>
<record id="rule_parameter_us_wa_fml_rate_2020" model="hr.payroll.rate">
<field name="name">US WA Washington FML Rate (Total)</field>
<field name="code">us_wa_fml_rate</field>
<field name="parameter_value">0.4</field>
<field name="date_from" eval="datetime(2020, 1, 1).date()"/>
</record>
</data>
<data noupdate="1">
<record id="rule_parameter_us_wa_fml_rate_ee_2019" model="hr.payroll.rate">
<field name="name">US WA Washington FML Rate (Employee)</field>
<field name="code">us_wa_fml_rate_ee</field>
<field name="parameter_value">66.33</field>
<field name="date_from" eval="datetime(2019, 1, 1).date()"/>
</record>
<record id="rule_parameter_us_wa_fml_rate_ee_2020" model="hr.payroll.rate">
<field name="name">US WA Washington FML Rate (Employee)</field>
<field name="code">us_wa_fml_rate_ee</field>
<field name="parameter_value">66.33</field>
<field name="date_from" eval="datetime(2020, 1, 1).date()"/>
</record>
</data>
<data noupdate="1">
<record id="rule_parameter_us_wa_fml_rate_er_2019" model="hr.payroll.rate">
<field name="name">US WA Washington FML Rate (Employer)</field>
<field name="code">us_wa_fml_rate_er</field>
<field name="parameter_value">33.67</field>
<field name="date_from" eval="datetime(2019, 1, 1).date()"/>
</record>
<record id="rule_parameter_us_wa_fml_rate_er_2020" model="hr.payroll.rate">
<field name="name">US WA Washington FML Rate (Employer)</field>
<field name="code">us_wa_fml_rate_er</field>
<field name="parameter_value">33.67</field>
<field name="date_from" eval="datetime(2020, 1, 1).date()"/>
</record>
</data>
<!-- Partners and Contribution Registers -->
<record id="res_partner_us_wa_dor" model="res.partner">
<field name="name">US Washington - Employment Security Department (Unemployment)</field>
</record>
<record id="contrib_register_us_wa_dor" model="hr.contribution.register">
<field name="name">US Washington - Employment Security Department (Unemployment)</field>
<field name="partner_id" ref="res_partner_us_wa_dor"/>
</record>
<record id="res_partner_us_wa_dor_lni" model="res.partner">
<field name="name">US Washington - Department of Labor &amp; Industries</field>
</record>
<record id="contrib_register_us_wa_dor_lni" model="hr.contribution.register">
<field name="name">US Washington - Department of Labor &amp; Industries</field>
<field name="partner_id" ref="res_partner_us_wa_dor_lni"/>
</record>
<record id="res_partner_us_wa_dor_fml" model="res.partner">
<field name="name">US Washington - Employment Security Department (PFML)</field>
</record>
<record id="contrib_register_us_wa_dor_fml" model="hr.contribution.register">
<field name="name">US Washington - Employment Security Department (PFML)</field>
<field name="partner_id" ref="res_partner_us_wa_dor_fml"/>
</record>
<!-- Categories -->
<!-- Rules -->
<record id="hr_payroll_rule_er_us_wa_suta" model="hr.salary.rule">
<field name="sequence" eval="450"/>
<field name="category_id" ref="hr_payroll_category_er_us_suta"/>
<field name="name">ER: US WA Washington State Unemployment (5208A/B)</field>
<field name="code">ER_US_WA_SUTA</field>
<field name="condition_select">python</field>
<field name="condition_python">result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_wa_suta_wage_base', rate='us_wa_suta_rate', state_code='WA')</field>
<field name="amount_select">code</field>
<field name="amount_python_compute">result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_wa_suta_wage_base', rate='us_wa_suta_rate', state_code='WA')</field>
<field name="register_id" ref="contrib_register_us_wa_dor"/>
<field name="appears_on_payslip" eval="False"/>
</record>
<record id="hr_payroll_rule_er_us_wa_fml" model="hr.salary.rule">
<field name="sequence" eval="451"/>
<field name="category_id" ref="hr_payroll.COMP"/>
<field name="name">ER: US WA Washington State Family Medical Leave</field>
<field name="code">ER_US_WA_FML</field>
<field name="condition_select">python</field>
<field name="condition_python">result, _ = wa_washington_fml_er(payslip, categories, worked_days, inputs)</field>
<field name="amount_select">code</field>
<field name="amount_python_compute">result, result_rate = wa_washington_fml_er(payslip, categories, worked_days, inputs)</field>
<field name="register_id" ref="contrib_register_us_wa_dor_fml"/>
<field name="appears_on_payslip" eval="False"/>
</record>
<record id="hr_payroll_rule_ee_us_wa_fml" model="hr.salary.rule">
<field name="sequence" eval="196"/>
<field name="category_id" ref="hr_payroll.DED"/>
<field name="name">EE: US WA Washington State Family Medical Leave</field>
<field name="code">EE_US_WA_FML</field>
<field name="condition_select">python</field>
<field name="condition_python">result, _ = wa_washington_fml_ee(payslip, categories, worked_days, inputs)</field>
<field name="amount_select">code</field>
<field name="amount_python_compute">result, result_rate = wa_washington_fml_ee(payslip, categories, worked_days, inputs)</field>
<field name="register_id" ref="contrib_register_us_wa_dor_fml"/>
<field name="appears_on_payslip" eval="True"/>
</record>
<!-- LNI May need to be updated depending on hours worked (or drywall laid) -->
<record id="hr_payroll_rule_er_us_wa_lni" model="hr.salary.rule">
<field name="sequence" eval="451"/>
<field name="category_id" ref="hr_payroll.COMP"/>
<field name="name">ER: US WA Washington State LNI</field>
<field name="code">ER_US_WA_LNI</field>
<field name="condition_select">python</field>
<field name="condition_python">result = is_us_state(payslip, 'WA') and payslip.contract_id.us_payroll_config_value('workers_comp_er_code') and worked_days.WORK100 and worked_days.WORK100.number_of_hours and payslip.dict.rule_parameter(payslip.contract_id.us_payroll_config_value('workers_comp_er_code'))</field>
<field name="amount_select">code</field>
<field name="amount_python_compute">
hours = worked_days.WORK100.number_of_hours
rate = payslip.dict.rule_parameter(payslip.contract_id.us_payroll_config_value('workers_comp_er_code'))
try:
# Redo employee withholding calculation
ee_withholding = worked_days.WORK100.number_of_hours * -payslip.dict.rule_parameter(payslip.contract_id.us_payroll_config_value('workers_comp_ee_code')) / 100.0
except:
ee_withholding = 0.0
er_withholding = -(hours * (rate / 100.0)) - ee_withholding
result = hours
result_rate = (er_withholding / hours) * 100.0
</field>
<field name="register_id" ref="contrib_register_us_wa_dor_lni"/>
<field name="appears_on_payslip" eval="False"/>
</record>
<record id="hr_payroll_rule_ee_us_wa_lni" model="hr.salary.rule">
<field name="sequence" eval="196"/>
<field name="category_id" ref="hr_payroll.DED"/>
<field name="name">EE: US WA Washington State LNI</field>
<field name="code">EE_US_WA_LNI</field>
<field name="condition_select">python</field>
<field name="condition_python">result = is_us_state(payslip, 'WA') and payslip.contract_id.us_payroll_config_value('workers_comp_ee_code') and worked_days.WORK100 and worked_days.WORK100.number_of_hours and payslip.dict.rule_parameter(payslip.contract_id.us_payroll_config_value('workers_comp_ee_code'))</field>
<field name="amount_select">code</field>
<field name="amount_python_compute">result, result_rate = worked_days.WORK100.number_of_hours, -payslip.dict.rule_parameter(payslip.contract_id.us_payroll_config_value('workers_comp_ee_code'))</field>
<field name="register_id" ref="contrib_register_us_wa_dor_lni"/>
<field name="appears_on_payslip" eval="True"/>
</record>
</odoo>

View File

@@ -0,0 +1,93 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
from odoo.addons.l10n_us_hr_payroll.migrations.data import FIELDS_CONTRACT_TO_US_PAYROLL_FORMS_2020, \
XMLIDS_COPY_ACCOUNTING_2020
from odoo.addons.l10n_us_hr_payroll.migrations.helper import field_exists, \
temp_field_exists, \
remove_temp_field, \
temp_field_values
from odoo import SUPERUSER_ID
from odoo.api import Environment
import logging
_logger = logging.getLogger(__name__)
def migrate(cr, installed_version):
fields_to_move = [f for f in FIELDS_CONTRACT_TO_US_PAYROLL_FORMS_2020 if temp_field_exists(cr, 'hr_contract', f)]
if not fields_to_move:
_logger.warn(' migration aborted because no temporary fields exist...')
return
env = Environment(cr, SUPERUSER_ID, {})
new_structure = env.ref('l10n_us_hr_payroll.structure_type_employee')
def get_state(code, cache={}):
country_key = 'US_COUNTRY'
if code in cache:
return cache[code]
if country_key not in cache:
cache[country_key] = env.ref('base.us')
us_country = cache[country_key]
us_state = env['res.country.state'].search([
('country_id', '=', us_country.id),
('code', '=', code),
], limit=1)
cache[code] = us_state
return us_state
# We will assume all contracts without a struct (because we deleted it), or with one like US_xx_EMP, need config
contracts = env['hr.contract'].search([
('employee_id', '!=', False),
'|',
('struct_id', '=', False),
('struct_id.code', '=like', 'US_%'),
])
_logger.warn('Migrating Contracts: ' + str(contracts))
for contract in contracts:
_logger.warn('Migrating contract: ' + str(contract) + ' for employee: ' + str(contract.employee_id))
if not contract.employee_id.id:
_logger.warn(' unable to migrate for missing employee id')
continue
# Could we somehow detect the state off of the current/orphaned salary structure?
state_code = False
old_struct_code = contract.struct_id.code
if old_struct_code:
state_code = old_struct_code.split('_')[1]
temp_values = temp_field_values(cr, 'hr_contract', contract.id, fields_to_move)
# Resolve mapping to the new field names.
values = {FIELDS_CONTRACT_TO_US_PAYROLL_FORMS_2020[k]: v for k, v in temp_values.items()}
values.update({
'name': 'MIG: ' + str(contract.name),
'employee_id': contract.employee_id.id,
'state_id': get_state(state_code).id,
})
us_payroll_config = env['hr.contract.us_payroll_config'].create(values)
contract.write({
'struct_id': new_structure.id,
'us_payroll_config_id': us_payroll_config.id,
})
for field in fields_to_move:
remove_temp_field(cr, 'hr_contract', field)
# Some added rules should have the same accounting side effects of other migrated rules
# To ease the transition, we will copy the accounting fields from one to the other.
rule_model = env['hr.salary.rule']
if hasattr(rule_model, 'account_debit'):
for source, destinations in XMLIDS_COPY_ACCOUNTING_2020.items():
source_rule = env.ref(source, raise_if_not_found=False)
if source_rule:
for destination in destinations:
destination_rule = env.ref(destination, raise_if_not_found=False)
if destination_rule:
_logger.warn('Mirgrating accounting from rule: ' + source + ' to rule: ' + destination)
destination_rule.write({
'account_debit': source_rule.account_debit.id,
'account_credit': source_rule.account_credit.id,
'account_tax_id': source_rule.account_tax_id.id,
'analytic_account_id': source_rule.analytic_account_id.id,
})

View File

@@ -0,0 +1,29 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
from odoo.addons.l10n_us_hr_payroll.migrations.data import FIELDS_CONTRACT_TO_US_PAYROLL_FORMS_2020, \
XMLIDS_TO_REMOVE_2020, \
XMLIDS_TO_RENAME_2020
from odoo.addons.l10n_us_hr_payroll.migrations.helper import field_exists, \
temp_field_exists, \
make_temp_field, \
remove_xmlid, \
rename_xmlid
def migrate(cr, installed_version):
# Add temporary columns for all hr_contract fields that move to hr_contract_us_payroll_config
fields_to_move = [f for f in FIELDS_CONTRACT_TO_US_PAYROLL_FORMS_2020 if field_exists(cr, 'hr_contract', f)]
# Prevent error if repeatedly running and already copied.
fields_to_move = [f for f in fields_to_move if not temp_field_exists(cr, 'hr_contract', f)]
for field in fields_to_move:
make_temp_field(cr, 'hr_contract', field)
# Need to migrate XMLIDs..
for xmlid in XMLIDS_TO_REMOVE_2020:
remove_xmlid(cr, xmlid)
for from_xmlid, to_xmlid in XMLIDS_TO_RENAME_2020.items():
rename_xmlid(cr, from_xmlid, to_xmlid)
# Need to remove views as they don't work anymore.
cr.execute("DELETE FROM ir_ui_view as v WHERE v.id in (SELECT t.res_id FROM ir_model_data as t WHERE t.model = 'ir.ui.view' and (t.module = 'l10n_us_hr_payroll' or t.module like 'l10n_us_%_hr_payroll'))")

View File

@@ -0,0 +1 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.

View File

@@ -0,0 +1,191 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
FIELDS_CONTRACT_TO_US_PAYROLL_FORMS_2020 = {
# Federal
'w4_allowances': 'fed_941_fit_w4_allowances',
'w4_filing_status': 'fed_941_fit_w4_filing_status',
'w4_is_nonresident_alien': 'fed_941_fit_w4_is_nonresident_alien',
'w4_additional_withholding': 'fed_941_fit_w4_additional_withholding',
'fica_exempt': 'fed_941_fica_exempt',
'futa_type': 'fed_940_type',
# State
'ga_g4_filing_status': 'ga_g4_sit_filing_status',
'ga_g4_dependent_allowances': 'ga_g4_sit_dependent_allowances',
'ga_g4_additional_allowances': 'ga_g4_sit_additional_allowances',
'ga_g4_additional_wh': 'state_income_tax_additional_withholding',
'ms_89_350_filing_status': 'ms_89_350_sit_filing_status',
'ms_89_350_exemption': 'ms_89_350_sit_exemption_value',
'ms_89_350_additional_withholding': 'state_income_tax_additional_withholding',
'mt_mw4_additional_withholding': 'state_income_tax_additional_withholding',
'mt_mw4_exemptions': 'mt_mw4_sit_exemptions',
'mt_mw4_exempt': 'mt_mw4_sit_exempt',
'oh_additional_withholding': 'state_income_tax_additional_withholding',
'oh_income_allowances': 'oh_it4_sit_exemptions',
'pa_additional_withholding': 'state_income_tax_additional_withholding',
'va_va4_exemptions': 'va_va4_sit_exemptions',
}
XMLIDS_TO_REMOVE_2020 = [
# Federal
# Categories -- These are now all up in the EE FICA or ER FICA
'l10n_us_hr_payroll.hr_payroll_fica_emp_m',
'l10n_us_hr_payroll.hr_payroll_fica_emp_m_add',
'l10n_us_hr_payroll.hr_payroll_fica_emp_m_add_wages',
'l10n_us_hr_payroll.hr_payroll_fica_comp_m',
'l10n_us_hr_payroll.hr_payroll_futa_wages',
'l10n_us_hr_payroll.hr_payroll_fica_emp_m_wages',
'l10n_us_hr_payroll.hr_payroll_fica_emp_ss_wages',
# Rules -- These are mainly Wage rules or were simplified to a single rule
'l10n_us_hr_payroll.hr_payroll_rules_fica_emp_ss_wages_2018',
'l10n_us_hr_payroll.hr_payroll_rules_fica_emp_m_wages_2018',
'l10n_us_hr_payroll.hr_payroll_rules_fica_emp_m_add_wages_2018',
'l10n_us_hr_payroll.hr_payroll_rules_futa_wages_2018',
'l10n_us_hr_payroll.hr_payroll_rules_fed_inc_withhold_2018_married',
# State
'l10n_us_fl_hr_payroll.hr_payroll_fl_unemp_wages',
'l10n_us_fl_hr_payroll.hr_payroll_fl_unemp',
'l10n_us_fl_hr_payroll.hr_payroll_rules_fl_unemp_wages_2018',
'l10n_us_ga_hr_payroll.hr_payroll_ga_unemp_wages',
'l10n_us_ga_hr_payroll.hr_payroll_ga_unemp',
'l10n_us_ga_hr_payroll.hr_payroll_ga_income_withhold',
'l10n_us_ga_hr_payroll.hr_payroll_rules_ga_unemp_wages',
'l10n_us_ms_hr_payroll.hr_payroll_ms_unemp_wages',
'l10n_us_ms_hr_payroll.hr_payroll_ms_unemp',
'l10n_us_ms_hr_payroll.hr_payroll_ms_income_withhold',
'l10n_us_ms_hr_payroll.hr_payroll_rules_ms_unemp_wages',
'l10n_us_mt_hr_payroll.hr_payroll_mt_unemp_wages',
'l10n_us_mt_hr_payroll.hr_payroll_mt_unemp',
'l10n_us_mt_hr_payroll.hr_payroll_mt_income_withhold',
'l10n_us_mt_hr_payroll.hr_payroll_rules_mt_unemp_wages',
'l10n_us_oh_hr_payroll.hr_payroll_oh_unemp_wages',
'l10n_us_oh_hr_payroll.hr_payroll_oh_unemp',
'l10n_us_oh_hr_payroll.hr_payroll_oh_income_withhold',
'l10n_us_oh_hr_payroll.hr_payroll_rules_oh_unemp_wages_2018',
'l10n_us_pa_hr_payroll.res_partner_pador_unemp_employee',
'l10n_us_pa_hr_payroll.contrib_register_pador_unemp_employee',
'l10n_us_pa_hr_payroll.hr_payroll_pa_unemp_wages',
'l10n_us_pa_hr_payroll.hr_payroll_pa_unemp_employee',
'l10n_us_pa_hr_payroll.hr_payroll_pa_unemp_company',
'l10n_us_pa_hr_payroll.hr_payroll_pa_withhold',
'l10n_us_pa_hr_payroll.hr_payroll_rules_pa_unemp_wages_2018',
'l10n_us_pa_hr_payroll.hr_payroll_rules_pa_inc_withhold_add',
'l10n_us_tx_hr_payroll.contrib_register_txdor',
'l10n_us_tx_hr_payroll.hr_payroll_tx_unemp_wages',
'l10n_us_tx_hr_payroll.hr_payroll_tx_unemp',
'l10n_us_tx_hr_payroll.hr_payroll_tx_oa',
'l10n_us_tx_hr_payroll.hr_payroll_tx_etia',
'l10n_us_tx_hr_payroll.hr_payroll_rules_tx_unemp_wages_2018',
'l10n_us_va_hr_payroll.hr_payroll_va_unemp_wages',
'l10n_us_va_hr_payroll.hr_payroll_va_unemp',
'l10n_us_va_hr_payroll.hr_payroll_va_income_withhold',
'l10n_us_va_hr_payroll.hr_payroll_rules_va_unemp_wages_2018',
'l10n_us_wa_hr_payroll.hr_payroll_wa_unemp_wages',
'l10n_us_wa_hr_payroll.hr_payroll_wa_unemp',
'l10n_us_wa_hr_payroll.hr_payroll_wa_lni',
'l10n_us_wa_hr_payroll.hr_payroll_wa_lni_withhold',
'l10n_us_wa_hr_payroll.hr_payroll_rules_wa_unemp_wages_2018',
]
XMLIDS_TO_RENAME_2020 = {
# Federal
'l10n_us_hr_payroll.hr_payroll_futa': 'l10n_us_hr_payroll.hr_payroll_category_er_fed_940',
'l10n_us_hr_payroll.hr_payroll_fica_emp_ss': 'l10n_us_hr_payroll.hr_payroll_category_ee_fed_941',
'l10n_us_hr_payroll.hr_payroll_fed_income_withhold': 'l10n_us_hr_payroll.hr_payroll_category_ee_fed_941_fit',
'l10n_us_hr_payroll.hr_payroll_fica_comp_ss': 'l10n_us_hr_payroll.hr_payroll_category_er_fed_941',
'l10n_us_hr_payroll.hr_payroll_rules_fica_emp_ss_2018': 'l10n_us_hr_payroll.hr_payroll_rule_ee_fed_941_ss',
'l10n_us_hr_payroll.hr_payroll_rules_fica_emp_m_2018': 'l10n_us_hr_payroll.hr_payroll_rule_ee_fed_941_m',
'l10n_us_hr_payroll.hr_payroll_rules_fica_emp_m_add_2018': 'l10n_us_hr_payroll.hr_payroll_rule_ee_fed_941_m_add',
'l10n_us_hr_payroll.hr_payroll_rules_futa_2018': 'l10n_us_hr_payroll.hr_payroll_rule_er_fed_940',
'l10n_us_hr_payroll.hr_payroll_rules_fica_comp_ss': 'l10n_us_hr_payroll.hr_payroll_rule_er_fed_941_ss',
'l10n_us_hr_payroll.hr_payroll_rules_fica_comp_m': 'l10n_us_hr_payroll.hr_payroll_rule_er_fed_941_m',
'l10n_us_hr_payroll.hr_payroll_rules_fed_inc_withhold_2018_single': 'l10n_us_hr_payroll.hr_payroll_rule_ee_fed_941_fit',
# State
'l10n_us_fl_hr_payroll.hr_payroll_rules_fl_unemp_2018': 'l10n_us_hr_payroll.hr_payroll_rule_er_us_fl_suta',
'l10n_us_fl_hr_payroll.res_partner_fldor': 'l10n_us_hr_payroll.res_partner_us_fl_dor',
'l10n_us_fl_hr_payroll.contrib_register_fldor': 'l10n_us_hr_payroll.contrib_register_us_fl_dor',
'l10n_us_ga_hr_payroll.res_partner_ga_dol_unemp': 'l10n_us_hr_payroll.res_partner_us_ga_dor',
'l10n_us_ga_hr_payroll.res_partner_ga_dor_withhold': 'l10n_us_hr_payroll.res_partner_us_ga_dor_sit',
'l10n_us_ga_hr_payroll.contrib_register_ga_dol_unemp': 'l10n_us_hr_payroll.contrib_register_us_ga_dor',
'l10n_us_ga_hr_payroll.contrib_register_ga_dor_withhold': 'l10n_us_hr_payroll.contrib_register_us_ga_dor_sit',
'l10n_us_ga_hr_payroll.hr_payroll_rules_ga_unemp': 'l10n_us_hr_payroll.hr_payroll_rule_er_us_ga_suta',
'l10n_us_ga_hr_payroll.hr_payroll_rules_ga_inc_withhold': 'l10n_us_hr_payroll.hr_payroll_rule_ee_us_ga_sit',
'l10n_us_ms_hr_payroll.res_partner_msdor_unemp': 'l10n_us_hr_payroll.res_partner_us_ms_dor',
'l10n_us_ms_hr_payroll.res_partner_msdor_withhold': 'l10n_us_hr_payroll.res_partner_us_ms_dor_sit',
'l10n_us_ms_hr_payroll.contrib_register_msdor_unemp': 'l10n_us_hr_payroll.contrib_register_us_ms_dor',
'l10n_us_ms_hr_payroll.contrib_register_msdor_withhold': 'l10n_us_hr_payroll.contrib_register_us_ms_dor_sit',
'l10n_us_ms_hr_payroll.hr_payroll_rules_ms_unemp': 'l10n_us_hr_payroll.hr_payroll_rule_er_us_ms_suta',
'l10n_us_ms_hr_payroll.hr_payroll_rules_ms_inc_withhold': 'l10n_us_hr_payroll.hr_payroll_rule_ee_us_ms_sit',
'l10n_us_mt_hr_payroll.res_partner_mtdor_unemp': 'l10n_us_hr_payroll.res_partner_us_mt_dor',
'l10n_us_mt_hr_payroll.res_partner_mtdor_withhold': 'l10n_us_hr_payroll.res_partner_us_mt_dor_sit',
'l10n_us_mt_hr_payroll.contrib_register_mtdor_unemp': 'l10n_us_hr_payroll.contrib_register_us_mt_dor',
'l10n_us_mt_hr_payroll.contrib_register_mtdor_withhold': 'l10n_us_hr_payroll.contrib_register_us_mt_dor_sit',
'l10n_us_mt_hr_payroll.hr_payroll_rules_mt_unemp': 'l10n_us_hr_payroll.hr_payroll_rule_er_us_mt_suta',
'l10n_us_mt_hr_payroll.hr_payroll_rules_mt_inc_withhold': 'l10n_us_hr_payroll.hr_payroll_rule_ee_us_mt_sit',
'l10n_us_oh_hr_payroll.res_partner_ohdor_unemp': 'l10n_us_hr_payroll.res_partner_us_oh_dor',
'l10n_us_oh_hr_payroll.res_partner_ohdor_withhold': 'l10n_us_hr_payroll.res_partner_us_oh_dor_sit',
'l10n_us_oh_hr_payroll.res_partner_ohdor_unemp': 'l10n_us_hr_payroll.res_partner_us_oh_dor',
'l10n_us_oh_hr_payroll.res_partner_ohdor_withhold': 'l10n_us_hr_payroll.res_partner_us_oh_dor_sit',
'l10n_us_oh_hr_payroll.hr_payroll_rules_oh_unemp_2018': 'l10n_us_hr_payroll.hr_payroll_rule_er_us_oh_suta',
'l10n_us_oh_hr_payroll.hr_payroll_rules_oh_inc_withhold_2018': 'l10n_us_hr_payroll.hr_payroll_rule_ee_us_oh_sit',
'l10n_us_pa_hr_payroll.res_partner_pador_unemp_company': 'l10n_us_hr_payroll.res_partner_us_pa_dor',
'l10n_us_pa_hr_payroll.res_partner_pador_withhold': 'l10n_us_hr_payroll.res_partner_us_pa_dor_sit',
'l10n_us_pa_hr_payroll.contrib_register_pador_unemp_company': 'l10n_us_hr_payroll.contrib_register_us_pa_dor',
'l10n_us_pa_hr_payroll.contrib_register_pador_withhold': 'l10n_us_hr_payroll.contrib_register_us_pa_dor_sit',
'l10n_us_pa_hr_payroll.hr_payroll_rules_pa_unemp_employee_2018': 'l10n_us_hr_payroll.hr_payroll_rule_ee_us_pa_suta',
'l10n_us_pa_hr_payroll.hr_payroll_rules_pa_unemp_company_2018': 'l10n_us_hr_payroll.hr_payroll_rule_er_us_pa_suta',
'l10n_us_pa_hr_payroll.hr_payroll_rules_pa_inc_withhold_2018': 'l10n_us_hr_payroll.hr_payroll_rule_ee_us_pa_sit',
'l10n_us_tx_hr_payroll.res_partner_txdor': 'l10n_us_hr_payroll.res_partner_us_tx_dor',
'l10n_us_tx_hr_payroll.contrib_register_txdor': 'l10n_us_hr_payroll.contrib_register_us_tx_dor',
'l10n_us_tx_hr_payroll.hr_payroll_rules_tx_unemp_2018': 'l10n_us_hr_payroll.hr_payroll_rule_er_us_tx_suta',
'l10n_us_tx_hr_payroll.hr_payroll_rules_tx_oa_2018': 'l10n_us_hr_payroll.hr_payroll_rule_er_us_tx_suta_oa',
'l10n_us_tx_hr_payroll.hr_payroll_rules_tx_etia_2018': 'l10n_us_hr_payroll.hr_payroll_rule_er_us_tx_suta_etia',
'l10n_us_va_hr_payroll.res_partner_vador_unemp': 'l10n_us_hr_payroll.res_partner_us_va_dor',
'l10n_us_va_hr_payroll.res_partner_vador_withhold': 'l10n_us_hr_payroll.res_partner_us_va_dor_sit',
'l10n_us_va_hr_payroll.contrib_register_vador_unemp': 'l10n_us_hr_payroll.contrib_register_us_va_dor',
'l10n_us_va_hr_payroll.contrib_register_vador_withhold': 'l10n_us_hr_payroll.contrib_register_us_va_dor_sit',
'l10n_us_va_hr_payroll.hr_payroll_rules_va_unemp_2018': 'l10n_us_hr_payroll.hr_payroll_rule_er_us_va_suta',
'l10n_us_va_hr_payroll.hr_payroll_rules_va_inc_withhold_2018': 'l10n_us_hr_payroll.hr_payroll_rule_ee_us_va_sit',
'l10n_us_wa_hr_payroll.res_partner_wador_unemp': 'l10n_us_hr_payroll.res_partner_us_wa_dor',
'l10n_us_wa_hr_payroll.res_partner_wador_lni': 'l10n_us_hr_payroll.res_partner_us_wa_dor_lni',
'l10n_us_wa_hr_payroll.contrib_register_wador_unemp': 'l10n_us_hr_payroll.contrib_register_us_wa_dor',
'l10n_us_wa_hr_payroll.contrib_register_wador_lni': 'l10n_us_hr_payroll.contrib_register_us_wa_dor_lni',
'l10n_us_wa_hr_payroll.hr_payroll_rules_wa_unemp_2018': 'l10n_us_hr_payroll.hr_payroll_rule_er_us_wa_suta',
'l10n_us_wa_hr_payroll.hr_payroll_rules_wa_lni_withhold': 'l10n_us_hr_payroll.hr_payroll_rule_ee_us_wa_lni',
'l10n_us_wa_hr_payroll.hr_payroll_rules_wa_lni': 'l10n_us_hr_payroll.hr_payroll_rule_er_us_wa_lni',
}
XMLIDS_COPY_ACCOUNTING_2020 = {
'l10n_us_hr_payroll.hr_payroll_rule_er_us_mt_suta': [
'l10n_us_hr_payroll.hr_payroll_rule_er_us_mt_suta_aft',
],
'l10n_us_hr_payroll.hr_payroll_rule_er_us_wa_lni': [
'l10n_us_hr_payroll.hr_payroll_rule_er_us_wa_fml',
],
'l10n_us_hr_payroll.hr_payroll_rule_ee_us_wa_lni': [
'l10n_us_hr_payroll.hr_payroll_rule_ee_us_wa_fml',
],
}

View File

@@ -0,0 +1,63 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
TMP_PREFIX = 'tmp_'
"""
Fields
"""
def field_exists(cr, table_name, field_name):
cr.execute('SELECT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name=%s and column_name=%s);', (table_name, field_name))
return cr.fetchone()[0]
def temp_field_exists(cr, table_name, field_name):
tmp_field_name = TMP_PREFIX + field_name
cr.execute('SELECT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name=%s and column_name=%s);', (table_name, tmp_field_name))
return cr.fetchone()[0]
def make_temp_field(cr, table_name, field_name):
tmp_field_name = TMP_PREFIX + field_name
cr.execute('SELECT data_type FROM information_schema.columns WHERE table_name=%s and column_name=%s;', (table_name, field_name))
tmp_field_type = cr.fetchone()[0]
cr.execute('ALTER TABLE ' + table_name + ' ADD ' + tmp_field_name + ' ' + tmp_field_type)
cr.execute('UPDATE ' + table_name + ' SET ' + tmp_field_name + '=' + field_name)
def remove_temp_field(cr, table_name, field_name):
tmp_field_name = TMP_PREFIX + field_name
cr.execute('ALTER TABLE ' + table_name + ' DROP COLUMN ' + tmp_field_name)
def temp_field_values(cr, table_name, id, field_names):
tmp_field_names = [TMP_PREFIX + f for f in field_names]
if not tmp_field_names:
return {}
cr.execute('SELECT ' + ', '.join(tmp_field_names) + ' FROM ' + table_name + ' WHERE id=' + str(id))
values = cr.dictfetchone()
if not values:
return {}
def _remove_tmp_prefix(key):
if key.startswith(TMP_PREFIX):
return key[len(TMP_PREFIX):]
return key
return {_remove_tmp_prefix(k): v for k, v in values.items()}
"""
XMLIDs
"""
def remove_xmlid(cr, xmlid):
module, name = xmlid.split('.')
cr.execute('DELETE FROM ir_model_data WHERE module=%s and name=%s;', (module, name))
def rename_xmlid(cr, from_xmlid, to_xmlid):
from_module, from_name = from_xmlid.split('.')
to_module, to_name = to_xmlid.split('.')
cr.execute('UPDATE ir_model_data SET module=%s, name=%s WHERE module=%s and name=%s;', (to_module, to_name, from_module, from_name))

View File

@@ -1 +1,6 @@
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 hr_salary_rule
from . import us_payroll_config

View File

@@ -0,0 +1 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.

View File

@@ -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 + DED_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('DED_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.DED_FUTA_EXEMPT
if remaining < 0.0:
result = 0.0
elif remaining < wage:
result = remaining
else:
result = wage
return result, result_rate

View File

@@ -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 + DED_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('DED_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.DED_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 + DED_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('DED_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.DED_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('DED_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.DED_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 + DED_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.DED_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)

View File

@@ -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]

View File

@@ -0,0 +1,235 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
from odoo import api, fields, models
from odoo.tools.safe_eval import safe_eval
from odoo.exceptions import UserError
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
from .state.general import general_state_unemployment, \
general_state_income_withholding, \
is_us_state
from .state.ga_georgia import ga_georgia_state_income_withholding
from .state.ms_mississippi import ms_mississippi_state_income_withholding
from .state.mt_montana import mt_montana_state_income_withholding
from .state.oh_ohio import oh_ohio_state_income_withholding
from .state.va_virginia import va_virginia_state_income_withholding
from .state.wa_washington import wa_washington_fml_er, \
wa_washington_fml_ee
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,
'general_state_unemployment': general_state_unemployment,
'general_state_income_withholding': general_state_income_withholding,
'is_us_state': is_us_state,
'ga_georgia_state_income_withholding': ga_georgia_state_income_withholding,
'ms_mississippi_state_income_withholding': ms_mississippi_state_income_withholding,
'mt_montana_state_income_withholding': mt_montana_state_income_withholding,
'oh_ohio_state_income_withholding': oh_ohio_state_income_withholding,
'va_virginia_state_income_withholding': va_virginia_state_income_withholding,
'wa_washington_fml_er': wa_washington_fml_er,
'wa_washington_fml_ee': wa_washington_fml_ee,
}
def get_year(self):
# Helper method to get the year (normalized between Odoo Versions)
return int(self.date_to[:4])
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())

View File

@@ -0,0 +1,35 @@
from odoo import api, fields, models, _
from odoo.tools.safe_eval import safe_eval
from odoo.exceptions import UserError
class HRSalaryRule(models.Model):
_inherit = 'hr.salary.rule'
@api.multi
def _compute_rule(self, localdict):
"""
:param localdict: dictionary containing the environement in which to compute the rule
:return: returns a tuple build as the base/amount computed, the quantity and the rate
:rtype: (float, float, float)
"""
self.ensure_one()
if self.amount_select == 'fix':
try:
return self.amount_fix, float(safe_eval(self.quantity, localdict)), 100.0
except:
raise UserError(_('Wrong quantity defined for salary rule %s (%s).') % (self.name, self.code))
elif self.amount_select == 'percentage':
try:
return (float(safe_eval(self.amount_percentage_base, localdict)),
float(safe_eval(self.quantity, localdict)),
self.amount_percentage)
except:
raise UserError(_('Wrong percentage base or quantity defined for salary rule %s (%s).') % (self.name, self.code))
else:
try:
safe_eval(self.amount_python_compute, localdict, mode='exec', nocopy=True)
# Hibou Fix for setting 0.0 for result rate or result qty
return float(localdict['result']), localdict.get('result_qty', 1.0), localdict.get('result_rate', 100.0)
except:
raise UserError(_('Wrong python code defined for salary rule %s (%s).') % (self.name, self.code))

View File

@@ -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')

View File

@@ -0,0 +1 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.

View File

@@ -0,0 +1,52 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
from .general import _state_applies
def ga_georgia_state_income_withholding(payslip, categories, worked_days, inputs):
"""
Returns SIT eligible wage and rate.
WAGE = GROSS + DED_FIT_EXEMPT
:return: result, result_rate (wage, percent)
"""
state_code = 'GA'
if not _state_applies(payslip, state_code):
return 0.0, 0.0
ga_filing_status = payslip.dict.contract_id.us_payroll_config_value('ga_g4_sit_filing_status')
if not ga_filing_status or ga_filing_status == 'exempt':
return 0.0, 0.0
# Determine Wage
wage = categories.GROSS + categories.DED_FIT_EXEMPT
schedule_pay = payslip.dict.contract_id.schedule_pay
additional = payslip.dict.contract_id.us_payroll_config_value('state_income_tax_additional_withholding')
dependent_allowances = payslip.dict.contract_id.us_payroll_config_value('ga_g4_sit_dependent_allowances')
additional_allowances = payslip.dict.contract_id.us_payroll_config_value('ga_g4_sit_additional_allowances')
dependent_allowance_rate = payslip.dict.rule_parameter('us_ga_sit_dependent_allowance_rate').get(schedule_pay)
personal_allowance = payslip.dict.rule_parameter('us_ga_sit_personal_allowance').get(ga_filing_status, {}).get(schedule_pay)
deduction = payslip.dict.rule_parameter('us_ga_sit_deduction').get(ga_filing_status, {}).get(schedule_pay)
withholding_rate = payslip.dict.rule_parameter('us_ga_sit_rate').get(ga_filing_status, {}).get(schedule_pay)
if not all((dependent_allowance_rate, personal_allowance, deduction, withholding_rate)) or wage == 0.0:
return 0.0, 0.0
if wage == 0.0:
return 0.0, 0.0
after_standard_deduction = wage - deduction
allowances = dependent_allowances + additional_allowances
working_wages = after_standard_deduction - (personal_allowance + (allowances * dependent_allowance_rate))
withholding = 0.0
if working_wages > 0.0:
prior_row_base = 0.0
for row in withholding_rate:
wage_base, base, rate = row
wage_base = float(wage_base)
if working_wages < wage_base:
withholding = base + ((working_wages - prior_row_base) * rate / 100.0)
break
prior_row_base = wage_base
withholding += additional
return wage, -((withholding / wage) * 100.0)

View File

@@ -0,0 +1,127 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
from odoo.exceptions import UserError
# import logging
# _logger = logging.getLogger(__name__)
def _state_applies(payslip, state_code):
return state_code == payslip.dict.contract_id.us_payroll_config_value('state_code')
# Export for eval context
is_us_state = _state_applies
def _general_rate(payslip, wage, ytd_wage, wage_base=None, wage_start=None, rate=None):
"""
Function parameters:
wage_base, wage_start, rate can either be strings (rule_parameters) or floats
:return: result, result_rate(wage, percent)
"""
# Resolve parameters. On exception, return (probably missing a year, would rather not have exception)
if wage_base and isinstance(wage_base, str):
try:
wage_base = payslip.dict.rule_parameter(wage_base)
except (KeyError, UserError):
return 0.0, 0.0
if wage_start and isinstance(wage_start, str):
try:
wage_start = payslip.dict.rule_parameter(wage_start)
except (KeyError, UserError):
return 0.0, 0.0
if rate and isinstance(rate, str):
try:
rate = payslip.dict.rule_parameter(rate)
except (KeyError, UserError):
return 0.0, 0.0
if not rate:
return 0.0, 0.0
else:
# Rate assumed positive percentage!
rate = -rate
if wage_base:
remaining = wage_base - ytd_wage
if remaining < 0.0:
result = 0.0
elif remaining < wage:
result = remaining
else:
result = wage
# _logger.warn(' wage_base method result: ' + str(result) + ' rate: ' + str(rate))
return result, rate
if wage_start:
if ytd_wage >= wage_start:
# _logger.warn(' wage_start 1 method result: ' + str(wage) + ' rate: ' + str(rate))
return wage, rate
if ytd_wage + wage <= wage_start:
# _logger.warn(' wage_start 2 method result: ' + str(0.0) + ' rate: ' + str(0.0))
return 0.0, 0.0
# _logger.warn(' wage_start 3 method result: ' + str((wage - (wage_start - ytd_wage))) + ' rate: ' + str(rate))
return (wage - (wage_start - ytd_wage)), rate
# If the wage doesn't have a start or a base
# _logger.warn(' basic result: ' + str(wage) + ' rate: ' + str(rate))
return wage, rate
def general_state_unemployment(payslip, categories, worked_days, inputs, wage_base=None, wage_start=None, rate=None, state_code=None):
"""
Returns SUTA eligible wage and rate.
WAGE = GROSS + DED_FUTA_EXEMPT
The contract's `futa_type` determines if SUTA should be collected.
:return: result, result_rate(wage, percent)
"""
if not _state_applies(payslip, state_code):
return 0.0, 0.0
# Determine Eligible.
if payslip.dict.contract_id.futa_type in (payslip.dict.contract_id.FUTA_TYPE_EXEMPT, payslip.dict.contract_id.FUTA_TYPE_BASIC):
return 0.0, 0.0
# 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('DED_FUTA_EXEMPT', str(year) + '-01-01', str(year + 1) + '-01-01')
ytd_wage += payslip.dict.contract_id.external_wages
wage = categories.GROSS + categories.DED_FUTA_EXEMPT
return _general_rate(payslip, wage, ytd_wage, wage_base=wage_base, wage_start=wage_start, rate=rate)
def general_state_income_withholding(payslip, categories, worked_days, inputs, wage_base=None, wage_start=None, rate=None, state_code=None):
"""
Returns SIT eligible wage and rate.
WAGE = GROSS + DED_FIT_EXEMPT
:return: result, result_rate (wage, percent)
"""
if not _state_applies(payslip, state_code):
return 0.0, 0.0
if payslip.dict.contract_id.us_payroll_config_value('state_income_tax_exempt'):
return 0.0, 0.0
# 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('DED_FIT_EXEMPT', str(year) + '-01-01', str(year + 1) + '-01-01')
ytd_wage += payslip.dict.contract_id.external_wages
wage = categories.GROSS + categories.DED_FIT_EXEMPT
result, result_rate = _general_rate(payslip, wage, ytd_wage, wage_base=wage_base, wage_start=wage_start, rate=rate)
additional = payslip.dict.contract_id.us_payroll_config_value('state_income_tax_additional_withholding')
if additional:
tax = result * (result_rate / 100.0)
tax -= additional # assumed result_rate is negative and that the 'additional' should increase it.
return result, ((tax / result) * 100.0)
return result, result_rate

View File

@@ -0,0 +1,46 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
from .general import _state_applies
def ms_mississippi_state_income_withholding(payslip, categories, worked_days, inputs):
"""
Returns SIT eligible wage and rate.
WAGE = GROSS + DED_FIT_EXEMPT
:return: result, result_rate (wage, percent)
"""
state_code = 'MS'
if not _state_applies(payslip, state_code):
return 0.0, 0.0
filing_status = payslip.dict.contract_id.us_payroll_config_value('ms_89_350_sit_filing_status')
if not filing_status:
return 0.0, 0.0
# Determine Wage
wage = categories.GROSS + categories.DED_FIT_EXEMPT
if wage == 0.0:
return 0.0, 0.0
pay_periods = payslip.dict.get_pay_periods_in_year()
additional = payslip.dict.contract_id.us_payroll_config_value('state_income_tax_additional_withholding')
exemptions = payslip.dict.contract_id.us_payroll_config_value('ms_89_350_sit_exemption_value')
standard_deduction = payslip.dict.rule_parameter('us_ms_sit_deduction').get(filing_status)
withholding_rate = payslip.dict.rule_parameter('us_ms_sit_rate')
wage_annual = wage * pay_periods
taxable_income = wage_annual - (exemptions + standard_deduction)
if taxable_income <= 0.01:
return wage, 0.0
withholding = 0.0
for row in withholding_rate:
wage_base, base, rate = row
if taxable_income >= wage_base:
withholding = base + ((taxable_income - wage_base) * rate)
break
withholding /= pay_periods
withholding = round(withholding)
withholding += round(additional)
return wage, -((withholding / wage) * 100.0)

View File

@@ -0,0 +1,42 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
from .general import _state_applies
def mt_montana_state_income_withholding(payslip, categories, worked_days, inputs):
"""
Returns SIT eligible wage and rate.
WAGE = GROSS + DED_FIT_EXEMPT
:return: result, result_rate (wage, percent)
"""
state_code = 'MT'
if not _state_applies(payslip, state_code):
return 0.0, 0.0
if payslip.dict.contract_id.us_payroll_config_value('mt_mw4_sit_exempt'):
return 0.0, 0.0
# Determine Wage
wage = categories.GROSS + categories.DED_FIT_EXEMPT
schedule_pay = payslip.dict.contract_id.schedule_pay
additional = payslip.dict.contract_id.us_payroll_config_value('state_income_tax_additional_withholding')
exemptions = payslip.dict.contract_id.us_payroll_config_value('mt_mw4_sit_exemptions')
exemption_rate = payslip.dict.rule_parameter('us_mt_sit_exemption_rate').get(schedule_pay)
withholding_rate = payslip.dict.rule_parameter('us_mt_sit_rate').get(schedule_pay)
if not exemption_rate or not withholding_rate or wage == 0.0:
return 0.0, 0.0
adjusted_wage = wage - (exemption_rate * (exemptions or 0))
withholding = 0.0
if adjusted_wage > 0.0:
prior_wage_cap = 0.0
for row in withholding_rate:
wage_cap, base, rate = row
wage_cap = float(wage_cap) # e.g. 'inf'
if adjusted_wage < wage_cap:
withholding = round(base + ((rate / 100.0) * (adjusted_wage - prior_wage_cap)))
break
prior_wage_cap = wage_cap
withholding += additional
return wage, -((withholding / wage) * 100.0)

View File

@@ -0,0 +1,46 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
from .general import _state_applies
def oh_ohio_state_income_withholding(payslip, categories, worked_days, inputs):
"""
Returns SIT eligible wage and rate.
WAGE = GROSS + DED_FIT_EXEMPT
:return: result, result_rate (wage, percent)
"""
state_code = 'OH'
if not _state_applies(payslip, state_code):
return 0.0, 0.0
if payslip.dict.contract_id.us_payroll_config_value('state_income_tax_exempt'):
return 0.0, 0.0
# Determine Wage
wage = categories.GROSS + categories.DED_FIT_EXEMPT
pay_periods = payslip.dict.get_pay_periods_in_year()
additional = payslip.dict.contract_id.us_payroll_config_value('state_income_tax_additional_withholding')
exemptions = payslip.dict.contract_id.us_payroll_config_value('oh_it4_sit_exemptions')
exemption_rate = payslip.dict.rule_parameter('us_oh_sit_exemption_rate')
withholding_rate = payslip.dict.rule_parameter('us_oh_sit_rate')
multiplier_rate = payslip.dict.rule_parameter('us_oh_sit_multiplier')
if wage == 0.0:
return 0.0, 0.0
taxable_wage = (wage * pay_periods) - (exemption_rate * (exemptions or 0))
withholding = 0.0
if taxable_wage > 0.0:
prior_wage_cap = 0.0
for row in withholding_rate:
wage_cap, base, rate = row
wage_cap = float(wage_cap) # e.g. 'inf'
if taxable_wage < wage_cap:
withholding = base + (rate * (taxable_wage - prior_wage_cap))
break
prior_wage_cap = wage_cap
# Normalize to pay periods
withholding /= pay_periods
withholding *= multiplier_rate
withholding += additional
return wage, -((withholding / wage) * 100.0)

View File

@@ -0,0 +1,43 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
from .general import _state_applies
def va_virginia_state_income_withholding(payslip, categories, worked_days, inputs):
"""
Returns SIT eligible wage and rate.
WAGE = GROSS + DED_FIT_EXEMPT
:return: result, result_rate (wage, percent)
"""
state_code = 'VA'
if not _state_applies(payslip, state_code):
return 0.0, 0.0
if payslip.dict.contract_id.us_payroll_config_value('state_income_tax_exempt'):
return 0.0, 0.0
# Determine Wage
wage = categories.GROSS + categories.DED_FIT_EXEMPT
pay_periods = payslip.dict.get_pay_periods_in_year()
additional = payslip.dict.contract_id.us_payroll_config_value('state_income_tax_additional_withholding')
personal_exemptions = payslip.dict.contract_id.us_payroll_config_value('va_va4_sit_exemptions')
other_exemptions = payslip.dict.contract_id.us_payroll_config_value('va_va4_sit_other_exemptions')
personal_exemption_rate = payslip.dict.rule_parameter('us_va_sit_exemption_rate')
other_exemption_rate = payslip.dict.rule_parameter('us_va_sit_other_exemption_rate')
deduction = payslip.dict.rule_parameter('us_va_sit_deduction')
withholding_rate = payslip.dict.rule_parameter('us_va_sit_rate')
if wage == 0.0:
return 0.0, 0.0
taxable_wage = (wage * pay_periods) - (deduction + (personal_exemptions * personal_exemption_rate) + (other_exemptions * other_exemption_rate))
withholding = 0.0
if taxable_wage > 0.0:
for row in withholding_rate:
if taxable_wage > row[0]:
selected_row = row
wage_min, base, rate = selected_row
withholding = base + ((taxable_wage - wage_min) * rate / 100.0)
withholding /= pay_periods
withholding += additional
return wage, -((withholding / wage) * 100.0)

View File

@@ -0,0 +1,27 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
from .general import _state_applies, _general_rate
def _wa_washington_fml(payslip, categories, worked_days, inputs, inner_rate=None):
if not inner_rate:
return 0.0, 0.0
if not _state_applies(payslip, 'WA'):
return 0.0, 0.0
wage = categories.GROSS
year = payslip.dict.get_year()
ytd_wage = payslip.sum_category('GROSS', str(year) + '-01-01', str(year + 1) + '-01-01')
ytd_wage += payslip.contract_id.external_wages
rate = payslip.dict.rule_parameter('us_wa_fml_rate')
rate *= payslip.dict.rule_parameter(inner_rate) / 100.0
return _general_rate(payslip, wage, ytd_wage, wage_base='us_wa_fml_wage_base', rate=rate)
def wa_washington_fml_er(payslip, categories, worked_days, inputs):
return _wa_washington_fml(payslip, categories, worked_days, inputs, inner_rate='us_wa_fml_rate_er')
def wa_washington_fml_ee(payslip, categories, worked_days, inputs):
return _wa_washington_fml(payslip, categories, worked_days, inputs, inner_rate='us_wa_fml_rate_ee')

View File

@@ -0,0 +1,96 @@
# 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")
state_code = fields.Char(related='state_id.code')
state_income_tax_exempt = fields.Boolean(string='State Income Tax Exempt')
state_income_tax_additional_withholding = fields.Float(string='State Income Tax Additional Withholding')
workers_comp_ee_code = fields.Char(string='Workers\' Comp Code (Employee Withholding)',
help='Code for a Payroll Rate, used by some states or your own rules.')
workers_comp_er_code = fields.Char(string='Workers\' Comp Code (Employer Withholding)',
help='Code for a Payroll Rate, used by some states or your own rules.')
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)')
ga_g4_sit_filing_status = fields.Selection([
('exempt', 'Exempt'),
('single', 'Single'),
('married filing joint, both spouses working', 'Married Filing Joint, both spouses working'),
('married filing joint, one spouse working', 'Married Filing Joint, one spouse working'),
('married filing separate', 'Married Filing Separate'),
('head of household', 'Head of Household'),
], string='Georgia G-4 Filing Status', help='G-4 3.')
ga_g4_sit_dependent_allowances = fields.Integer(string='Georgia G-4 Dependent Allowances',
help='G-4 4.')
ga_g4_sit_additional_allowances = fields.Integer(string='Georgia G-4 Additional Allowances',
help='G-4 5.')
ms_89_350_sit_filing_status = fields.Selection([
('', 'Exempt'),
('single', 'Single'),
('married', 'Married (spouse NOT employed)'),
('married_dual', 'Married (spouse IS employed)'),
('head_of_household', 'Head of Household'),
], string='Mississippi 89-350 Filing Status', help='89-350 1. 2. 3. 8.')
ms_89_350_sit_exemption_value = fields.Float(string='Mississippi 89-350 Exemption Total',
help='89-350 Box 6 (including filing status amounts)')
mt_mw4_sit_exemptions = fields.Integer(string='Montana MW-4 Exemptions',
help='MW-4 Box G')
# Don't use the main state_income_tax_exempt because of special meaning and reporting
# Use additional withholding but name it on the form 'MW-4 Box H'
mt_mw4_sit_exempt = fields.Selection([
('', 'Not Exempt'),
('tribe', 'Registered Tribe'),
('reserve', 'Reserve or National Guard'),
('north_dakota', 'North Dakota'),
('montana_for_marriage', 'Montana for Marriage'),
], string='Montana MW-4 Exempt from Withholding', help='MW-4 Section 2')
# Ohio will use generic SIT exempt and additional fields
oh_it4_sit_exemptions = fields.Integer(string='Ohio IT-4 Exemptions',
help='Line 4')
va_va4_sit_exemptions = fields.Integer(string='Virginia VA-4(P) Personal Exemptions',
help='VA-4(P) 1(a)')
va_va4_sit_other_exemptions = fields.Integer(string='Virginia VA-4(P) Age & Blindness Exemptions',
help='VA-4(P) 1(b)')

View File

@@ -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
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

View File

@@ -1,5 +1,32 @@
# -*- coding: utf-8 -*-
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
from . import test_us_payslip
from . import test_us_payslip_2018
from . import common
from . import test_us_payslip_2019
from . import test_us_payslip_2020
from . import test_us_fl_florida_payslip_2019
from . import test_us_fl_florida_payslip_2020
from . import test_us_ga_georgia_payslip_2019
from . import test_us_ga_georgia_payslip_2020
from . import test_us_ms_mississippi_payslip_2019
from . import test_us_ms_mississippi_payslip_2020
from . import test_us_mt_montana_payslip_2019
from . import test_us_mt_montana_payslip_2020
from . import test_us_oh_ohio_payslip_2019
from . import test_us_oh_ohio_payslip_2020
from . import test_us_pa_pennsylvania_payslip_2019
from . import test_us_pa_pennsylvania_payslip_2020
from . import test_us_tx_texas_payslip_2019
from . import test_us_tx_texas_payslip_2020
from . import test_us_va_virginia_payslip_2019
from . import test_us_va_virginia_payslip_2020
from . import test_us_wa_washington_payslip_2019
from . import test_us_wa_washington_payslip_2020

View File

@@ -0,0 +1,242 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
from logging import getLogger
from sys import float_info as sys_float_info
from collections import defaultdict
from datetime import timedelta
from odoo.tests import common
from odoo.tools.float_utils import float_round as odoo_float_round
from odoo.addons.l10n_us_hr_payroll.models.hr_contract import USHRContract
def process_payslip(payslip):
try:
payslip.action_payslip_done()
except AttributeError:
# v9
payslip.process_sheet()
class TestUsPayslip(common.TransactionCase):
debug = False
_logger = getLogger(__name__)
float_info = sys_float_info
def float_round(self, value, digits):
return odoo_float_round(value, digits)
_payroll_digits = -1
@property
def payroll_digits(self):
if self._payroll_digits == -1:
self._payroll_digits = self.env['decimal.precision'].precision_get('Payroll')
return self._payroll_digits
def _log(self, message):
if self.debug:
self._logger.warn(message)
def _createEmployee(self):
return self.env['hr.employee'].create({
'birthday': '1985-03-14',
'country_id': self.ref('base.us'),
'department_id': self.ref('hr.dep_rd'),
'gender': 'male',
'name': 'Jared'
})
def _createContract(self, employee, **kwargs):
if not 'schedule_pay' in kwargs:
kwargs['schedule_pay'] = 'monthly'
schedule_pay = kwargs['schedule_pay']
config_model = self.env['hr.contract.us_payroll_config']
contract_model = self.env['hr.contract']
config_values = {
'name': 'Test Config Values',
'employee_id': employee.id,
}
contract_values = {
'name': 'Test Contract',
'employee_id': employee.id,
}
# Backwards compatability with 'futa_type'
if 'futa_type' in kwargs:
kwargs['fed_940_type'] = kwargs['futa_type']
for key, val in kwargs.items():
# Assume any Odoo object is in a Many2one
if hasattr(val, 'id'):
val = val.id
found = False
if hasattr(contract_model, key):
contract_values[key] = val
found = True
if hasattr(config_model, key):
config_values[key] = val
found = True
if not found:
self._logger.warn('cannot locate attribute names "%s" on contract or payroll config' % (key, ))
# US Payroll Config Defaults Should be set on the Model
config = config_model.create(config_values)
contract_values['us_payroll_config_id'] = config.id
# Some Basic Defaults
if not contract_values.get('state'):
contract_values['state'] = 'open' # Running
if not contract_values.get('struct_id'):
contract_values['struct_id'] = self.ref('l10n_us_hr_payroll.structure_type_employee')
if not contract_values.get('date_start'):
contract_values['date_start'] = '2016-01-01'
if not contract_values.get('date_end'):
contract_values['date_end'] = '2030-12-31'
if not contract_values.get('resource_calendar_id'):
contract_values['resource_calendar_id'] = self.ref('resource.resource_calendar_std')
# Compatibility with earlier Odoo versions
if not contract_values.get('journal_id') and hasattr(contract_model, 'journal_id'):
try:
contract_values['journal_id'] = self.env['account.journal'].search([('type', '=', 'general')], limit=1).id
except KeyError:
# Accounting not installed
pass
contract = contract_model.create(contract_values)
return contract
def _createPayslip(self, employee, date_from, date_to):
slip = self.env['hr.payslip'].create({
'name': 'Test %s From: %s To: %s' % (employee.name, date_from, date_to),
'employee_id': employee.id,
'date_from': date_from,
'date_to': date_to
})
if hasattr(slip, '_onchange_employee'):
slip._onchange_employee()
if hasattr(slip, 'onchange_employee'):
# Odoo 12
slip.onchange_employee()
if self.debug:
self._logger.warn(slip.read())
self._logger.warn(slip.contract_id.read())
return slip
def _getCategories(self, payslip):
categories = defaultdict(float)
for line in payslip.line_ids:
self._log(' line code: ' + str(line.code) +
' category code: ' + line.category_id.code +
' total: ' + str(line.total) +
' rate: ' + str(line.rate) +
' amount: ' + str(line.amount))
category_id = line.category_id
category_code = line.category_id.code
while category_code:
categories[category_code] += line.total
category_id = category_id.parent_id
category_code = category_id.code
return categories
def _getRules(self, payslip):
rules = defaultdict(float)
for line in payslip.line_ids:
rules[line.code] += line.total
return rules
def assertPayrollEqual(self, first, second):
self.assertAlmostEqual(first, second, self.payroll_digits)
def assertPayrollAlmostEqual(self, first, second):
self.assertAlmostEqual(first, second, self.payroll_digits-1)
def test_semi_monthly(self):
salary = 80000.0
employee = self._createEmployee()
# so the schedule_pay is now on the Structure...
contract = self._createContract(employee, wage=salary, schedule_pay='semi-monthly')
payslip = self._createPayslip(employee, '2019-01-01', '2019-01-14')
payslip.compute_sheet()
def get_us_state(self, code, cache={}):
country_key = 'US_COUNTRY'
if code in cache:
return cache[code]
if country_key not in cache:
cache[country_key] = self.env.ref('base.us')
us_country = cache[country_key]
us_state = self.env['res.country.state'].search([
('country_id', '=', us_country.id),
('code', '=', code),
], limit=1)
cache[code] = us_state
return us_state
def _test_suta(self, category, state_code, rate, date, wage_base=None, **extra_contract):
if wage_base:
# Slightly larger than 1/2 the wage_base
wage = round(wage_base / 2.0) + 100.0
self.assertTrue((2 * wage) > wage_base, 'Granularity of wage_base too low.')
else:
wage = 1000.0
employee = self._createEmployee()
contract = self._createContract(employee,
wage=wage,
state_id=self.get_us_state(state_code),
**extra_contract)
rate = -rate / 100.0 # Assumed passed as percent positive
# Tests
payslip = self._createPayslip(employee, date, date + timedelta(days=30))
# Test exemptions
contract.us_payroll_config_id.fed_940_type = USHRContract.FUTA_TYPE_EXEMPT
payslip.compute_sheet()
cats = self._getCategories(payslip)
self.assertPayrollEqual(cats.get(category, 0.0), 0.0)
contract.us_payroll_config_id.fed_940_type = USHRContract.FUTA_TYPE_BASIC
payslip.compute_sheet()
cats = self._getCategories(payslip)
self.assertPayrollEqual(cats.get(category, 0.0), 0.0)
# Test Normal
contract.us_payroll_config_id.fed_940_type = USHRContract.FUTA_TYPE_NORMAL
payslip.compute_sheet()
cats = self._getCategories(payslip)
self.assertPayrollEqual(cats.get(category, 0.0), wage * rate)
process_payslip(payslip)
# Second Payslip
payslip = self._createPayslip(employee, date + timedelta(days=31), date + timedelta(days=60))
payslip.compute_sheet()
cats = self._getCategories(payslip)
if wage_base:
remaining_unemp_wages = wage_base - wage
self.assertTrue((remaining_unemp_wages * rate) <= 0.01) # less than 0.01 because rate is negative
self.assertPayrollEqual(cats.get(category, 0.0), remaining_unemp_wages * rate)
# As if they were paid once already, so the first "two payslips" would remove all of the tax obligation
# 1 wage - Payslip (confirmed)
# 1 wage - external_wages
# 1 wage - current Payslip
contract.external_wages = wage
payslip.compute_sheet()
cats = self._getCategories(payslip)
self.assertPayrollEqual(cats.get(category, 0.0), 0.0)
else:
self.assertPayrollEqual(cats.get(category, 0.0), wage * rate)
def _test_er_suta(self, state_code, rate, date, wage_base=None, **extra_contract):
self._test_suta('ER_US_SUTA', state_code, rate, date, wage_base=wage_base, **extra_contract)
def _test_ee_suta(self, state_code, rate, date, wage_base=None, **extra_contract):
self._test_suta('EE_US_SUTA', state_code, rate, date, wage_base=wage_base, **extra_contract)

View File

@@ -0,0 +1,84 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
from .common import TestUsPayslip, process_payslip
from odoo.addons.l10n_us_hr_payroll.models.hr_contract import USHRContract
class TestUsFlPayslip(TestUsPayslip):
###
# 2019 Taxes and Rates
###
FL_UNEMP_MAX_WAGE = 7000.0
FL_UNEMP = -2.7 / 100.0
def test_2019_taxes(self):
salary = 5000.0
employee = self._createEmployee()
contract = self._createContract(employee,
wage=salary,
state_id=self.get_us_state('FL'))
self._log('2019 Florida tax first payslip:')
payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
payslip.compute_sheet()
cats = self._getCategories(payslip)
self.assertPayrollEqual(cats['ER_US_SUTA'], salary * self.FL_UNEMP)
process_payslip(payslip)
# Make a new payslip, this one will have maximums
remaining_fl_unemp_wages = self.FL_UNEMP_MAX_WAGE - salary if (self.FL_UNEMP_MAX_WAGE - 2*salary < salary) \
else salary
self._log('2019 Florida tax second payslip:')
payslip = self._createPayslip(employee, '2019-02-01', '2019-02-28')
payslip.compute_sheet()
cats = self._getCategories(payslip)
self.assertPayrollEqual(cats['ER_US_SUTA'], remaining_fl_unemp_wages * self.FL_UNEMP)
def test_2019_taxes_with_external(self):
salary = 5000.0
external_wages = 6000.0
employee = self._createEmployee()
contract = self._createContract(employee,
wage=salary,
external_wages=external_wages,
state_id=self.get_us_state('FL'))
self._log('2019 Forida_external tax first payslip:')
payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
payslip.compute_sheet()
cats = self._getCategories(payslip)
self.assertPayrollEqual(cats['ER_US_SUTA'], (self.FL_UNEMP_MAX_WAGE - external_wages) * self.FL_UNEMP)
def test_2019_taxes_with_state_exempt(self):
salary = 5000.0
external_wages = 6000.0
employee = self._createEmployee()
contract = self._createContract(employee,
wage=salary,
external_wages=external_wages,
futa_type=USHRContract.FUTA_TYPE_BASIC,
state_id=self.get_us_state('FL'))
self._log('2019 Forida_external tax first payslip:')
payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
payslip.compute_sheet()
cats = self._getCategories(payslip)
self.assertPayrollEqual(cats.get('ER_US_SUTA', 0.0), 0.0)

View File

@@ -0,0 +1,16 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
from datetime import date
from .common import TestUsPayslip
class TestUsFlPayslip(TestUsPayslip):
###
# 2020 Taxes and Rates
###
FL_UNEMP_MAX_WAGE = 7000.0
FL_UNEMP = 2.7
def test_2020_taxes(self):
# Only has state unemployment
self._test_er_suta('FL', self.FL_UNEMP, date(2020, 1, 1), wage_base=self.FL_UNEMP_MAX_WAGE)

View File

@@ -0,0 +1,135 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
from .common import TestUsPayslip, process_payslip
class TestUsGAPayslip(TestUsPayslip):
# TAXES AND RATES
GA_UNEMP_MAX_WAGE = 9500.00
GA_UNEMP = -(2.70 / 100.0)
def test_taxes_weekly_single_with_additional_wh(self):
salary = 15000.00
schedule_pay = 'weekly'
allowances = 1
filing_status = 'single'
additional_wh = 12.50
# Hand Calculated Amount to Test
# Step 1 - Subtract standard deduction from wages. Std Deduct for single weekly is 88.50
# step1 = 15000.00 - 88.50 = 14911.5
# Step 2 - Subtract personal allowance from step1. Allowance for single weekly is 51.92
# step2 = step1 - 51.92 = 14859.58
# Step 3 - Subtract amount for dependents. Weekly dependent allowance is 57.50
# step3 = 14859.58 - 57.50 = 14802.08
# Step 4 -Determine wh amount from tables
# step4 = 4.42 + ((5.75 / 100.00) * (14802.08 - 135.00))
# Add additional_wh
# wh = 847.7771 + 12.50 = 860.2771
wh = -860.28
employee = self._createEmployee()
contract = self._createContract(employee,
wage=salary,
state_id=self.get_us_state('GA'),
ga_g4_sit_dependent_allowances=allowances,
ga_g4_sit_additional_allowances=0,
ga_g4_sit_filing_status=filing_status,
state_income_tax_additional_withholding=additional_wh,
schedule_pay=schedule_pay)
self.assertEqual(contract.schedule_pay, 'weekly')
self._log('2019 Georgia tax first payslip weekly:')
payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
payslip.compute_sheet()
cats = self._getCategories(payslip)
self.assertPayrollEqual(cats['ER_US_SUTA'], self.GA_UNEMP_MAX_WAGE * self.GA_UNEMP)
self.assertPayrollEqual(cats['EE_US_SIT'], wh)
process_payslip(payslip)
remaining_GA_UNEMP_wages = 0.0 # We already reached max unemployment wages.
self._log('2019 Georgia tax second payslip weekly:')
payslip = self._createPayslip(employee, '2019-02-01', '2019-02-28')
payslip.compute_sheet()
cats = self._getCategories(payslip)
self.assertPayrollEqual(cats['ER_US_SUTA'], remaining_GA_UNEMP_wages * self.GA_UNEMP)
def test_taxes_monthly_head_of_household(self):
salary = 25000.00
schedule_pay = 'monthly'
allowances = 2
filing_status = 'head of household'
additional_wh = 15.00
# Hand Calculated Amount to Test
# Step 1 - Subtract standard deduction from wages. Std Deduct for head of household monthly is 383.50
# step1 = 25000.00 - 383.50 = 24616.5
# Step 2 - Subtract personal allowance from step1. Allowance for head of household monthly is 225.00
# step2 = 24616.5 - 225.00 = 24391.5
# Step 3 - Subtract amount for dependents. Weekly dependent allowance is 250.00
# step3 = 24391.5 - (2 * 250.00) = 23891.5
# Step 4 - Determine wh amount from tables
# step4 = 28.33 + ((5.75 / 100.00) * (23891.5 - 833.00)) = 1354.19375
# Add additional_wh
# wh = 1354.19375 + 15.00 = 1369.19375
wh = -1369.19
employee = self._createEmployee()
contract = self._createContract(employee,
wage=salary,
state_id=self.get_us_state('GA'),
ga_g4_sit_dependent_allowances=allowances,
ga_g4_sit_additional_allowances=0,
ga_g4_sit_filing_status=filing_status,
state_income_tax_additional_withholding=additional_wh,
schedule_pay=schedule_pay)
self.assertEqual(contract.schedule_pay, 'monthly')
self._log('2019 Georgia tax first payslip monthly:')
payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
payslip.compute_sheet()
cats = self._getCategories(payslip)
self.assertPayrollEqual(cats['ER_US_SUTA'], self.GA_UNEMP_MAX_WAGE * self.GA_UNEMP)
self.assertPayrollAlmostEqual(cats['EE_US_SIT'], wh)
process_payslip(payslip)
remaining_GA_UNEMP_wages = 0.0 # We already reached max unemployment wages.
self._log('2019 Georgia tax second payslip weekly:')
payslip = self._createPayslip(employee, '2019-02-01', '2019-02-28')
payslip.compute_sheet()
cats = self._getCategories(payslip)
self.assertPayrollEqual(cats['ER_US_SUTA'], remaining_GA_UNEMP_wages * self.GA_UNEMP)
def test_taxes_exempt(self):
salary = 25000.00
schedule_pay = 'monthly'
allowances = 2
filing_status = 'exempt'
additional_wh = 15.00
employee = self._createEmployee()
contract = self._createContract(employee,
wage=salary,
state_id=self.get_us_state('GA'),
ga_g4_sit_dependent_allowances=allowances,
ga_g4_sit_additional_allowances=0,
ga_g4_sit_filing_status=filing_status,
state_income_tax_additional_withholding=additional_wh,
schedule_pay=schedule_pay)
self._log('2019 Georgia tax first payslip exempt:')
payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
payslip.compute_sheet()
cats = self._getCategories(payslip)
self.assertPayrollEqual(cats.get('EE_US_SIT', 0), 0)

View File

@@ -0,0 +1,148 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
from datetime import date
from .common import TestUsPayslip, process_payslip
class TestUsGAPayslip(TestUsPayslip):
# TAXES AND RATES
GA_UNEMP_MAX_WAGE = 9500.00
GA_UNEMP = 2.70
def _run_test_sit(self,
wage=0.0,
schedule_pay='monthly',
filing_status='single',
dependent_credit=0.0,
other_income=0.0,
deductions=0.0,
additional_withholding=0.0,
is_nonresident_alien=False,
state_income_tax_exempt=False,
state_income_tax_additional_withholding=0.0,
ga_g4_sit_dependent_allowances=0,
ga_g4_sit_additional_allowances=0,
ga_g4_sit_filing_status=None,
expected=0.0,
):
employee = self._createEmployee()
contract = self._createContract(employee,
wage=wage,
schedule_pay=schedule_pay,
fed_941_fit_w4_is_nonresident_alien=is_nonresident_alien,
fed_941_fit_w4_filing_status=filing_status,
fed_941_fit_w4_multiple_jobs_higher=False,
fed_941_fit_w4_dependent_credit=dependent_credit,
fed_941_fit_w4_other_income=other_income,
fed_941_fit_w4_deductions=deductions,
fed_941_fit_w4_additional_withholding=additional_withholding,
state_income_tax_exempt=state_income_tax_exempt,
state_income_tax_additional_withholding=state_income_tax_additional_withholding,
ga_g4_sit_dependent_allowances=ga_g4_sit_dependent_allowances,
ga_g4_sit_additional_allowances=ga_g4_sit_additional_allowances,
ga_g4_sit_filing_status=ga_g4_sit_filing_status,
state_id=self.get_us_state('GA'),
)
payslip = self._createPayslip(employee, '2020-01-01', '2020-01-31')
payslip.compute_sheet()
cats = self._getCategories(payslip)
# Instead of PayrollEqual after initial first round of testing.
self.assertPayrollAlmostEqual(cats.get('EE_US_SIT', 0.0), -expected)
return payslip
def test_taxes_weekly_single_with_additional_wh(self):
self._test_er_suta('GA', self.GA_UNEMP, date(2020, 1, 1), wage_base=self.GA_UNEMP_MAX_WAGE)
salary = 15000.00
schedule_pay = 'weekly'
allowances = 1
filing_status = 'single'
additional_wh = 12.50
# Hand Calculated Amount to Test
# Step 1 - Subtract standard deduction from wages. Std Deduct for single weekly is 88.50
# step1 = 15000.00 - 88.50 = 14911.5
# Step 2 - Subtract personal allowance from step1. Allowance for single weekly is 51.92
# step2 = step1 - 51.92 = 14859.58
# Step 3 - Subtract amount for dependents. Weekly dependent allowance is 57.50
# step3 = 14859.58 - 57.50 = 14802.08
# Step 4 -Determine wh amount from tables
# step4 = 4.42 + ((5.75 / 100.00) * (14802.08 - 135.00))
# Add additional_wh
# wh = 847.7771 + 12.50 = 860.2771
wh = 860.28
self._run_test_sit(wage=salary,
schedule_pay=schedule_pay,
state_income_tax_additional_withholding=additional_wh,
ga_g4_sit_dependent_allowances=allowances,
ga_g4_sit_additional_allowances=0,
ga_g4_sit_filing_status=filing_status,
expected=wh,
)
def test_taxes_monthly_head_of_household(self):
salary = 25000.00
schedule_pay = 'monthly'
allowances = 2
filing_status = 'head of household'
additional_wh = 15.00
# Hand Calculated Amount to Test
# Step 1 - Subtract standard deduction from wages. Std Deduct for head of household monthly is 383.50
# step1 = 25000.00 - 383.50 = 24616.5
# Step 2 - Subtract personal allowance from step1. Allowance for head of household monthly is 225.00
# step2 = 24616.5 - 225.00 = 24391.5
# Step 3 - Subtract amount for dependents. Weekly dependent allowance is 250.00
# step3 = 24391.5 - (2 * 250.00) = 23891.5
# Step 4 - Determine wh amount from tables
# step4 = 28.33 + ((5.75 / 100.00) * (23891.5 - 833.00)) = 1354.19375
# Add additional_wh
# wh = 1354.19375 + 15.00 = 1369.19375
wh = 1369.19
self._run_test_sit(wage=salary,
schedule_pay=schedule_pay,
state_income_tax_additional_withholding=additional_wh,
ga_g4_sit_dependent_allowances=allowances,
ga_g4_sit_additional_allowances=0,
ga_g4_sit_filing_status=filing_status,
expected=wh,
)
# additional from external calculator
self._run_test_sit(wage=425.0,
schedule_pay='weekly',
state_income_tax_additional_withholding=0.0,
ga_g4_sit_dependent_allowances=1,
ga_g4_sit_additional_allowances=0,
ga_g4_sit_filing_status='married filing separate',
expected=11.45,
)
self._run_test_sit(wage=3000.0,
schedule_pay='quarterly',
state_income_tax_additional_withholding=0.0,
ga_g4_sit_dependent_allowances=1,
ga_g4_sit_additional_allowances=1,
ga_g4_sit_filing_status='single',
expected=0.0,
)
# TODO 'married filing joint, both spouses working' returns lower than calculator
# TODO 'married filing joint, one spouse working' returns lower than calculator
def test_taxes_exempt(self):
salary = 25000.00
schedule_pay = 'monthly'
allowances = 2
filing_status = 'exempt'
additional_wh = 15.00
self._run_test_sit(wage=salary,
schedule_pay=schedule_pay,
state_income_tax_additional_withholding=additional_wh,
ga_g4_sit_dependent_allowances=allowances,
ga_g4_sit_additional_allowances=0,
ga_g4_sit_filing_status=filing_status,
expected=0.0,
)

View File

@@ -0,0 +1,94 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
from .common import TestUsPayslip
class TestUsMsPayslip(TestUsPayslip):
# Calculations from https://www.dor.ms.gov/Documents/Computer%20Payroll%20Accounting%201-2-19.pdf
MS_UNEMP = -1.2 / 100.0
def test_2019_taxes_one(self):
salary = 1250.0
ms_89_350_exemption = 11000.0
employee = self._createEmployee()
contract = self._createContract(employee,
wage=salary,
state_id=self.get_us_state('MS'),
ms_89_350_sit_filing_status='head_of_household',
ms_89_350_sit_exemption_value=ms_89_350_exemption,
state_income_tax_additional_withholding=0.0,
schedule_pay='semi-monthly')
self._log('2019 Mississippi tax single first payslip:')
payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
payslip.compute_sheet()
cats = self._getCategories(payslip)
self.assertPayrollEqual(cats['ER_US_SUTA'], salary * self.MS_UNEMP)
STDED = 3400.0 # Head of Household
AGP = salary * 24 # Semi-Monthly
TI = AGP - (ms_89_350_exemption + STDED)
self.assertPayrollEqual(TI, 15600.0)
TAX = ((TI - 10000) * 0.05) + 290 # Over 10,000
self.assertPayrollEqual(TAX, 570.0)
ms_withhold = round(TAX / 24) # Semi-Monthly
self.assertPayrollEqual(cats['EE_US_SIT'], -ms_withhold)
def test_2019_taxes_one_exempt(self):
salary = 1250.0
ms_89_350_exemption = 11000.0
employee = self._createEmployee()
contract = self._createContract(employee,
wage=salary,
state_id=self.get_us_state('MS'),
ms_89_350_sit_filing_status='',
ms_89_350_sit_exemption_value=ms_89_350_exemption,
state_income_tax_additional_withholding=0.0,
schedule_pay='semi-monthly')
self._log('2019 Mississippi tax single first payslip:')
payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
payslip.compute_sheet()
cats = self._getCategories(payslip)
self.assertPayrollEqual(cats.get('EE_US_SIT', 0.0), 0.0)
def test_2019_taxes_additional(self):
salary = 1250.0
ms_89_350_exemption = 11000.0
additional = 40.0
employee = self._createEmployee()
contract = self._createContract(employee,
wage=salary,
state_id=self.get_us_state('MS'),
ms_89_350_sit_filing_status='head_of_household',
ms_89_350_sit_exemption_value=ms_89_350_exemption,
state_income_tax_additional_withholding=additional,
schedule_pay='semi-monthly')
self._log('2019 Mississippi tax single first payslip:')
payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
payslip.compute_sheet()
cats = self._getCategories(payslip)
self.assertPayrollEqual(cats['ER_US_SUTA'], salary * self.MS_UNEMP)
STDED = 3400.0 # Head of Household
AGP = salary * 24 # Semi-Monthly
TI = AGP - (ms_89_350_exemption + STDED)
self.assertPayrollEqual(TI, 15600.0)
TAX = ((TI - 10000) * 0.05) + 290 # Over 10,000
self.assertPayrollEqual(TAX, 570.0)
ms_withhold = round(TAX / 24) # Semi-Monthly
self.assertPayrollEqual(cats['EE_US_SIT'], -ms_withhold + -additional)

View File

@@ -0,0 +1,120 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
from datetime import date
from .common import TestUsPayslip
class TestUsMsPayslip(TestUsPayslip):
# Calculations from https://www.dor.ms.gov/Documents/Computer%20Payroll%20Accounting%201-2-19.pdf
MS_UNEMP = 1.2
MS_UNEMP_MAX_WAGE = 14000.0
def test_2020_taxes_one(self):
self._test_er_suta('MS', self.MS_UNEMP, date(2020, 1, 1), wage_base=self.MS_UNEMP_MAX_WAGE)
salary = 1250.0
ms_89_350_exemption = 11000.0
employee = self._createEmployee()
contract = self._createContract(employee,
wage=salary,
state_id=self.get_us_state('MS'),
ms_89_350_sit_filing_status='head_of_household',
ms_89_350_sit_exemption_value=ms_89_350_exemption,
state_income_tax_additional_withholding=0.0,
schedule_pay='semi-monthly')
self._log('2020 Mississippi tax single first payslip:')
payslip = self._createPayslip(employee, '2020-01-01', '2020-01-31')
payslip.compute_sheet()
cats = self._getCategories(payslip)
STDED = 3400.0 # Head of Household
AGP = salary * 24 # Semi-Monthly
TI = AGP - (ms_89_350_exemption + STDED)
self.assertPayrollEqual(TI, 15600.0)
TAX = ((TI - 10000) * 0.05) + 260 # Over 10,000
self.assertPayrollEqual(TAX, 540.0)
ms_withhold = round(TAX / 24) # Semi-Monthly
self.assertPayrollEqual(cats['EE_US_SIT'], -ms_withhold)
def test_2020_taxes_one_exempt(self):
salary = 1250.0
ms_89_350_exemption = 11000.0
employee = self._createEmployee()
contract = self._createContract(employee,
wage=salary,
state_id=self.get_us_state('MS'),
ms_89_350_sit_filing_status='',
ms_89_350_sit_exemption_value=ms_89_350_exemption,
state_income_tax_additional_withholding=0.0,
schedule_pay='semi-monthly')
self._log('2020 Mississippi tax single first payslip:')
payslip = self._createPayslip(employee, '2020-01-01', '2020-01-31')
payslip.compute_sheet()
cats = self._getCategories(payslip)
self.assertPayrollEqual(cats.get('EE_US_SIT', 0.0), 0.0)
def test_2020_taxes_additional(self):
salary = 1250.0
ms_89_350_exemption = 11000.0
additional = 40.0
employee = self._createEmployee()
contract = self._createContract(employee,
wage=salary,
state_id=self.get_us_state('MS'),
ms_89_350_sit_filing_status='single',
ms_89_350_sit_exemption_value=ms_89_350_exemption,
state_income_tax_additional_withholding=additional,
schedule_pay='monthly')
self._log('2020 Mississippi tax single first payslip:')
payslip = self._createPayslip(employee, '2020-01-01', '2020-01-31')
payslip.compute_sheet()
cats = self._getCategories(payslip)
STDED = 2300.0 # Single
AGP = salary * 12 # Monthly
TI = AGP - (ms_89_350_exemption + STDED)
self.assertPayrollEqual(TI, 1700.0)
TAX = ((TI - 3000) * 0.03)
self.assertPayrollEqual(TAX, -39.0)
ms_withhold = round(TAX / 12) # Monthly
self.assertTrue(ms_withhold <= 0.0)
self.assertPayrollEqual(cats['EE_US_SIT'], -40.0) # only additional
# Test with higher wage
salary = 1700.0
employee = self._createEmployee()
contract = self._createContract(employee,
wage=salary,
state_id=self.get_us_state('MS'),
ms_89_350_sit_filing_status='single',
ms_89_350_sit_exemption_value=ms_89_350_exemption,
state_income_tax_additional_withholding=additional,
schedule_pay='monthly')
payslip = self._createPayslip(employee, '2020-01-01', '2020-01-31')
payslip.compute_sheet()
cats = self._getCategories(payslip)
STDED = 2300.0 # Single
AGP = salary * 12 # Monthly
TI = AGP - (ms_89_350_exemption + STDED)
self.assertPayrollEqual(TI, 7100.0)
TAX = ((TI - 5000) * 0.04) + 60.0
self.assertPayrollEqual(TAX, 144.0)
ms_withhold = round(TAX / 12) # Monthly
self.assertPayrollEqual(ms_withhold, 12.0)
self.assertPayrollEqual(cats['EE_US_SIT'], -(ms_withhold + additional))

View File

@@ -0,0 +1,139 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
from .common import TestUsPayslip, process_payslip
class TestUsMtPayslip(TestUsPayslip):
# Calculations from https://app.mt.gov/myrevenue/Endpoint/DownloadPdf?yearId=705
MT_UNEMP = -1.18 / 100.0
MT_UNEMP_AFT = -0.13 / 100.0
def test_2019_taxes_one(self):
# Payroll Period Semi-Monthly example
salary = 550
mt_mw4_exemptions = 5
employee = self._createEmployee()
contract = self._createContract(employee,
wage=salary,
state_id=self.get_us_state('MT'),
mt_mw4_sit_exemptions=mt_mw4_exemptions,
schedule_pay='semi-monthly')
self._log('2019 Montana tax single first payslip:')
payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
payslip.compute_sheet()
cats = self._getCategories(payslip)
self.assertPayrollEqual(cats['ER_US_SUTA'], salary * (self.MT_UNEMP + self.MT_UNEMP_AFT)) # New non-combined...
mt_taxable_income = salary - (79.0 * mt_mw4_exemptions)
mt_withhold = round(0 + (0.018 * (mt_taxable_income - 0)))
self.assertPayrollEqual(mt_taxable_income, 155.0)
self.assertPayrollEqual(mt_withhold, 3.0)
self.assertPayrollEqual(cats['EE_US_SIT'], -mt_withhold)
def test_2019_taxes_two(self):
# Payroll Period Bi-Weekly example
salary = 2950
mt_mw4_exemptions = 2
employee = self._createEmployee()
contract = self._createContract(employee,
wage=salary,
state_id=self.get_us_state('MT'),
mt_mw4_sit_exemptions=mt_mw4_exemptions,
schedule_pay='bi-weekly')
self._log('2019 Montana tax single first payslip:')
payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
payslip.compute_sheet()
cats = self._getCategories(payslip)
self.assertPayrollEqual(cats['ER_US_SUTA'], round(salary * (self.MT_UNEMP + self.MT_UNEMP_AFT), 2))
# Note!!
# The example calculation uses A = 16 but the actual table describes this as A = 18
mt_taxable_income = salary - (73.0 * mt_mw4_exemptions)
mt_withhold = round(18 + (0.06 * (mt_taxable_income - 577)))
self.assertPayrollEqual(mt_taxable_income, 2804.0)
self.assertPayrollEqual(mt_withhold, 152.0)
self.assertPayrollEqual(cats['EE_US_SIT'], -mt_withhold)
def test_2019_taxes_three(self):
# Payroll Period Weekly example
salary = 135
mt_mw4_exemptions = 1
employee = self._createEmployee()
contract = self._createContract(employee,
wage=salary,
state_id=self.get_us_state('MT'),
mt_mw4_sit_exemptions=mt_mw4_exemptions,
schedule_pay='weekly')
self._log('2019 Montana tax single first payslip:')
payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
payslip.compute_sheet()
cats = self._getCategories(payslip)
self.assertPayrollEqual(cats['ER_US_SUTA'], round(salary * (self.MT_UNEMP + self.MT_UNEMP_AFT), 2))
mt_taxable_income = salary - (37.0 * mt_mw4_exemptions)
mt_withhold = round(0 + (0.018 * (mt_taxable_income - 0)))
self.assertPayrollEqual(mt_taxable_income, 98.0)
self.assertPayrollEqual(mt_withhold, 2.0)
self.assertPayrollEqual(cats['EE_US_SIT'], -mt_withhold)
def test_2019_taxes_three_exempt(self):
# Payroll Period Weekly example
salary = 135
mt_mw4_exemptions = 1
employee = self._createEmployee()
contract = self._createContract(employee,
wage=salary,
state_id=self.get_us_state('MT'),
mt_mw4_sit_exemptions=mt_mw4_exemptions,
mt_mw4_sit_exempt='reserve',
schedule_pay='weekly')
self._log('2019 Montana tax single first payslip:')
payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
payslip.compute_sheet()
cats = self._getCategories(payslip)
self.assertPayrollEqual(cats.get('EE_US_SIT', 0.0), 0.0)
def test_2019_taxes_three_additional(self):
# Payroll Period Weekly example
salary = 135
mt_mw4_exemptions = 1
mt_mw4_additional_withholding = 20.0
employee = self._createEmployee()
contract = self._createContract(employee,
wage=salary,
state_id=self.get_us_state('MT'),
mt_mw4_sit_exemptions=mt_mw4_exemptions,
state_income_tax_additional_withholding=mt_mw4_additional_withholding,
schedule_pay='weekly')
self._log('2019 Montana tax single first payslip:')
payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
payslip.compute_sheet()
cats = self._getCategories(payslip)
mt_taxable_income = salary - (37.0 * mt_mw4_exemptions)
mt_withhold = round(0 + (0.018 * (mt_taxable_income - 0)))
self.assertPayrollEqual(mt_taxable_income, 98.0)
self.assertPayrollEqual(mt_withhold, 2.0)
self.assertPayrollEqual(cats['EE_US_SIT'], -mt_withhold + -mt_mw4_additional_withholding)

View File

@@ -0,0 +1,17 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
from datetime import date
from .common import TestUsPayslip, process_payslip
class TestUsMtPayslip(TestUsPayslip):
# Calculations from https://app.mt.gov/myrevenue/Endpoint/DownloadPdf?yearId=705
MT_UNEMP_WAGE_MAX = 34100.0
MT_UNEMP = 1.18
MT_UNEMP_AFT = 0.13
def test_2020_taxes_one(self):
combined_rate = self.MT_UNEMP + self.MT_UNEMP_AFT # Combined for test as they both go to the same category and have the same cap
self._test_er_suta('MT', combined_rate, date(2020, 1, 1), wage_base=self.MT_UNEMP_WAGE_MAX)
# TODO Montana Incometax rates for 2020 when released

View File

@@ -0,0 +1,96 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
from .common import TestUsPayslip, process_payslip
from odoo.addons.l10n_us_hr_payroll.models.hr_contract import USHRContract
class TestUsOhPayslip(TestUsPayslip):
###
# Taxes and Rates
###
OH_UNEMP_MAX_WAGE = 9500.0
OH_UNEMP = -2.7 / 100.0
def test_2019_taxes(self):
salary = 5000.0
# For formula here
# http://www.tax.ohio.gov/Portals/0/employer_withholding/August2015Rates/WTH_OptionalComputerFormula_073015.pdf
tw = salary * 12 # = 60000
wd = ((tw - 40000) * 0.035 + 900) / 12 * 1.075
employee = self._createEmployee()
contract = self._createContract(employee,
wage=salary,
state_id=self.get_us_state('OH'),
)
self._log('2019 Ohio tax first payslip:')
payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
payslip.compute_sheet()
cats = self._getCategories(payslip)
self.assertPayrollEqual(cats['ER_US_SUTA'], salary * self.OH_UNEMP)
self.assertPayrollAlmostEqual(cats['EE_US_SIT'], -wd) # Off by 0.6 cents so it rounds off by a penny
#self.assertPayrollEqual(cats['EE_US_SIT'], -wd)
process_payslip(payslip)
# Make a new payslip, this one will have maximums
remaining_oh_unemp_wages = self.OH_UNEMP_MAX_WAGE - salary if (self.OH_UNEMP_MAX_WAGE - 2*salary < salary) \
else salary
self._log('2019 Ohio tax second payslip:')
payslip = self._createPayslip(employee, '2019-02-01', '2019-02-28')
payslip.compute_sheet()
cats = self._getCategories(payslip)
self.assertPayrollEqual(cats['ER_US_SUTA'], remaining_oh_unemp_wages * self.OH_UNEMP)
def test_2019_taxes_with_external(self):
salary = 5000.0
external_wages = 6000.0
employee = self._createEmployee()
contract = self._createContract(employee,
wage=salary,
state_id=self.get_us_state('OH'),
external_wages=external_wages,
)
self._log('2019 Ohio_external tax first payslip:')
payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
payslip.compute_sheet()
cats = self._getCategories(payslip)
self.assertPayrollEqual(cats['ER_US_SUTA'], (self.OH_UNEMP_MAX_WAGE - external_wages) * self.OH_UNEMP)
def test_2019_taxes_with_state_exempt(self):
salary = 5000.0
external_wages = 6000.0
employee = self._createEmployee()
contract = self._createContract(employee,
wage=salary,
state_id=self.get_us_state('OH'),
external_wages=external_wages,
futa_type=USHRContract.FUTA_TYPE_BASIC)
self._log('2019 Ohio exempt tax first payslip:')
payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
payslip.compute_sheet()
cats = self._getCategories(payslip)
# FUTA_TYPE_BASIC
self.assertPayrollEqual(cats.get('ER_US_SUTA', 0.0), salary * 0.0)

View File

@@ -0,0 +1,108 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
from datetime import date
from .common import TestUsPayslip, process_payslip
class TestUsOhPayslip(TestUsPayslip):
###
# Taxes and Rates
###
OH_UNEMP_MAX_WAGE = 9000.0
OH_UNEMP = 2.7
def test_2020_taxes(self):
self._test_er_suta('OH', self.OH_UNEMP, date(2020, 1, 1), wage_base=self.OH_UNEMP_MAX_WAGE)
def _run_test_sit(self,
wage=0.0,
schedule_pay='monthly',
filing_status='single',
dependent_credit=0.0,
other_income=0.0,
deductions=0.0,
additional_withholding=0.0,
is_nonresident_alien=False,
state_income_tax_exempt=False,
state_income_tax_additional_withholding=0.0,
oh_it4_sit_exemptions=0,
expected=0.0,
):
employee = self._createEmployee()
contract = self._createContract(employee,
wage=wage,
schedule_pay=schedule_pay,
fed_941_fit_w4_is_nonresident_alien=is_nonresident_alien,
fed_941_fit_w4_filing_status=filing_status,
fed_941_fit_w4_multiple_jobs_higher=False,
fed_941_fit_w4_dependent_credit=dependent_credit,
fed_941_fit_w4_other_income=other_income,
fed_941_fit_w4_deductions=deductions,
fed_941_fit_w4_additional_withholding=additional_withholding,
state_income_tax_exempt=state_income_tax_exempt,
state_income_tax_additional_withholding=state_income_tax_additional_withholding,
oh_it4_sit_exemptions=oh_it4_sit_exemptions,
state_id=self.get_us_state('OH'),
)
payslip = self._createPayslip(employee, '2020-01-01', '2020-01-31')
payslip.compute_sheet()
cats = self._getCategories(payslip)
# Instead of PayrollEqual after initial first round of testing.
self.assertPayrollAlmostEqual(cats.get('EE_US_SIT', 0.0), -expected)
return payslip
def test_2020_sit_1(self):
wage = 400.0
exemptions = 1
additional = 10.0
pay_periods = 12.0
annual_adjusted_wage = (wage * pay_periods) - (650.0 * exemptions)
self.assertPayrollEqual(4150.0, annual_adjusted_wage)
WD = ((annual_adjusted_wage * 0.005) / pay_periods) * 1.032
self.assertPayrollEqual(WD, 1.7845)
expected = WD + additional
self._run_test_sit(wage=wage,
schedule_pay='monthly',
state_income_tax_exempt=False,
state_income_tax_additional_withholding=additional,
oh_it4_sit_exemptions=exemptions,
expected=expected,
)
# the above agrees with online calculator to the penny 0.01
# below expected coming from calculator to 0.10
#
# semi-monthly
self._run_test_sit(wage=1200,
schedule_pay='semi-monthly',
state_income_tax_exempt=False,
state_income_tax_additional_withholding=20.0,
oh_it4_sit_exemptions=2,
expected=42.58,
)
# bi-weekly
self._run_test_sit(wage=3000,
schedule_pay='bi-weekly',
state_income_tax_exempt=False,
#state_income_tax_additional_withholding=0.0,
oh_it4_sit_exemptions=0,
expected=88.51,
)
# weekly
self._run_test_sit(wage=355,
schedule_pay='weekly',
state_income_tax_exempt=False,
# state_income_tax_additional_withholding=0.0,
oh_it4_sit_exemptions=1,
expected=4.87,
)
# Exempt!
self._run_test_sit(wage=355,
schedule_pay='weekly',
state_income_tax_exempt=True,
# state_income_tax_additional_withholding=0.0,
oh_it4_sit_exemptions=1,
expected=0.0,
)

View File

@@ -0,0 +1,33 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
from .common import TestUsPayslip, process_payslip
class TestUsPAPayslip(TestUsPayslip):
###
# Taxes and Rates
###
PA_UNEMP_MAX_WAGE = 10000.0
ER_PA_UNEMP = -3.6890 / 100.0
EE_PA_UNEMP = -0.06 / 100.0
PA_INC_WITHHOLD = 3.07
def test_2019_taxes(self):
salary = 4166.67
wh = -127.92
employee = self._createEmployee()
contract = self._createContract(employee,
wage=salary,
state_id=self.get_us_state('PA'))
self._log('2019 Pennsylvania tax first payslip:')
payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
payslip.compute_sheet()
cats = self._getCategories(payslip)
self.assertPayrollEqual(cats['EE_US_SUTA'], cats['GROSS'] * self.EE_PA_UNEMP)
self.assertPayrollEqual(cats['ER_US_SUTA'], cats['GROSS'] * self.ER_PA_UNEMP)
self.assertPayrollEqual(cats['EE_US_SIT'], wh)

View File

@@ -0,0 +1,43 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
from datetime import date
from .common import TestUsPayslip
class TestUsPAPayslip(TestUsPayslip):
###
# Taxes and Rates
###
PA_UNEMP_MAX_WAGE = 10000.0
ER_PA_UNEMP = 3.6890
EE_PA_UNEMP = 0.06
PA_INC_WITHHOLD = 3.07
def test_2020_taxes(self):
self._test_er_suta('PA', self.ER_PA_UNEMP, date(2020, 1, 1), wage_base=self.PA_UNEMP_MAX_WAGE)
self._test_ee_suta('PA', self.EE_PA_UNEMP, date(2020, 1, 1))
salary = 4166.67
wh = -127.92
employee = self._createEmployee()
contract = self._createContract(employee,
wage=salary,
state_id=self.get_us_state('PA'))
self._log('2019 Pennsylvania tax first payslip:')
payslip = self._createPayslip(employee, '2020-01-01', '2020-01-31')
payslip.compute_sheet()
cats = self._getCategories(payslip)
self.assertPayrollEqual(cats['EE_US_SIT'], wh)
# Test Additional
contract.us_payroll_config_id.state_income_tax_additional_withholding = 100.0
payslip.compute_sheet()
cats = self._getCategories(payslip)
self.assertPayrollEqual(cats['EE_US_SIT'], wh - 100.0)
# Test Exempt
contract.us_payroll_config_id.state_income_tax_exempt = True
payslip.compute_sheet()
cats = self._getCategories(payslip)
self.assertPayrollEqual(cats.get('EE_US_SIT', 0.0), 0.0)

View File

@@ -1,119 +0,0 @@
# -*- coding: utf-8 -*-
from logging import getLogger
from sys import float_info as sys_float_info
from odoo.tests import common
from odoo.tools.float_utils import float_round as odoo_float_round
from odoo.addons.l10n_us_hr_payroll.models.l10n_us_hr_payroll import USHrContract
def process_payslip(payslip):
try:
#v9
payslip.process_sheet()
except AttributeError:
payslip.action_payslip_done()
class TestUsPayslip(common.TransactionCase):
debug = False
_logger = getLogger(__name__)
float_info = sys_float_info
def float_round(self, value, digits):
return odoo_float_round(value, digits)
_payroll_digits = -1
@property
def payroll_digits(self):
if self._payroll_digits == -1:
self._payroll_digits = self.env['decimal.precision'].precision_get('Payroll')
return self._payroll_digits
def _log(self, message):
if self.debug:
self._logger.warn(message)
def _createEmployee(self):
return self.env['hr.employee'].create({
'birthday': '1985-03-14',
'country_id': self.ref('base.us'),
'department_id': self.ref('hr.dep_rd'),
'gender': 'male',
'name': 'Jared'
})
def _createContract(self, employee, salary,
schedule_pay='monthly',
w4_allowances=0,
w4_filing_status='single',
w4_is_nonresident_alien=False,
w4_additional_withholding=0.0,
external_wages=0.0,
struct_id=False,
futa_type=USHrContract.FUTA_TYPE_NORMAL,
):
if not struct_id:
struct_id = self.ref('l10n_us_hr_payroll.hr_payroll_salary_structure_us_employee')
values = {
'date_start': '2016-01-01',
'date_end': '2030-12-31',
'name': 'Contract for Jared 2016',
'wage': salary,
'type_id': self.ref('hr_contract.hr_contract_type_emp'),
'employee_id': employee.id,
'struct_id': struct_id,
'resource_calendar_id': self.ref('resource.resource_calendar_std'),
'schedule_pay': schedule_pay,
'w4_allowances': w4_allowances,
'w4_filing_status': w4_filing_status,
'w4_is_nonresident_alien': w4_is_nonresident_alien,
'w4_additional_withholding': w4_additional_withholding,
'external_wages': external_wages,
'futa_type': futa_type,
'state': 'open', # if not "Running" then no automatic selection when Payslip is created
}
try:
values['journal_id'] = self.env['account.journal'].search([('type', '=', 'general')], limit=1).id
except KeyError:
pass
return self.env['hr.contract'].create(values)
def _createPayslip(self, employee, date_from, date_to):
return self.env['hr.payslip'].create({
'employee_id': employee.id,
'date_from': date_from,
'date_to': date_to
})
def _getCategories(self, payslip):
detail_lines = payslip.details_by_salary_rule_category
categories = {}
for line in detail_lines:
self._log(' line code: ' + str(line.code) +
' category code: ' + line.category_id.code +
' total: ' + str(line.total) +
' rate: ' + str(line.rate) +
' amount: ' + str(line.amount))
if line.category_id.code not in categories:
categories[line.category_id.code] = line.total
else:
categories[line.category_id.code] += line.total
return categories
def assertPayrollEqual(self, first, second):
self.assertAlmostEqual(first, second, self.payroll_digits)
def test_semi_monthly(self):
salary = 80000.0
employee = self._createEmployee()
contract = self._createContract(employee, salary, schedule_pay='semi-monthly')
payslip = self._createPayslip(employee, '2016-01-01', '2016-01-14')
payslip.compute_sheet()

View File

@@ -1,368 +0,0 @@
from .test_us_payslip import TestUsPayslip, process_payslip
from odoo.addons.l10n_us_hr_payroll.models.l10n_us_hr_payroll import USHrContract
from sys import float_info
class TestUsPayslip2018(TestUsPayslip):
# FUTA Constants
FUTA_RATE_NORMAL = 0.6
FUTA_RATE_BASIC = 6.0
FUTA_RATE_EXEMPT = 0.0
# Wage caps
FICA_SS_MAX_WAGE = 128400.0
FICA_M_MAX_WAGE = float_info.max
FICA_M_ADD_START_WAGE = 200000.0
FUTA_MAX_WAGE = 7000.0
# Rates
FICA_SS = 6.2 / -100.0
FICA_M = 1.45 / -100.0
FUTA = FUTA_RATE_NORMAL / -100.0
FICA_M_ADD = 0.9 / -100.0
###
# 2018 Taxes and Rates
###
def test_2018_taxes(self):
# salary is high so that second payslip runs over max
# social security salary
salary = 80000.0
employee = self._createEmployee()
self._createContract(employee, salary)
self._log('2017 tax last slip')
payslip = self._createPayslip(employee, '2017-12-01', '2017-12-31')
payslip.compute_sheet()
process_payslip(payslip)
self._log('2018 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_FICA_SS'], salary)
self.assertPayrollEqual(cats['EE_US_FICA_SS'], cats['WAGE_US_FICA_SS'] * self.FICA_SS)
self.assertPayrollEqual(cats['WAGE_US_FICA_M'], salary)
self.assertPayrollEqual(cats['EE_US_FICA_M'], cats['WAGE_US_FICA_M'] * self.FICA_M)
self.assertPayrollEqual(cats['WAGE_US_FICA_M_ADD'], 0.0)
self.assertPayrollEqual(cats['EE_US_FICA_M_ADD'], 0.0)
self.assertPayrollEqual(cats['ER_US_FICA_SS'], cats['EE_US_FICA_SS'])
self.assertPayrollEqual(cats['ER_US_FICA_M'], cats['EE_US_FICA_M'])
self.assertPayrollEqual(cats['WAGE_US_FUTA'], self.FUTA_MAX_WAGE)
self.assertPayrollEqual(cats['ER_US_FUTA'], cats['WAGE_US_FUTA'] * self.FUTA)
process_payslip(payslip)
# Make a new payslip, this one will have maximums for FICA Social Security Wages
remaining_ss_wages = self.FICA_SS_MAX_WAGE - salary if (self.FICA_SS_MAX_WAGE - 2 * salary < salary) else salary
remaining_m_wages = self.FICA_M_MAX_WAGE - salary if (self.FICA_M_MAX_WAGE - 2 * salary < salary) else salary
self._log('2018 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_FICA_SS'], remaining_ss_wages)
self.assertPayrollEqual(cats['EE_US_FICA_SS'], cats['WAGE_US_FICA_SS'] * self.FICA_SS)
self.assertPayrollEqual(cats['WAGE_US_FICA_M'], remaining_m_wages)
self.assertPayrollEqual(cats['EE_US_FICA_M'], cats['WAGE_US_FICA_M'] * self.FICA_M)
self.assertPayrollEqual(cats['WAGE_US_FICA_M_ADD'], 0.0)
self.assertPayrollEqual(cats['EE_US_FICA_M_ADD'], 0.0)
self.assertPayrollEqual(cats['ER_US_FICA_SS'], cats['EE_US_FICA_SS'])
self.assertPayrollEqual(cats['ER_US_FICA_M'], cats['EE_US_FICA_M'])
self.assertPayrollEqual(cats['WAGE_US_FUTA'], 0)
self.assertPayrollEqual(cats['ER_US_FUTA'], 0)
process_payslip(payslip)
# Make a new payslip, this one will have reached Medicare Additional (employee only)
self._log('2018 tax third payslip:')
payslip = self._createPayslip(employee, '2018-03-01', '2018-03-31')
payslip.compute_sheet()
cats = self._getCategories(payslip)
self.assertPayrollEqual(cats['WAGE_US_FICA_M'], salary)
self.assertPayrollEqual(cats['WAGE_US_FICA_M_ADD'], self.FICA_M_ADD_START_WAGE - (salary * 2)) # aka 40k
self.assertPayrollEqual(cats['EE_US_FICA_M_ADD'], cats['WAGE_US_FICA_M_ADD'] * self.FICA_M_ADD)
process_payslip(payslip)
# Make a new payslip, this one will have all salary as Medicare Additional
self._log('2018 tax fourth payslip:')
payslip = self._createPayslip(employee, '2018-04-01', '2018-04-30')
payslip.compute_sheet()
cats = self._getCategories(payslip)
self.assertPayrollEqual(cats['WAGE_US_FICA_M'], salary)
self.assertPayrollEqual(cats['WAGE_US_FICA_M_ADD'], salary)
self.assertPayrollEqual(cats['EE_US_FICA_M_ADD'], cats['WAGE_US_FICA_M_ADD'] * self.FICA_M_ADD)
process_payslip(payslip)
def test_2018_fed_income_withholding_single(self):
salary = 6000.00
schedule_pay = 'monthly'
w4_allowances = 3
w4_allowance_amt = 345.80 * w4_allowances
adjusted_salary = salary - w4_allowance_amt # should be 4962.60, but would work over a wide value for the rate
###
# Single MONTHLY form Publication 15
expected_withholding = self.float_round(-(371.12 + ((adjusted_salary - 3533) * 0.22)), self.payroll_digits)
employee = self._createEmployee()
self._createContract(employee, salary, schedule_pay, w4_allowances, 'single')
self._log('2018 fed income single payslip: adjusted_salary: ' + str(adjusted_salary))
payslip = self._createPayslip(employee, '2018-01-01', '2018-01-31')
payslip.compute_sheet()
cats = self._getCategories(payslip)
self.assertPayrollEqual(cats['EE_US_FED_INC_WITHHOLD'], expected_withholding)
def test_2018_fed_income_withholding_married_as_single(self):
salary = 500.00
schedule_pay = 'weekly'
w4_allowances = 1
w4_allowance_amt = 79.80 * w4_allowances
adjusted_salary = salary - w4_allowance_amt # should be 420.50, but would work over a wide value for the rate
###
# Single MONTHLY form Publication 15
expected_withholding = self.float_round(-(18.30 + ((adjusted_salary - 254) * 0.12)), self.payroll_digits)
employee = self._createEmployee()
self._createContract(employee, salary, schedule_pay, w4_allowances, 'married_as_single')
self._log('2018 fed income married_as_single payslip: adjusted_salary: ' + str(adjusted_salary))
payslip = self._createPayslip(employee, '2018-01-01', '2018-01-31')
payslip.compute_sheet()
cats = self._getCategories(payslip)
self.assertPayrollEqual(cats['EE_US_FED_INC_WITHHOLD'], expected_withholding)
def test_2018_fed_income_withholding_married(self):
salary = 14000.00
schedule_pay = 'bi-weekly'
w4_allowances = 2
w4_allowance_amt = 159.60 * w4_allowances
adjusted_salary = salary - w4_allowance_amt # should be 13680.80, but would work over a wide value for the rate
###
# Single MONTHLY form Publication 15
expected_withholding = self.float_round(-(2468.56 + ((adjusted_salary - 12560) * 0.32)), self.payroll_digits)
employee = self._createEmployee()
self._createContract(employee, salary, schedule_pay, w4_allowances, 'married')
self._log('2018 fed income married payslip: adjusted_salary: ' + str(adjusted_salary))
payslip = self._createPayslip(employee, '2018-01-01', '2018-01-31')
payslip.compute_sheet()
cats = self._getCategories(payslip)
self.assertPayrollEqual(cats['EE_US_FED_INC_WITHHOLD'], expected_withholding)
def test_2018_taxes_with_external(self):
# social security salary
salary = self.FICA_M_ADD_START_WAGE
external_wages = 6000.0
employee = self._createEmployee()
self._createContract(employee, salary, external_wages=external_wages)
self._log('2018 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_FICA_SS'], self.FICA_SS_MAX_WAGE - external_wages)
self.assertPayrollEqual(cats['EE_US_FICA_SS'], cats['WAGE_US_FICA_SS'] * self.FICA_SS)
self.assertPayrollEqual(cats['WAGE_US_FICA_M'], salary)
self.assertPayrollEqual(cats['EE_US_FICA_M'], cats['WAGE_US_FICA_M'] * self.FICA_M)
self.assertPayrollEqual(cats['WAGE_US_FICA_M_ADD'], 0.0)
self.assertPayrollEqual(cats['EE_US_FICA_M_ADD'], cats['WAGE_US_FICA_M_ADD'] * self.FICA_M_ADD)
self.assertPayrollEqual(cats['ER_US_FICA_SS'], cats['EE_US_FICA_SS'])
self.assertPayrollEqual(cats['ER_US_FICA_M'], cats['EE_US_FICA_M'])
self.assertPayrollEqual(cats['WAGE_US_FUTA'], self.FUTA_MAX_WAGE - external_wages)
self.assertPayrollEqual(cats['ER_US_FUTA'], cats['WAGE_US_FUTA'] * self.FUTA)
def test_2018_taxes_with_full_futa(self):
futa_rate = self.FUTA_RATE_BASIC / -100.0
# social security salary
salary = self.FICA_M_ADD_START_WAGE
employee = self._createEmployee()
self._createContract(employee, salary, futa_type=USHrContract.FUTA_TYPE_BASIC)
self._log('2018 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_FICA_SS'], self.FICA_SS_MAX_WAGE)
self.assertPayrollEqual(cats['EE_US_FICA_SS'], cats['WAGE_US_FICA_SS'] * self.FICA_SS)
self.assertPayrollEqual(cats['WAGE_US_FICA_M'], salary)
self.assertPayrollEqual(cats['EE_US_FICA_M'], cats['WAGE_US_FICA_M'] * self.FICA_M)
self.assertPayrollEqual(cats['WAGE_US_FICA_M_ADD'], 0.0)
self.assertPayrollEqual(cats['EE_US_FICA_M_ADD'], cats['WAGE_US_FICA_M_ADD'] * self.FICA_M_ADD)
self.assertPayrollEqual(cats['ER_US_FICA_SS'], cats['EE_US_FICA_SS'])
self.assertPayrollEqual(cats['ER_US_FICA_M'], cats['EE_US_FICA_M'])
self.assertPayrollEqual(cats['WAGE_US_FUTA'], self.FUTA_MAX_WAGE)
self.assertPayrollEqual(cats['ER_US_FUTA'], cats['WAGE_US_FUTA'] * futa_rate)
def test_2018_taxes_with_futa_exempt(self):
futa_rate = self.FUTA_RATE_EXEMPT / -100.0 # because of exemption
# social security salary
salary = self.FICA_M_ADD_START_WAGE
employee = self._createEmployee()
self._createContract(employee, salary, futa_type=USHrContract.FUTA_TYPE_EXEMPT)
self._log('2018 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_FICA_SS'], self.FICA_SS_MAX_WAGE)
self.assertPayrollEqual(cats['EE_US_FICA_SS'], cats['WAGE_US_FICA_SS'] * self.FICA_SS)
self.assertPayrollEqual(cats['WAGE_US_FICA_M'], salary)
self.assertPayrollEqual(cats['EE_US_FICA_M'], cats['WAGE_US_FICA_M'] * self.FICA_M)
self.assertPayrollEqual(cats['WAGE_US_FICA_M_ADD'], 0.0)
self.assertPayrollEqual(cats['EE_US_FICA_M_ADD'], cats['WAGE_US_FICA_M_ADD'] * self.FICA_M_ADD)
self.assertPayrollEqual(cats['ER_US_FICA_SS'], cats['EE_US_FICA_SS'])
self.assertPayrollEqual(cats['ER_US_FICA_M'], cats['EE_US_FICA_M'])
futa_wages = 0.0
if 'WAGE_US_FUTA' in cats:
futa_wages = cats['WAGE_US_FUTA']
futa = 0.0
if 'ER_US_FUTA' in cats:
futa = cats['ER_US_FUTA']
self.assertPayrollEqual(futa_wages, 0.0)
self.assertPayrollEqual(futa, futa_wages * futa_rate)
def test_2018_fed_income_withholding_nonresident_alien(self):
salary = 3500.00
schedule_pay = 'quarterly'
w4_allowances = 1
w4_allowance_amt = 1037.50 * w4_allowances
nra_adjustment = 1962.50 # for quarterly
adjusted_salary = salary - w4_allowance_amt + nra_adjustment # 4425
###
# Single QUARTERLY form Publication 15
expected_withholding = self.float_round(-(238.10 + ((adjusted_salary - 3306) * 0.12)), self.payroll_digits)
employee = self._createEmployee()
self._createContract(employee, salary, schedule_pay, w4_allowances, 'single',
w4_is_nonresident_alien=True)
self._log('2018 fed income single payslip nonresident alien: adjusted_salary: ' + str(adjusted_salary))
payslip = self._createPayslip(employee, '2018-01-01', '2018-01-31')
payslip.compute_sheet()
cats = self._getCategories(payslip)
self.assertPayrollEqual(cats['EE_US_FED_INC_WITHHOLD'], expected_withholding)
def test_2018_fed_income_additional_withholding(self):
salary = 50000.00
schedule_pay = 'annually'
w4_additional_withholding = 5000.0
w4_allowances = 2
w4_allowance_amt = 4150.00 * w4_allowances
adjusted_salary = salary - w4_allowance_amt # 41700
###
# Single ANNUAL form Publication 15
expected_withholding = \
self.float_round(-((1905.00 + ((adjusted_salary - 30600) * 0.12)) + w4_additional_withholding),
self.payroll_digits)
employee = self._createEmployee()
self._createContract(employee, salary, schedule_pay, w4_allowances, 'married',
w4_additional_withholding=w4_additional_withholding)
self._log('2018 fed income married payslip additional withholding: adjusted_salary: ' + str(adjusted_salary))
payslip = self._createPayslip(employee, '2018-01-01', '2018-01-31')
payslip.compute_sheet()
cats = self._getCategories(payslip)
self.assertPayrollEqual(cats['EE_US_FED_INC_WITHHOLD'], expected_withholding)
def test_2018_taxes_with_w4_exempt(self):
salary = 6000.0
schedule_pay = 'bi-weekly'
w4_allowances = 0
employee = self._createEmployee()
self._createContract(employee, salary, schedule_pay, w4_allowances, '')
self._log('2018 tax w4 exempt payslip:')
payslip = self._createPayslip(employee, '2018-01-01', '2018-01-31')
payslip.compute_sheet()
cats = self._getCategories(payslip)
fed_inc_withhold = 0.0
if 'EE_US_FED_INC_WITHHOLD' in cats:
fed_inc_withhold = cats['EE_US_FED_INC_WITHHOLD']
self.assertPayrollEqual(fed_inc_withhold, 0.0)
def test_2018_taxes_with_fica_exempt(self):
salary = 6000.0
schedule_pay = 'bi-weekly'
w4_allowances = 2
employee = self._createEmployee()
contract = self._createContract(employee, salary, schedule_pay, w4_allowances)
contract.fica_exempt = True
self._log('2018 tax w4 exempt payslip:')
payslip = self._createPayslip(employee, '2018-01-01', '2018-01-31')
payslip.compute_sheet()
cats = self._getCategories(payslip)
ss_wages = cats.get('WAGE_US_FICA_SS', 0.0)
med_wages = cats.get('WAGE_US_FICA_M', 0.0)
ss = cats.get('EE_US_FICA_SS', 0.0)
med = cats.get('EE_US_FICA_M', 0.0)
self.assertPayrollEqual(ss_wages, 0.0)
self.assertPayrollEqual(med_wages, 0.0)
self.assertPayrollEqual(ss, 0.0)
self.assertPayrollEqual(med, 0.0)

239
l10n_us_hr_payroll/tests/test_us_payslip_2019.py Executable file → Normal file
View File

@@ -1,6 +1,8 @@
from .test_us_payslip import TestUsPayslip, process_payslip
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
from odoo.addons.l10n_us_hr_payroll.models.l10n_us_hr_payroll import USHrContract
from .common import TestUsPayslip, process_payslip
from odoo.addons.l10n_us_hr_payroll.models.hr_contract import USHRContract
from sys import float_info
@@ -28,41 +30,44 @@ class TestUsPayslip2019(TestUsPayslip):
###
def test_2019_taxes(self):
self.debug = False
# salary is high so that second payslip runs over max
# social security salary
salary = 80000.0
employee = self._createEmployee()
self._createContract(employee, salary)
contract = self._createContract(employee, wage=salary)
self._log(contract.read())
self._log('2018 tax last slip')
payslip = self._createPayslip(employee, '2018-12-01', '2018-12-31')
payslip.compute_sheet()
self._log(payslip.read())
process_payslip(payslip)
# Ensure amounts are there, they shouldn't be added in the next year...
cats = self._getCategories(payslip)
self.assertTrue(cats['ER_US_940_FUTA'], self.FUTA_MAX_WAGE * self.FUTA)
self._log('2019 tax first payslip:')
payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
payslip.compute_sheet()
cats = self._getCategories(payslip)
self.assertPayrollEqual(cats['WAGE_US_FICA_SS'], salary)
self.assertPayrollEqual(cats['EE_US_FICA_SS'], cats['WAGE_US_FICA_SS'] * self.FICA_SS)
self.assertPayrollEqual(cats['WAGE_US_FICA_M'], salary)
self.assertPayrollEqual(cats['EE_US_FICA_M'], cats['WAGE_US_FICA_M'] * self.FICA_M)
self.assertPayrollEqual(cats['WAGE_US_FICA_M_ADD'], 0.0)
self.assertPayrollEqual(cats['EE_US_FICA_M_ADD'], 0.0)
self.assertPayrollEqual(cats['ER_US_FICA_SS'], cats['EE_US_FICA_SS'])
self.assertPayrollEqual(cats['ER_US_FICA_M'], cats['EE_US_FICA_M'])
self.assertPayrollEqual(cats['WAGE_US_FUTA'], self.FUTA_MAX_WAGE)
self.assertPayrollEqual(cats['ER_US_FUTA'], cats['WAGE_US_FUTA'] * self.FUTA)
rules = self._getRules(payslip)
# Employee
self.assertPayrollEqual(rules['EE_US_941_FICA_SS'], cats['BASIC'] * self.FICA_SS)
self.assertPayrollEqual(rules['EE_US_941_FICA_M'], cats['BASIC'] * self.FICA_M)
self.assertPayrollEqual(rules['EE_US_941_FICA_M_ADD'], 0.0)
# Employer
self.assertPayrollEqual(rules['ER_US_941_FICA_SS'], rules['EE_US_941_FICA_SS'])
self.assertPayrollEqual(rules['ER_US_941_FICA_M'], rules['EE_US_941_FICA_M'])
self.assertTrue(cats['ER_US_940_FUTA'], self.FUTA_MAX_WAGE * self.FUTA)
process_payslip(payslip)
# Make a new payslip, this one will have maximums for FICA Social Security Wages
# Make a new payslip, this one will have reached Medicare Additional (employee only)
remaining_ss_wages = self.FICA_SS_MAX_WAGE - salary if (self.FICA_SS_MAX_WAGE - 2 * salary < salary) else salary
remaining_m_wages = self.FICA_M_MAX_WAGE - salary if (self.FICA_M_MAX_WAGE - 2 * salary < salary) else salary
@@ -72,32 +77,25 @@ class TestUsPayslip2019(TestUsPayslip):
payslip.compute_sheet()
cats = self._getCategories(payslip)
rules = self._getRules(payslip)
self.assertPayrollEqual(cats['WAGE_US_FICA_SS'], remaining_ss_wages)
self.assertPayrollEqual(cats['EE_US_FICA_SS'], cats['WAGE_US_FICA_SS'] * self.FICA_SS)
self.assertPayrollEqual(cats['WAGE_US_FICA_M'], remaining_m_wages)
self.assertPayrollEqual(cats['EE_US_FICA_M'], cats['WAGE_US_FICA_M'] * self.FICA_M)
self.assertPayrollEqual(cats['WAGE_US_FICA_M_ADD'], 0.0)
self.assertPayrollEqual(cats['EE_US_FICA_M_ADD'], 0.0)
self.assertPayrollEqual(cats['ER_US_FICA_SS'], cats['EE_US_FICA_SS'])
self.assertPayrollEqual(cats['ER_US_FICA_M'], cats['EE_US_FICA_M'])
self.assertPayrollEqual(cats['WAGE_US_FUTA'], 0)
self.assertPayrollEqual(cats['ER_US_FUTA'], 0)
self.assertPayrollEqual(rules['EE_US_941_FICA_SS'], remaining_ss_wages * self.FICA_SS)
self.assertPayrollEqual(rules['EE_US_941_FICA_M'], remaining_m_wages * self.FICA_M)
self.assertPayrollEqual(rules['EE_US_941_FICA_M_ADD'], 0.0)
self.assertPayrollEqual(cats['ER_US_940_FUTA'], 0.0)
process_payslip(payslip)
# Make a new payslip, this one will have reached Medicare Additional (employee only)
self._log('2019 tax third payslip:')
payslip = self._createPayslip(employee, '2019-03-01', '2019-03-31')
payslip.compute_sheet()
cats = self._getCategories(payslip)
rules = self._getRules(payslip)
self.assertPayrollEqual(cats['WAGE_US_FICA_M'], salary)
self.assertPayrollEqual(cats['WAGE_US_FICA_M_ADD'], self.FICA_M_ADD_START_WAGE - (salary * 2)) # aka 40k
self.assertPayrollEqual(cats['EE_US_FICA_M_ADD'], cats['WAGE_US_FICA_M_ADD'] * self.FICA_M_ADD)
self.assertPayrollEqual(rules['EE_US_941_FICA_M_ADD'], (self.FICA_M_ADD_START_WAGE - (salary * 2)) * self.FICA_M_ADD) # aka 40k
process_payslip(payslip)
@@ -109,14 +107,15 @@ class TestUsPayslip2019(TestUsPayslip):
payslip.compute_sheet()
cats = self._getCategories(payslip)
rules = self._getRules(payslip)
self.assertPayrollEqual(cats['WAGE_US_FICA_M'], salary)
self.assertPayrollEqual(cats['WAGE_US_FICA_M_ADD'], salary)
self.assertPayrollEqual(cats['EE_US_FICA_M_ADD'], cats['WAGE_US_FICA_M_ADD'] * self.FICA_M_ADD)
self.assertPayrollEqual(rules['EE_US_941_FICA_M_ADD'], salary * self.FICA_M_ADD)
process_payslip(payslip)
def test_2019_fed_income_withholding_single(self):
self.debug = False
salary = 6000.00
schedule_pay = 'monthly'
w4_allowances = 3
@@ -127,16 +126,18 @@ class TestUsPayslip2019(TestUsPayslip):
expected_withholding = self.float_round(-(378.52 + ((adjusted_salary - 3606) * 0.22)), self.payroll_digits)
employee = self._createEmployee()
self._createContract(employee, salary, schedule_pay, w4_allowances, 'single')
contract = self._createContract(employee,
wage=salary,
schedule_pay=schedule_pay,
fed_941_fit_w4_filing_status='single',
fed_941_fit_w4_allowances=w4_allowances
)
self._log('2019 fed income single payslip: adjusted_salary: ' + str(adjusted_salary))
payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
payslip.compute_sheet()
cats = self._getCategories(payslip)
self.assertPayrollEqual(cats['EE_US_FED_INC_WITHHOLD'], expected_withholding)
self.assertPayrollEqual(cats['EE_US_941_FIT'], expected_withholding)
def test_2019_fed_income_withholding_married_as_single(self):
salary = 500.00
@@ -145,20 +146,21 @@ class TestUsPayslip2019(TestUsPayslip):
w4_allowance_amt = 80.80 * w4_allowances
adjusted_salary = salary - w4_allowance_amt # should be 420.50, but would work over a wide value for the rate
###
# Single MONTHLY form Publication 15
expected_withholding = self.float_round(-(18.70 + ((adjusted_salary - 260) * 0.12)), self.payroll_digits)
employee = self._createEmployee()
self._createContract(employee, salary, schedule_pay, w4_allowances, 'married_as_single')
contract = self._createContract(employee,
wage=salary,
schedule_pay=schedule_pay,
fed_941_fit_w4_filing_status='married_as_single',
fed_941_fit_w4_allowances=w4_allowances,
)
self._log('2019 fed income married_as_single payslip: adjusted_salary: ' + str(adjusted_salary))
payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
payslip.compute_sheet()
cats = self._getCategories(payslip)
self.assertPayrollEqual(cats['EE_US_FED_INC_WITHHOLD'], expected_withholding)
self.assertPayrollEqual(cats['EE_US_941_FIT'], expected_withholding)
def test_2019_fed_income_withholding_married(self):
salary = 14000.00
@@ -171,53 +173,52 @@ class TestUsPayslip2019(TestUsPayslip):
expected_withholding = self.float_round(-(2519.06 + ((adjusted_salary - 12817) * 0.32)), self.payroll_digits)
employee = self._createEmployee()
self._createContract(employee, salary, schedule_pay, w4_allowances, 'married')
contract = self._createContract(employee,
wage=salary,
schedule_pay=schedule_pay,
fed_941_fit_w4_filing_status='married',
fed_941_fit_w4_allowances=w4_allowances
)
self._log('2019 fed income married payslip: adjusted_salary: ' + str(adjusted_salary))
payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
payslip.compute_sheet()
cats = self._getCategories(payslip)
self.assertPayrollEqual(cats['EE_US_FED_INC_WITHHOLD'], expected_withholding)
# This is off by 1 penny given our new reporting of adjusted wage * computed percentage
#self.assertPayrollEqual(cats['EE_US_941_FIT'], expected_withholding)
self.assertTrue(abs(cats['EE_US_941_FIT'] - expected_withholding) < 0.011)
def test_2019_taxes_with_external(self):
# social security salary
salary = self.FICA_M_ADD_START_WAGE
external_wages = 6000.0
employee = self._createEmployee()
self._createContract(employee, salary, external_wages=external_wages)
self._createContract(employee, wage=salary, external_wages=external_wages)
self._log('2019 tax first payslip:')
payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
payslip.compute_sheet()
cats = self._getCategories(payslip)
self.assertPayrollEqual(cats['WAGE_US_FICA_SS'], self.FICA_SS_MAX_WAGE - external_wages)
self.assertPayrollEqual(cats['EE_US_FICA_SS'], cats['WAGE_US_FICA_SS'] * self.FICA_SS)
self.assertPayrollEqual(cats['WAGE_US_FICA_M'], salary)
self.assertPayrollEqual(cats['EE_US_FICA_M'], cats['WAGE_US_FICA_M'] * self.FICA_M)
self.assertPayrollEqual(cats['WAGE_US_FICA_M_ADD'], 0.0)
self.assertPayrollEqual(cats['EE_US_FICA_M_ADD'], cats['WAGE_US_FICA_M_ADD'] * self.FICA_M_ADD)
self.assertPayrollEqual(cats['ER_US_FICA_SS'], cats['EE_US_FICA_SS'])
self.assertPayrollEqual(cats['ER_US_FICA_M'], cats['EE_US_FICA_M'])
self.assertPayrollEqual(cats['WAGE_US_FUTA'], self.FUTA_MAX_WAGE - external_wages)
self.assertPayrollEqual(cats['ER_US_FUTA'], cats['WAGE_US_FUTA'] * self.FUTA)
rules = self._getRules(payslip)
self.assertPayrollEqual(rules['EE_US_941_FICA_SS'], (self.FICA_SS_MAX_WAGE - external_wages) * self.FICA_SS)
self.assertPayrollEqual(rules['EE_US_941_FICA_M'], salary * self.FICA_M)
self.assertPayrollEqual(rules['EE_US_941_FICA_M_ADD'], external_wages * self.FICA_M_ADD)
self.assertPayrollEqual(rules['ER_US_941_FICA_SS'], rules['EE_US_941_FICA_SS'])
self.assertPayrollEqual(rules['ER_US_941_FICA_M'], rules['EE_US_941_FICA_M'])
self.assertPayrollEqual(cats['ER_US_940_FUTA'], (self.FUTA_MAX_WAGE - external_wages) * self.FUTA)
def test_2019_taxes_with_full_futa(self):
self.debug = False
futa_rate = self.FUTA_RATE_BASIC / -100.0
# social security salary
salary = self.FICA_M_ADD_START_WAGE
employee = self._createEmployee()
self._createContract(employee, salary, futa_type=USHrContract.FUTA_TYPE_BASIC)
self._createContract(employee, wage=salary, fed_940_type=USHRContract.FUTA_TYPE_BASIC)
self._log('2019 tax first payslip:')
payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
@@ -225,52 +226,26 @@ class TestUsPayslip2019(TestUsPayslip):
payslip.compute_sheet()
cats = self._getCategories(payslip)
self.assertPayrollEqual(cats['WAGE_US_FICA_SS'], self.FICA_SS_MAX_WAGE)
self.assertPayrollEqual(cats['EE_US_FICA_SS'], cats['WAGE_US_FICA_SS'] * self.FICA_SS)
self.assertPayrollEqual(cats['WAGE_US_FICA_M'], salary)
self.assertPayrollEqual(cats['EE_US_FICA_M'], cats['WAGE_US_FICA_M'] * self.FICA_M)
self.assertPayrollEqual(cats['WAGE_US_FICA_M_ADD'], 0.0)
self.assertPayrollEqual(cats['EE_US_FICA_M_ADD'], cats['WAGE_US_FICA_M_ADD'] * self.FICA_M_ADD)
self.assertPayrollEqual(cats['ER_US_FICA_SS'], cats['EE_US_FICA_SS'])
self.assertPayrollEqual(cats['ER_US_FICA_M'], cats['EE_US_FICA_M'])
self.assertPayrollEqual(cats['WAGE_US_FUTA'], self.FUTA_MAX_WAGE)
self.assertPayrollEqual(cats['ER_US_FUTA'], cats['WAGE_US_FUTA'] * futa_rate)
rules = self._getRules(payslip)
self.assertPayrollEqual(rules['EE_US_941_FICA_SS'], self.FICA_SS_MAX_WAGE * self.FICA_SS)
self.assertPayrollEqual(rules['EE_US_941_FICA_M'], salary * self.FICA_M)
self.assertPayrollEqual(rules['EE_US_941_FICA_M_ADD'], 0.0 * self.FICA_M_ADD)
self.assertPayrollEqual(rules['ER_US_941_FICA_SS'], rules['EE_US_941_FICA_SS'])
self.assertPayrollEqual(rules['ER_US_941_FICA_M'], rules['EE_US_941_FICA_M'])
self.assertPayrollEqual(cats['ER_US_940_FUTA'], self.FUTA_MAX_WAGE * futa_rate)
def test_2019_taxes_with_futa_exempt(self):
futa_rate = self.FUTA_RATE_EXEMPT / -100.0 # because of exemption
# social security salary
salary = self.FICA_M_ADD_START_WAGE
employee = self._createEmployee()
self._createContract(employee, salary, futa_type=USHrContract.FUTA_TYPE_EXEMPT)
self._createContract(employee, wage=salary, fed_940_type=USHRContract.FUTA_TYPE_EXEMPT)
self._log('2019 tax first payslip:')
payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
payslip.compute_sheet()
cats = self._getCategories(payslip)
self.assertPayrollEqual(cats['WAGE_US_FICA_SS'], self.FICA_SS_MAX_WAGE)
self.assertPayrollEqual(cats['EE_US_FICA_SS'], cats['WAGE_US_FICA_SS'] * self.FICA_SS)
self.assertPayrollEqual(cats['WAGE_US_FICA_M'], salary)
self.assertPayrollEqual(cats['EE_US_FICA_M'], cats['WAGE_US_FICA_M'] * self.FICA_M)
self.assertPayrollEqual(cats['WAGE_US_FICA_M_ADD'], 0.0)
self.assertPayrollEqual(cats['EE_US_FICA_M_ADD'], cats['WAGE_US_FICA_M_ADD'] * self.FICA_M_ADD)
self.assertPayrollEqual(cats['ER_US_FICA_SS'], cats['EE_US_FICA_SS'])
self.assertPayrollEqual(cats['ER_US_FICA_M'], cats['EE_US_FICA_M'])
futa_wages = 0.0
if 'WAGE_US_FUTA' in cats:
futa_wages = cats['WAGE_US_FUTA']
futa = 0.0
if 'ER_US_FUTA' in cats:
futa = cats['ER_US_FUTA']
self.assertPayrollEqual(futa_wages, 0.0)
self.assertPayrollEqual(futa, futa_wages * futa_rate)
self.assertPayrollEqual(cats['ER_US_940_FUTA'], 0.0)
def test_2019_fed_income_withholding_nonresident_alien(self):
salary = 3500.00
@@ -278,24 +253,26 @@ class TestUsPayslip2019(TestUsPayslip):
w4_allowances = 1
w4_allowance_amt = 1050.0 * w4_allowances
nra_adjustment = 2000.0 # for quarterly
adjusted_salary = salary - w4_allowance_amt + nra_adjustment # 4425
adjusted_salary = salary - w4_allowance_amt + nra_adjustment # 4450
###
# Single QUARTERLY form Publication 15
expected_withholding = self.float_round(-(242.50 + ((adjusted_salary - 3375) * 0.12)), self.payroll_digits)
employee = self._createEmployee()
self._createContract(employee, salary, schedule_pay, w4_allowances, 'single',
w4_is_nonresident_alien=True)
contract = self._createContract(employee,
wage=salary,
schedule_pay=schedule_pay,
fed_941_fit_w4_allowances=w4_allowances,
fed_941_fit_w4_is_nonresident_alien=True,
)
self._log('2019 fed income single payslip nonresident alien: adjusted_salary: ' + str(adjusted_salary))
payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
payslip.compute_sheet()
cats = self._getCategories(payslip)
self.assertPayrollEqual(cats['EE_US_FED_INC_WITHHOLD'], expected_withholding)
rules = self._getRules(payslip)
self.assertPayrollEqual(rules['EE_US_941_FIT'], expected_withholding)
def test_2019_fed_income_additional_withholding(self):
salary = 50000.00
@@ -312,44 +289,45 @@ class TestUsPayslip2019(TestUsPayslip):
self.payroll_digits)
employee = self._createEmployee()
self._createContract(employee, salary, schedule_pay, w4_allowances, 'married',
w4_additional_withholding=w4_additional_withholding)
contract = self._createContract(employee,
wage=salary,
schedule_pay=schedule_pay,
fed_941_fit_w4_filing_status='married',
fed_941_fit_w4_allowances=w4_allowances,
fed_941_fit_w4_additional_withholding=w4_additional_withholding,
)
self._log('2019 fed income married payslip additional withholding: adjusted_salary: ' + str(adjusted_salary))
payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
payslip.compute_sheet()
cats = self._getCategories(payslip)
self.assertPayrollEqual(cats['EE_US_FED_INC_WITHHOLD'], expected_withholding)
rules = self._getRules(payslip)
self.assertPayrollEqual(rules['EE_US_941_FIT'], expected_withholding)
def test_2019_taxes_with_w4_exempt(self):
salary = 6000.0
schedule_pay = 'bi-weekly'
w4_allowances = 0
employee = self._createEmployee()
self._createContract(employee, salary, schedule_pay, w4_allowances, '')
contract = self._createContract(employee,
wage=salary,
schedule_pay=schedule_pay,
fed_941_fit_w4_allowances=w4_allowances,
fed_941_fit_w4_filing_status='',
)
self._log('2019 tax w4 exempt payslip:')
payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
payslip.compute_sheet()
cats = self._getCategories(payslip)
fed_inc_withhold = 0.0
if 'EE_US_FED_INC_WITHHOLD' in cats:
fed_inc_withhold = cats['EE_US_FED_INC_WITHHOLD']
self.assertPayrollEqual(fed_inc_withhold, 0.0)
rules = self._getRules(payslip)
self.assertPayrollEqual(rules['EE_US_941_FIT'], 0.0)
def test_2019_taxes_with_fica_exempt(self):
salary = 6000.0
schedule_pay = 'bi-weekly'
w4_allowances = 2
employee = self._createEmployee()
contract = self._createContract(employee, salary, schedule_pay, w4_allowances)
contract.fica_exempt = True
contract = self._createContract(employee, wage=salary, schedule_pay=schedule_pay)
contract.us_payroll_config_id.fed_941_fica_exempt = True
self._log('2019 tax w4 exempt payslip:')
payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
@@ -357,12 +335,5 @@ class TestUsPayslip2019(TestUsPayslip):
payslip.compute_sheet()
cats = self._getCategories(payslip)
ss_wages = cats.get('WAGE_US_FICA_SS', 0.0)
med_wages = cats.get('WAGE_US_FICA_M', 0.0)
ss = cats.get('EE_US_FICA_SS', 0.0)
med = cats.get('EE_US_FICA_M', 0.0)
self.assertPayrollEqual(ss_wages, 0.0)
self.assertPayrollEqual(med_wages, 0.0)
self.assertPayrollEqual(ss, 0.0)
self.assertPayrollEqual(med, 0.0)
self.assertPayrollEqual(cats['EE_US_941_FICA'], 0.0)
self.assertPayrollEqual(cats['ER_US_941_FICA'], 0.0)

View File

@@ -0,0 +1,302 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
from .common import TestUsPayslip, process_payslip
from odoo.addons.l10n_us_hr_payroll.models.hr_contract import USHRContract
from sys import float_info
class TestUsPayslip2020(TestUsPayslip):
# FUTA Constants
FUTA_RATE_NORMAL = 0.6
FUTA_RATE_BASIC = 6.0
FUTA_RATE_EXEMPT = 0.0
# Wage caps
FICA_SS_MAX_WAGE = 137700.0
FICA_M_MAX_WAGE = float_info.max
FICA_M_ADD_START_WAGE = 200000.0
FUTA_MAX_WAGE = 7000.0
# Rates
FICA_SS = 6.2 / -100.0
FICA_M = 1.45 / -100.0
FUTA = FUTA_RATE_NORMAL / -100.0
FICA_M_ADD = 0.9 / -100.0
###
# 2020 Taxes and Rates
###
def test_2020_taxes(self):
self.debug = False
# salary is high so that second payslip runs over max
# social security salary
salary = 80000.0
employee = self._createEmployee()
contract = self._createContract(employee, wage=salary)
self._log(contract.read())
self._log('2019 tax last slip')
payslip = self._createPayslip(employee, '2019-12-01', '2019-12-31')
payslip.compute_sheet()
self._log(payslip.read())
process_payslip(payslip)
# Ensure amounts are there, they shouldn't be added in the next year...
cats = self._getCategories(payslip)
self.assertTrue(cats['ER_US_940_FUTA'], self.FUTA_MAX_WAGE * self.FUTA)
self._log('2020 tax first payslip:')
payslip = self._createPayslip(employee, '2020-01-01', '2020-01-31')
payslip.compute_sheet()
cats = self._getCategories(payslip)
rules = self._getRules(payslip)
# Employee
self.assertPayrollEqual(rules['EE_US_941_FICA_SS'], cats['BASIC'] * self.FICA_SS)
self.assertPayrollEqual(rules['EE_US_941_FICA_M'], cats['BASIC'] * self.FICA_M)
self.assertPayrollEqual(rules['EE_US_941_FICA_M_ADD'], 0.0)
# Employer
self.assertPayrollEqual(rules['ER_US_941_FICA_SS'], rules['EE_US_941_FICA_SS'])
self.assertPayrollEqual(rules['ER_US_941_FICA_M'], rules['EE_US_941_FICA_M'])
self.assertTrue(cats['ER_US_940_FUTA'], self.FUTA_MAX_WAGE * self.FUTA)
process_payslip(payslip)
# Make a new payslip, this one will have reached Medicare Additional (employee only)
remaining_ss_wages = self.FICA_SS_MAX_WAGE - salary if (self.FICA_SS_MAX_WAGE - 2 * salary < salary) else salary
remaining_m_wages = self.FICA_M_MAX_WAGE - salary if (self.FICA_M_MAX_WAGE - 2 * salary < salary) else salary
self._log('2020 tax second payslip:')
payslip = self._createPayslip(employee, '2020-02-01', '2020-02-28')
payslip.compute_sheet()
cats = self._getCategories(payslip)
rules = self._getRules(payslip)
self.assertPayrollEqual(rules['EE_US_941_FICA_SS'], remaining_ss_wages * self.FICA_SS)
self.assertPayrollEqual(rules['EE_US_941_FICA_M'], remaining_m_wages * self.FICA_M)
self.assertPayrollEqual(rules['EE_US_941_FICA_M_ADD'], 0.0)
self.assertPayrollEqual(cats['ER_US_940_FUTA'], 0.0)
process_payslip(payslip)
# Make a new payslip, this one will have reached Medicare Additional (employee only)
self._log('2020 tax third payslip:')
payslip = self._createPayslip(employee, '2020-03-01', '2020-03-31')
payslip.compute_sheet()
cats = self._getCategories(payslip)
rules = self._getRules(payslip)
self.assertPayrollEqual(rules['EE_US_941_FICA_M_ADD'], (self.FICA_M_ADD_START_WAGE - (salary * 2)) * self.FICA_M_ADD) # aka 40k
process_payslip(payslip)
# Make a new payslip, this one will have all salary as Medicare Additional
self._log('2020 tax fourth payslip:')
payslip = self._createPayslip(employee, '2020-04-01', '2020-04-30')
payslip.compute_sheet()
cats = self._getCategories(payslip)
rules = self._getRules(payslip)
self.assertPayrollEqual(rules['EE_US_941_FICA_M_ADD'], salary * self.FICA_M_ADD)
process_payslip(payslip)
def test_2020_taxes_with_external(self):
# social security salary
salary = self.FICA_M_ADD_START_WAGE
external_wages = 6000.0
employee = self._createEmployee()
self._createContract(employee, wage=salary, external_wages=external_wages)
self._log('2020 tax first payslip:')
payslip = self._createPayslip(employee, '2020-01-01', '2020-01-31')
payslip.compute_sheet()
cats = self._getCategories(payslip)
rules = self._getRules(payslip)
self.assertPayrollEqual(rules['EE_US_941_FICA_SS'], (self.FICA_SS_MAX_WAGE - external_wages) * self.FICA_SS)
self.assertPayrollEqual(rules['EE_US_941_FICA_M'], salary * self.FICA_M)
self.assertPayrollEqual(rules['EE_US_941_FICA_M_ADD'], external_wages * self.FICA_M_ADD)
self.assertPayrollEqual(rules['ER_US_941_FICA_SS'], rules['EE_US_941_FICA_SS'])
self.assertPayrollEqual(rules['ER_US_941_FICA_M'], rules['EE_US_941_FICA_M'])
self.assertPayrollEqual(cats['ER_US_940_FUTA'], (self.FUTA_MAX_WAGE - external_wages) * self.FUTA)
def test_2020_taxes_with_full_futa(self):
futa_rate = self.FUTA_RATE_BASIC / -100.0
# social security salary
salary = self.FICA_M_ADD_START_WAGE
employee = self._createEmployee()
self._createContract(employee, wage=salary, fed_940_type=USHRContract.FUTA_TYPE_BASIC)
self._log('2020 tax first payslip:')
payslip = self._createPayslip(employee, '2020-01-01', '2020-01-31')
payslip.compute_sheet()
cats = self._getCategories(payslip)
rules = self._getRules(payslip)
self.assertPayrollEqual(rules['EE_US_941_FICA_SS'], self.FICA_SS_MAX_WAGE * self.FICA_SS)
self.assertPayrollEqual(rules['EE_US_941_FICA_M'], salary * self.FICA_M)
self.assertPayrollEqual(rules['EE_US_941_FICA_M_ADD'], 0.0 * self.FICA_M_ADD)
self.assertPayrollEqual(rules['ER_US_941_FICA_SS'], rules['EE_US_941_FICA_SS'])
self.assertPayrollEqual(rules['ER_US_941_FICA_M'], rules['EE_US_941_FICA_M'])
self.assertPayrollEqual(cats['ER_US_940_FUTA'], self.FUTA_MAX_WAGE * futa_rate)
def test_2020_taxes_with_futa_exempt(self):
futa_rate = self.FUTA_RATE_EXEMPT / -100.0 # because of exemption
# social security salary
salary = self.FICA_M_ADD_START_WAGE
employee = self._createEmployee()
self._createContract(employee, wage=salary, fed_940_type=USHRContract.FUTA_TYPE_EXEMPT)
self._log('2020 tax first payslip:')
payslip = self._createPayslip(employee, '2020-01-01', '2020-01-31')
payslip.compute_sheet()
cats = self._getCategories(payslip)
self.assertPayrollEqual(cats['ER_US_940_FUTA'], 0.0)
def test_2020_taxes_with_fica_exempt(self):
salary = 6000.0
schedule_pay = 'bi-weekly'
employee = self._createEmployee()
contract = self._createContract(employee, wage=salary, schedule_pay=schedule_pay)
contract.us_payroll_config_id.fed_941_fica_exempt = True
self._log('2020 tax w4 exempt payslip:')
payslip = self._createPayslip(employee, '2020-01-01', '2020-01-31')
payslip.compute_sheet()
cats = self._getCategories(payslip)
self.assertPayrollEqual(cats['EE_US_941_FICA'], 0.0)
self.assertPayrollEqual(cats['ER_US_941_FICA'], 0.0)
"""
For Federal Income Tax Withholding, we are utilizing the calculations from the new IRS Excel calculator.
Given that you CAN round, we will round to compare even though we will calculate as close to the penny as possible
with the wage * computed_percent method.
"""
def _run_test_fit(self,
wage=0.0,
schedule_pay='monthly',
filing_status='single',
dependent_credit=0.0,
other_income=0.0,
deductions=0.0,
additional_withholding=0.0,
is_nonresident_alien=False,
expected_standard=0.0,
expected_higher=0.0,
):
employee = self._createEmployee()
contract = self._createContract(employee,
wage=wage,
schedule_pay=schedule_pay,
fed_941_fit_w4_is_nonresident_alien=is_nonresident_alien,
fed_941_fit_w4_filing_status=filing_status,
fed_941_fit_w4_multiple_jobs_higher=False,
fed_941_fit_w4_dependent_credit=dependent_credit,
fed_941_fit_w4_other_income=other_income,
fed_941_fit_w4_deductions=deductions,
fed_941_fit_w4_additional_withholding=additional_withholding,
)
payslip = self._createPayslip(employee, '2020-01-01', '2020-01-31')
payslip.compute_sheet()
cats = self._getCategories(payslip)
self.assertPayrollEqual(round(cats.get('EE_US_941_FIT', 0.0)), -expected_standard)
contract.us_payroll_config_id.fed_941_fit_w4_multiple_jobs_higher = True
payslip.compute_sheet()
cats = self._getCategories(payslip)
self.assertPayrollEqual(round(cats.get('EE_US_941_FIT', 0.0)), -expected_higher)
return payslip
def test_2020_fed_income_withholding_single(self):
_ = self._run_test_fit(
wage=6000.0,
schedule_pay='monthly',
filing_status='single',
dependent_credit=100.0,
other_income=200.0,
deductions=300.0,
additional_withholding=400.0,
is_nonresident_alien=False,
expected_standard=1132.0,
expected_higher=1459.0,
)
def test_2020_fed_income_withholding_married_as_single(self):
# This is "Head of Household" though the field name is the same for historical reasons.
_ = self._run_test_fit(
wage=500.0,
schedule_pay='weekly',
filing_status='married_as_single',
dependent_credit=20.0,
other_income=30.0,
deductions=40.0,
additional_withholding=10.0,
is_nonresident_alien=False,
expected_standard=24.0,
expected_higher=45.0,
)
def test_2020_fed_income_withholding_married(self):
_ = self._run_test_fit(
wage=14000.00,
schedule_pay='bi-weekly',
filing_status='married',
dependent_credit=2500.0,
other_income=1200.0,
deductions=1000.0,
additional_withholding=0.0,
is_nonresident_alien=False,
expected_standard=2621.0,
expected_higher=3702.0,
)
def test_2020_fed_income_withholding_nonresident_alien(self):
# Monthly NRA additional wage is 1033.30
# Wage input on IRS Form entered as (3500+1033.30)=4533.30, not 3500.0
_ = self._run_test_fit(
wage=3500.00,
schedule_pay='monthly',
filing_status='married',
dependent_credit=340.0,
other_income=0.0,
deductions=0.0,
additional_withholding=0.0,
is_nonresident_alien=True,
expected_standard=235.0,
expected_higher=391.0,
)
def test_2020_taxes_with_w4_exempt(self):
_ = self._run_test_fit(
wage=3500.00,
schedule_pay='monthly',
filing_status='', # Exempt
dependent_credit=340.0,
other_income=0.0,
deductions=0.0,
additional_withholding=0.0,
is_nonresident_alien=True,
expected_standard=0.0,
expected_higher=0.0,
)

View File

@@ -0,0 +1,100 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
from .common import TestUsPayslip, process_payslip
from odoo.addons.l10n_us_hr_payroll.models.hr_contract import USHRContract
class TestUsTXPayslip(TestUsPayslip):
###
# 2019 Taxes and Rates
###
TX_UNEMP_MAX_WAGE = 9000.0
TX_UNEMP = -2.7 / 100.0
TX_OA = 0.0
TX_ETIA = -0.1 / 100.0
def test_2019_taxes(self):
salary = 5000.0
employee = self._createEmployee()
contract = self._createContract(employee,
wage=salary,
state_id=self.get_us_state('TX'),
)
self._log('2019 Texas tax first payslip:')
payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
payslip.compute_sheet()
cats = self._getCategories(payslip)
rules = self._getRules(payslip)
self.assertPayrollEqual(rules['ER_US_TX_SUTA'], salary * self.TX_UNEMP)
self.assertPayrollEqual(rules['ER_US_TX_SUTA_OA'], salary * self.TX_OA)
self.assertPayrollEqual(rules['ER_US_TX_SUTA_ETIA'], salary * self.TX_ETIA)
process_payslip(payslip)
# Make a new payslip, this one will have maximums
remaining_tx_unemp_wages = self.TX_UNEMP_MAX_WAGE - salary if (self.TX_UNEMP_MAX_WAGE - 2*salary < salary) \
else salary
self._log('2019 Texas tax second payslip:')
payslip = self._createPayslip(employee, '2019-02-01', '2019-02-28')
payslip.compute_sheet()
cats = self._getCategories(payslip)
rules = self._getRules(payslip)
self.assertPayrollEqual(rules['ER_US_TX_SUTA'], remaining_tx_unemp_wages * self.TX_UNEMP)
def test_2019_taxes_with_external(self):
salary = 5000.0
external_wages = 6000.0
employee = self._createEmployee()
contract = self._createContract(employee,
wage=salary,
state_id=self.get_us_state('TX'),
external_wages=external_wages,
)
self._log('2019 Texas_external tax first payslip:')
payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
payslip.compute_sheet()
cats = self._getCategories(payslip)
rules = self._getRules(payslip)
expected_wage = self.TX_UNEMP_MAX_WAGE - external_wages
self.assertPayrollEqual(rules['ER_US_TX_SUTA'], expected_wage * self.TX_UNEMP)
self.assertPayrollEqual(rules['ER_US_TX_SUTA_OA'], expected_wage * self.TX_OA)
self.assertPayrollEqual(rules['ER_US_TX_SUTA_ETIA'], expected_wage * self.TX_ETIA)
def test_2019_taxes_with_state_exempt(self):
salary = 5000.0
external_wages = 6000.0
employee = self._createEmployee()
contract = self._createContract(employee,
wage=salary,
state_id=self.get_us_state('TX'),
external_wages=external_wages,
futa_type=USHRContract.FUTA_TYPE_BASIC)
self._log('2019 Texas_external tax first payslip:')
payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
payslip.compute_sheet()
cats = self._getCategories(payslip)
rules = self._getRules(payslip)
self.assertPayrollEqual(rules.get('ER_US_TX_SUTA', 0.0), 0.0)
self.assertPayrollEqual(rules.get('ER_US_TX_SUTA_OA', 0.0), 0.0)
self.assertPayrollEqual(rules.get('ER_US_TX_SUTA_ETIA', 0.0), 0.0)

View File

@@ -0,0 +1,17 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
from datetime import date
from .common import TestUsPayslip
class TestUsTXPayslip(TestUsPayslip):
###
# 2020 Taxes and Rates
###
TX_UNEMP_MAX_WAGE = 9000.0
TX_UNEMP = 2.7
TX_OA = 0.0
TX_ETIA = 0.1
def test_2020_taxes(self):
combined_rate = self.TX_UNEMP + self.TX_OA + self.TX_ETIA
self._test_er_suta('TX', combined_rate, date(2020, 1, 1), wage_base=self.TX_UNEMP_MAX_WAGE)

View File

@@ -0,0 +1,133 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
from datetime import date
from .common import TestUsPayslip, process_payslip
from odoo.addons.l10n_us_hr_payroll.models.hr_contract import USHRContract
class TestUsVaPayslip(TestUsPayslip):
###
# Taxes and Rates
###
VA_UNEMP_MAX_WAGE = 8000.0
VA_UNEMP = 2.51
VA_SIT_DEDUCTION = 4500.0
VA_SIT_EXEMPTION = 930.0
VA_SIT_OTHER_EXEMPTION = 800.0
def test_2019_taxes(self):
salary = 5000.0
# For formula from https://www.tax.virginia.gov/withholding-calculator
"""
Key
G = Gross Pay for Pay Period P = Pay periods per year
A = Annualized gross pay E1 = Personal and Dependent Exemptions
T = Annualized taxable income E2 = Age 65 and Over & Blind Exemptions
WH = Tax to be withheld for pay period W = Annualized tax to be withheld
G x P - [$3000+ (E1 x 930) + (E2 x 800)] = T
Calculate W as follows:
If T is: W is:
Not over $3,000 2% of T
Over But Not Over Then
$3,000 $5,000 $60 + (3% of excess over $3,000)
$5,000 $17,000 $120 + (5% of excess over $5,000)
$17,000 $720 + (5.75% of excess over $17,000)
W / P = WH
"""
e1 = 2
e2 = 0
t = salary * 12 - (self.VA_SIT_DEDUCTION + (e1 * self.VA_SIT_EXEMPTION) + (e2 * self.VA_SIT_OTHER_EXEMPTION))
if t <= 3000:
w = 0.02 * t
elif t <= 5000:
w = 60 + (0.03 * (t - 3000))
elif t <= 17000:
w = 120 + (0.05 * (t - 5000))
else:
w = 720 + (0.0575 * (t - 17000))
wh = w / 12
employee = self._createEmployee()
contract = self._createContract(employee,
wage=salary,
state_id=self.get_us_state('VA'),
va_va4_sit_exemptions=e1,
va_va4_sit_other_exemptions=e2
)
# tax rates
va_unemp = self.VA_UNEMP / -100.0
self._log('2019 Virginia tax first payslip:')
payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
payslip.compute_sheet()
cats = self._getCategories(payslip)
self.assertPayrollEqual(cats['ER_US_SUTA'], salary * va_unemp)
self.assertPayrollEqual(cats['EE_US_SIT'], -wh)
process_payslip(payslip)
# Make a new payslip, this one will have maximums
remaining_va_unemp_wages = self.VA_UNEMP_MAX_WAGE - salary if (self.VA_UNEMP_MAX_WAGE - 2*salary < salary) \
else salary
self._log('2019 Virginia tax second payslip:')
payslip = self._createPayslip(employee, '2019-02-01', '2019-02-28')
payslip.compute_sheet()
cats = self._getCategories(payslip)
self.assertPayrollEqual(cats['ER_US_SUTA'], remaining_va_unemp_wages * va_unemp)
def test_2019_taxes_with_external(self):
salary = 5000.0
external_wages = 6000.0
employee = self._createEmployee()
contract = self._createContract(employee,
wage=salary,
state_id=self.get_us_state('VA'),
external_wages=external_wages,
)
# tax rates
va_unemp = self.VA_UNEMP / -100.0
self._log('2019 Virginia_external tax first payslip:')
payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
payslip.compute_sheet()
cats = self._getCategories(payslip)
self.assertPayrollEqual(cats['ER_US_SUTA'], (self.VA_UNEMP_MAX_WAGE - external_wages) * va_unemp)
def test_2019_taxes_with_state_exempt(self):
salary = 5000.0
external_wages = 6000.0
employee = self._createEmployee()
contract = self._createContract(employee,
wage=salary,
state_id=self.get_us_state('VA'),
external_wages=external_wages,
futa_type=USHRContract.FUTA_TYPE_BASIC)
# tax rates
self._log('2019 Virginia exempt tax first payslip:')
payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
payslip.compute_sheet()
cats = self._getCategories(payslip)
self.assertPayrollEqual(cats['ER_US_SUTA'], 0.0)

View File

@@ -0,0 +1,116 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
from datetime import date
from .common import TestUsPayslip
class TestUsVaPayslip(TestUsPayslip):
###
# Taxes and Rates
###
VA_UNEMP_MAX_WAGE = 8000.0
VA_UNEMP = 2.51
VA_SIT_DEDUCTION = 4500.0
VA_SIT_EXEMPTION = 930.0
VA_SIT_OTHER_EXEMPTION = 800.0
def _run_test_sit(self,
wage=0.0,
schedule_pay='monthly',
filing_status='single',
dependent_credit=0.0,
other_income=0.0,
deductions=0.0,
additional_withholding=0.0,
is_nonresident_alien=False,
state_income_tax_exempt=False,
state_income_tax_additional_withholding=0.0,
va_va4_sit_exemptions=0,
va_va4_sit_other_exemptions=0,
expected=0.0,
):
employee = self._createEmployee()
contract = self._createContract(employee,
wage=wage,
schedule_pay=schedule_pay,
fed_941_fit_w4_is_nonresident_alien=is_nonresident_alien,
fed_941_fit_w4_filing_status=filing_status,
fed_941_fit_w4_multiple_jobs_higher=False,
fed_941_fit_w4_dependent_credit=dependent_credit,
fed_941_fit_w4_other_income=other_income,
fed_941_fit_w4_deductions=deductions,
fed_941_fit_w4_additional_withholding=additional_withholding,
state_income_tax_exempt=state_income_tax_exempt,
state_income_tax_additional_withholding=state_income_tax_additional_withholding,
va_va4_sit_exemptions=va_va4_sit_exemptions,
va_va4_sit_other_exemptions=va_va4_sit_other_exemptions,
state_id=self.get_us_state('VA'),
)
payslip = self._createPayslip(employee, '2020-01-01', '2020-01-31')
payslip.compute_sheet()
cats = self._getCategories(payslip)
# Instead of PayrollEqual after initial first round of testing.
self.assertPayrollAlmostEqual(cats.get('EE_US_SIT', 0.0), -expected)
return payslip
def test_2020_taxes(self):
self._test_er_suta('VA', self.VA_UNEMP, date(2020, 1, 1), wage_base=self.VA_UNEMP_MAX_WAGE)
salary = 5000.0
# For formula from https://www.tax.virginia.gov/withholding-calculator
e1 = 2
e2 = 0
t = salary * 12 - (self.VA_SIT_DEDUCTION + (e1 * self.VA_SIT_EXEMPTION) + (e2 * self.VA_SIT_OTHER_EXEMPTION))
if t <= 3000:
w = 0.02 * t
elif t <= 5000:
w = 60 + (0.03 * (t - 3000))
elif t <= 17000:
w = 120 + (0.05 * (t - 5000))
else:
w = 720 + (0.0575 * (t - 17000))
wh = w / 12
self._run_test_sit(wage=salary,
schedule_pay='monthly',
state_income_tax_exempt=False,
state_income_tax_additional_withholding=0.0,
va_va4_sit_exemptions=e1,
va_va4_sit_other_exemptions=e2,
expected=wh,)
self.assertPayrollEqual(wh, 235.57) # To test against calculator
# Below expected comes from the calculator linked above
self._run_test_sit(wage=450.0,
schedule_pay='weekly',
state_income_tax_exempt=False,
state_income_tax_additional_withholding=0.0,
va_va4_sit_exemptions=3,
va_va4_sit_other_exemptions=1,
expected=12.22,)
self._run_test_sit(wage=2500.0,
schedule_pay='bi-weekly',
state_income_tax_exempt=False,
state_income_tax_additional_withholding=0.0,
va_va4_sit_exemptions=1,
va_va4_sit_other_exemptions=0,
expected=121.84,)
self._run_test_sit(wage=10000.0,
schedule_pay='semi-monthly',
state_income_tax_exempt=False,
state_income_tax_additional_withholding=100.0,
va_va4_sit_exemptions=0,
va_va4_sit_other_exemptions=1,
expected=651.57,)
# Test exempt
self._run_test_sit(wage=2400.0,
schedule_pay='monthly',
state_income_tax_exempt=True,
state_income_tax_additional_withholding=0.0,
va_va4_sit_exemptions=1,
va_va4_sit_other_exemptions=1,
expected=0.0,)

View File

@@ -0,0 +1,88 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
from datetime import date
from .common import TestUsPayslip, process_payslip
class TestUsWAPayslip(TestUsPayslip):
###
# Taxes and Rates
###
WA_UNEMP_MAX_WAGE = 49800.0
WA_UNEMP_RATE = 1.18
WA_FML_RATE = 0.4
WA_FML_RATE_EE = 66.33
WA_FML_RATE_ER = 33.67
def setUp(self):
super(TestUsWAPayslip, self).setUp()
# self.lni = self.env['hr.contract.lni.wa'].create({
# 'name': '5302 Computer Consulting',
# 'rate': 0.1261,
# 'rate_emp_withhold': 0.05575,
# })
self.test_ee_lni = 0.05575 # per 100 hours
self.test_er_lni = 0.1261 # per 100 hours
self.parameter_lni_ee = self.env['hr.payroll.rate'].create({
'name': 'Test LNI EE',
'code': 'test_lni_ee',
'date_from': date(2019, 1, 1),
'parameter_value': str(self.test_ee_lni * 100),
})
self.parameter_lni_er = self.env['hr.payroll.rate'].create({
'name': 'Test LNI ER',
'code': 'test_lni_er',
'date_from': date(2019, 1, 1),
'parameter_value': str(self.test_er_lni * 100),
})
def test_2019_taxes(self):
salary = 25000.0
employee = self._createEmployee()
contract = self._createContract(employee,
wage=salary,
state_id=self.get_us_state('WA'),
workers_comp_ee_code=self.parameter_lni_ee.code,
workers_comp_er_code=self.parameter_lni_er.code,
)
self._log(str(contract.resource_calendar_id) + ' ' + contract.resource_calendar_id.name)
# tax rates
wa_unemp = self.WA_UNEMP_RATE / -100.0
self._log('2019 Washington tax first payslip:')
payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
hours_in_period = payslip.worked_days_line_ids.filtered(lambda l: l.code == 'WORK100').number_of_hours
self.assertEqual(hours_in_period, 184) # only asserted to test algorithm
payslip.compute_sheet()
cats = self._getCategories(payslip)
rules = self._getRules(payslip)
self.assertPayrollEqual(cats['ER_US_SUTA'], salary * wa_unemp)
self.assertPayrollEqual(rules['EE_US_WA_LNI'], -(self.test_ee_lni * hours_in_period))
self.assertPayrollEqual(rules['ER_US_WA_LNI'], -(self.test_er_lni * hours_in_period) - rules['EE_US_WA_LNI'])
# Both of these are known to be within 1 penny
self.assertPayrollAlmostEqual(rules['EE_US_WA_FML'], -(salary * (self.WA_FML_RATE / 100.0) * (self.WA_FML_RATE_EE / 100.0)))
self.assertPayrollAlmostEqual(rules['ER_US_WA_FML'], -(salary * (self.WA_FML_RATE / 100.0) * (self.WA_FML_RATE_ER / 100.0)))
# FML
process_payslip(payslip)
# Make a new payslip, this one will have maximums
remaining_wa_unemp_wages = self.WA_UNEMP_MAX_WAGE - salary if (self.WA_UNEMP_MAX_WAGE - 2*salary < salary) \
else salary
self._log('2019 Washington tax second payslip:')
payslip = self._createPayslip(employee, '2019-02-01', '2019-02-28')
payslip.compute_sheet()
cats = self._getCategories(payslip)
self.assertPayrollEqual(cats['ER_US_SUTA'], remaining_wa_unemp_wages * wa_unemp)

View File

@@ -0,0 +1,86 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
from datetime import date
from .common import TestUsPayslip, process_payslip
class TestUsWAPayslip(TestUsPayslip):
###
# Taxes and Rates
###
WA_UNEMP_MAX_WAGE = 52700.00
WA_UNEMP_RATE = 1.0
WA_FML_MAX_WAGE = 137700.00
WA_FML_RATE = 0.4
WA_FML_RATE_EE = 66.33
WA_FML_RATE_ER = 33.67
def setUp(self):
super(TestUsWAPayslip, self).setUp()
# self.lni = self.env['hr.contract.lni.wa'].create({
# 'name': '5302 Computer Consulting',
# 'rate': 0.1261,
# 'rate_emp_withhold': 0.05575,
# })
self.test_ee_lni = 0.05575 # per 100 hours
self.test_er_lni = 0.1261 # per 100 hours
self.parameter_lni_ee = self.env['hr.payroll.rate'].create({
'name': 'Test LNI EE',
'code': 'test_lni_ee',
'date_from': date(2019, 1, 1),
'parameter_value': str(self.test_ee_lni * 100),
})
self.parameter_lni_er = self.env['hr.payroll.rate'].create({
'name': 'Test LNI ER',
'code': 'test_lni_er',
'date_from': date(2019, 1, 1),
'parameter_value': str(self.test_er_lni * 100),
})
def test_2020_taxes(self):
self._test_er_suta('WA', self.WA_UNEMP_RATE, date(2020, 1, 1), wage_base=self.WA_UNEMP_MAX_WAGE)
salary = (self.WA_FML_MAX_WAGE / 2.0) + 1000.0
employee = self._createEmployee()
contract = self._createContract(employee,
wage=salary,
state_id=self.get_us_state('WA'),
workers_comp_ee_code=self.parameter_lni_ee.code,
workers_comp_er_code=self.parameter_lni_er.code,
)
self._log(str(contract.resource_calendar_id) + ' ' + contract.resource_calendar_id.name)
# Non SUTA
self._log('2020 Washington tax first payslip:')
payslip = self._createPayslip(employee, '2020-01-01', '2020-01-31')
hours_in_period = payslip.worked_days_line_ids.filtered(lambda l: l.code == 'WORK100').number_of_hours
self.assertEqual(hours_in_period, 184) # only asserted to test algorithm
payslip.compute_sheet()
rules = self._getRules(payslip)
self.assertPayrollEqual(rules['EE_US_WA_LNI'], -(self.test_ee_lni * hours_in_period))
self.assertPayrollEqual(rules['ER_US_WA_LNI'], -(self.test_er_lni * hours_in_period) - rules['EE_US_WA_LNI'])
# Both of these are known to be within 1 penny
self.assertPayrollAlmostEqual(rules['EE_US_WA_FML'], -(salary * (self.WA_FML_RATE / 100.0) * (self.WA_FML_RATE_EE / 100.0)))
self.assertPayrollAlmostEqual(rules['ER_US_WA_FML'], -(salary * (self.WA_FML_RATE / 100.0) * (self.WA_FML_RATE_ER / 100.0)))
process_payslip(payslip)
# Second payslip
remaining_wage = self.WA_FML_MAX_WAGE - salary
payslip = self._createPayslip(employee, '2020-03-01', '2020-03-31')
payslip.compute_sheet()
rules = self._getRules(payslip)
self.assertPayrollAlmostEqual(rules['EE_US_WA_FML'], -(remaining_wage * (self.WA_FML_RATE / 100.0) * (self.WA_FML_RATE_EE / 100.0)))
self.assertPayrollAlmostEqual(rules['ER_US_WA_FML'], -(remaining_wage * (self.WA_FML_RATE / 100.0) * (self.WA_FML_RATE_ER / 100.0)))
process_payslip(payslip)
# Third payslip
payslip = self._createPayslip(employee, '2020-04-01', '2020-04-30')
payslip.compute_sheet()
rules = self._getRules(payslip)
self.assertPayrollAlmostEqual(rules['EE_US_WA_FML'], 0.0)
self.assertPayrollAlmostEqual(rules['ER_US_WA_FML'], 0.0)

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="hr_contract_view_form_inherit_l10n_us" model="ir.ui.view">
<field name="name">hr.contract.form.inherit</field>
<field name="model">hr.contract</field>
<field name="inherit_id" ref="hr_payroll.hr_contract_form_inherit"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='struct_id']" position="after">
<field name="us_payroll_config_id"
domain="[('employee_id', '=', employee_id)]"
attrs="{'invisible': [('struct_id', '!=', %(l10n_us_hr_payroll.structure_type_employee)s)],
'required': [('struct_id', '=', %(l10n_us_hr_payroll.structure_type_employee)s)]}"
context="{'default_employee_id': employee_id}"/>
</xpath>
</field>
</record>
</odoo>

View File

@@ -1,35 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="hr_contract_form_l10n_us_inherit" model="ir.ui.view">
<field name="name">hr.contract.form.inherit</field>
<field name="model">hr.contract</field>
<field name="priority">20</field>
<field name="inherit_id" ref="hr_contract.hr_contract_view_form"/>
<field name="arch" type="xml">
<data>
<xpath expr="//page[@name='other']" position="before">
<page string="US Federal Filing">
<group>
<group string="W4" name="w4">
<field name="w4_filing_status" string="Filing Status"/>
<field name="w4_allowances" string="Allowances"/>
<field name="w4_additional_withholding" string="Additional Withholding"/>
<field name="w4_is_nonresident_alien" string="Non-resident Alien"/>
</group>
<group string="Other" name="other">
<field name="external_wages" string="External YTD Wages"/>
<field name="futa_type" string="Unemployment Tax Type (FUTA)"/>
<field name="fica_exempt"/>
</group>
</group>
</page>
<page string="US State Filing">
<group name="state_filing"/>
</page>
</xpath>
</data>
</field>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,126 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="us_payroll_config_tree" model="ir.ui.view">
<field name="name">hr.contract.us_payroll_config.tree</field>
<field name="model">hr.contract.us_payroll_config</field>
<field name="arch" type="xml">
<tree string="Employee Payroll Forms">
<field name="employee_id"/>
<field name="name"/>
<field name="state_id"/>
<field name="create_date"/>
<field name="write_date"/>
</tree>
</field>
</record>
<record id="us_payroll_config_form" model="ir.ui.view">
<field name="name">hr.contract.us_payroll_config.form</field>
<field name="model">hr.contract.us_payroll_config</field>
<field name="arch" type="xml">
<form string="Employee Payroll Forms">
<sheet>
<group name="General">
<field name="employee_id"/>
<field name="name"/>
</group>
<group>
<group name="federal" string="Federal">
<p colspan="2"><h3>Form 940 - Federal Unemployment</h3></p>
<field name="fed_940_type" string="Federal Unemployment Rate"/>
<p colspan="2"><h3>Form 941 / W4 - Federal Income Tax</h3></p>
<field name="fed_941_fica_exempt" string="FICA Exempt"/>
<field name="fed_941_fit_w4_filing_status" string="Filing Status"/>
<field name="fed_941_fit_w4_allowances" string="Allowances (Old W4)"/>
<field name="fed_941_fit_w4_is_nonresident_alien" string="Is Nonresident Alien"/>
<field name="fed_941_fit_w4_multiple_jobs_higher" string="Multiple Jobs Checked"/>
<field name="fed_941_fit_w4_dependent_credit" string="Dependent Credit"/>
<field name="fed_941_fit_w4_other_income" string="Other Income"/>
<field name="fed_941_fit_w4_deductions" string="Deductions"/>
<field name="fed_941_fit_w4_additional_withholding" string="Additional Withholding"/>
<p colspan="2"><h3>State Information and Extra</h3></p>
<field name="state_id" domain="[('country_id', '=', %(base.us)s)]" options="{'no_create': True}"/>
<field name="workers_comp_ee_code"/>
<field name="workers_comp_er_code"/>
</group>
<group name="state_fl_florida" string="FL Florida" attrs="{'invisible':[('state_id', '!=', %(base.state_us_10)s)]}">
<p colspan="2"><h3>No additional fields.</h3></p>
</group>
<group name="state_ga_georgia" string="GA Georgia" attrs="{'invisible':[('state_id', '!=', %(base.state_us_11)s)]}">
<p colspan="2"><h3>Form G-4 - State Income Tax</h3></p>
<field name="ga_g4_sit_filing_status" string="Marital Status 3."/>
<field name="ga_g4_sit_dependent_allowances" string="Dependent Allowances 4."/>
<field name="ga_g4_sit_additional_allowances" string="Additional Allowances 5."/>
<field name="state_income_tax_additional_withholding" string="Additional Withholding 6."/>
<field name="state_income_tax_exempt" string="Exempt 8."/>
</group>
<group name="state_ms_mississippi" string="MS Mississippi" attrs="{'invisible':[('state_id', '!=', %(base.state_us_37)s)]}">
<p colspan="2"><h3>Form 89-350 - State Income Tax</h3></p>
<field name="ms_89_350_sit_filing_status" string="Marital Status 1. 2. 3. 8."/>
<field name="ms_89_350_sit_exemption_value" string="Exemptions (Total) 6."/>
<field name="state_income_tax_additional_withholding" string="Additional Withholding 7."/>
</group>
<group name="state_mt_montana" string="MT Montana" attrs="{'invisible':[('state_id', '!=', %(base.state_us_21)s)]}">
<p colspan="2"><h3>Form MT-4 - State Income Tax</h3></p>
<field name="mt_mw4_sit_exempt" string="Exempt"/>
<field name="mt_mw4_sit_exemptions" string="Exemptions"/>
<field name="state_income_tax_additional_withholding" string="Additional Withholding (Box H)"/>
</group>
<group name="state_oh_ohio" string="OH Ohio" attrs="{'invisible':[('state_id', '!=', %(base.state_us_30)s)]}">
<p colspan="2"><h3>Form IT-4 - State Income Tax</h3></p>
<field name="state_income_tax_exempt"/>
<field name="oh_it4_sit_exemptions" string="Exemptions"/>
<field name="state_income_tax_additional_withholding" string="Additional Withholding (Line 5)"/>
</group>
<group name="state_pa_pennsylvania" string="PA Pennsylvania" attrs="{'invisible':[('state_id', '!=', %(base.state_us_39)s)]}">
<field name="state_income_tax_exempt"/>
<field name="state_income_tax_additional_withholding"/>
</group>
<group name="state_tx_texas" string="TX Texas" attrs="{'invisible':[('state_id', '!=', %(base.state_us_44)s)]}">
<p colspan="2"><h3>No additional fields.</h3></p>
</group>
<group name="state_va_virginia" string="VA Virginia" attrs="{'invisible':[('state_id', '!=', %(base.state_us_47)s)]}">
<p colspan="2"><h3>Form VA-4/VA-4P - State Income Tax</h3></p>
<field name="va_va4_sit_exemptions" string="Personal Exemptions (Line 1(a))"/>
<field name="va_va4_sit_other_exemptions" string="Age &amp; Blindness Exemptions (Line 1(b))"/>
<field name="state_income_tax_additional_withholding" string="Additional Withholding (Line 2)"/>
<field name="state_income_tax_exempt" string="Exempt (Line 3 or 4)"/>
</group>
<group name="state_wa_washington" string="WA Washington" attrs="{'invisible':[('state_id', '!=', %(base.state_us_48)s)]}">
<p colspan="2"><h3>No additional fields.</h3></p>
<p colspan="2">Ensure that your Employee and Employer workers' comp code fields are filled in for WA LNI withholding.</p>
</group>
</group>
</sheet>
</form>
</field>
</record>
<record id="us_payroll_config_search" model="ir.ui.view">
<field name="name">hr.contract.us_payroll_config.search</field>
<field name="model">hr.contract.us_payroll_config</field>
<field name="arch" type="xml">
<search string="Employee Payroll Forms Search">
<field name="employee_id"/>
<field name="name"/>
<field name="state_id"/>
</search>
</field>
</record>
<record id="us_payroll_config_action_main" model="ir.actions.act_window">
<field name="name">Employee Payroll Forms</field>
<field name="res_model">hr.contract.us_payroll_config</field>
<field name="view_mode">tree,form</field>
<field name="help" type="html">
<p>
No Forms
</p>
</field>
</record>
<menuitem id="us_payroll_config_menu_main" name="Payroll Forms"
action="us_payroll_config_action_main"
sequence="10" parent="hr.menu_hr_root"/>
</odoo>

View File

@@ -25,5 +25,5 @@ USA::Iowa Payroll Rules.
'data/rules.xml',
'data/final.xml',
],
'installable': True
'installable': False
}

View File

@@ -22,5 +22,5 @@ USA::Kansas Payroll Rules.
'data/rules_2018.xml',
'data/final.xml',
],
'installable': True
'installable': False
}

View File

@@ -24,5 +24,5 @@ USA::Michigan Payroll Rules.
'data/rules.xml',
'data/final.xml',
],
'installable': True
'installable': False
}

View File

@@ -24,5 +24,5 @@ USA - Minnesota Payroll Rules
'data/rules.xml',
'data/final.xml',
],
'installable': True
'installable': False
}

View File

@@ -25,5 +25,5 @@ USA::Missouri Payroll Rules.
'data/rules.xml',
'data/final.xml',
],
'installable': True
'installable': False
}

View File

@@ -24,5 +24,5 @@ USA::Mississippi Payroll Rules.
'data/rules.xml',
'data/final.xml',
],
'installable': True
'installable': False
}

View File

@@ -24,5 +24,5 @@ USA::Montana Payroll Rules.
'data/rules.xml',
'data/final.xml',
],
'installable': True
'installable': False
}

View File

@@ -24,5 +24,5 @@ USA::North Carolina Payroll Rules.
'data/rules.xml',
'data/final.xml',
],
'installable': True
'installable': False
}

View File

@@ -28,5 +28,5 @@ USA::New Jersey Payroll Rules.
'data/rules.xml',
'data/final.xml',
],
'installable': True
'installable': False
}

View File

@@ -25,5 +25,5 @@ USA::New York Payroll Rules.
'data/rules_2018.xml',
'data/final.xml',
],
'installable': True
'installable': False
}

View File

@@ -26,5 +26,5 @@ USA::Ohio Payroll Rules.
'data/rules.xml',
'data/final.xml',
],
'installable': True
'installable': False
}

View File

@@ -25,5 +25,5 @@ USA::Pennsylvania Payroll Rules.
'data/rules.xml',
'data/final.xml',
],
'installable': True
'installable': False
}

View File

@@ -23,5 +23,5 @@ USA::South Carolina Payroll Rules.
'data/rules.xml',
'data/final.xml',
],
'installable': True
'installable': False
}

View File

@@ -26,5 +26,5 @@ USA::Texas Payroll Rules.
'data/rules.xml',
'data/final.xml',
],
'installable': True
'installable': False
}

View File

@@ -24,5 +24,5 @@ USA::Virginia Payroll Rules.
'data/rules_2018.xml',
'data/final.xml',
],
'installable': True
'installable': False
}

View File

@@ -24,5 +24,5 @@ USA::Washington Payroll Rules.
'data/rules_2018.xml',
'data/final.xml',
],
'installable': True
'installable': False
}