mirror of
https://gitlab.com/hibou-io/hibou-odoo/suite.git
synced 2025-01-20 12:37:31 +02:00
NEW l10n_us_hr_payroll Initiall commit for Odoo 13 (rewrite) and 2020 Federal Rates (including new W4)
This commit is contained in:
3
l10n_us_hr_payroll/__init__.py
Normal file
3
l10n_us_hr_payroll/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
|
||||
|
||||
from . import models
|
||||
35
l10n_us_hr_payroll/__manifest__.py
Normal file
35
l10n_us_hr_payroll/__manifest__.py
Normal file
@@ -0,0 +1,35 @@
|
||||
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
|
||||
|
||||
{
|
||||
'name': 'United States of America - Payroll',
|
||||
'author': 'Hibou Corp. <hello@hibou.io>',
|
||||
'version': '13.0.2020.0.0',
|
||||
'category': 'Payroll Localization',
|
||||
'depends': [
|
||||
'hr_payroll',
|
||||
'hr_contract_reports',
|
||||
],
|
||||
'description': """
|
||||
United States of America - Payroll Rules.
|
||||
=========================================
|
||||
|
||||
""",
|
||||
|
||||
'data': [
|
||||
'security/ir.model.access.csv',
|
||||
'data/base.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',
|
||||
'views/hr_contract_views.xml',
|
||||
'views/us_payroll_config_views.xml',
|
||||
],
|
||||
'demo': [
|
||||
],
|
||||
'auto_install': False,
|
||||
'license': 'OPL-1',
|
||||
}
|
||||
20
l10n_us_hr_payroll/data/base.xml
Normal file
20
l10n_us_hr_payroll/data/base.xml
Normal file
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
|
||||
<record id="structure_type_employee" model="hr.payroll.structure.type">
|
||||
<field name="name">USA Employee</field>
|
||||
<field name="default_resource_calendar_id" ref="resource.resource_calendar_std"/>
|
||||
<field name="country_id" ref="base.us"/>
|
||||
</record>
|
||||
|
||||
<record id="hr_payroll_structure" model="hr.payroll.structure">
|
||||
<field name="name">USA Employee Standard</field>
|
||||
<field name="country_id" ref="base.us"/>
|
||||
<field name="type_id" ref="l10n_us_hr_payroll.structure_type_employee"/>
|
||||
<field name="regular_pay" eval="True"/>
|
||||
<field name="unpaid_work_entry_type_ids" eval="[
|
||||
(4, ref('hr_payroll.work_entry_type_unpaid_leave')),
|
||||
]"/>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
38
l10n_us_hr_payroll/data/federal/fed_940_futa_parameters.xml
Normal file
38
l10n_us_hr_payroll/data/federal/fed_940_futa_parameters.xml
Normal file
@@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
<!-- Wage Base -->
|
||||
<record id="rule_parameter_940_futa_wage_base" model="hr.rule.parameter">
|
||||
<field name="name">Federal 940 FUTA Wage Base</field>
|
||||
<field name="code">fed_940_futa_wage_base</field>
|
||||
<field name="country_id" ref="base.us"/>
|
||||
</record>
|
||||
<record id="rule_parameter_940_futa_wage_base_2016" model="hr.rule.parameter.value">
|
||||
<field name="parameter_value">7000.00</field>
|
||||
<field name="rule_parameter_id" ref="rule_parameter_940_futa_wage_base"/>
|
||||
<field name="date_from" eval="datetime(2016, 1, 1).date()"/>
|
||||
</record>
|
||||
|
||||
<!-- Rate -->
|
||||
<record id="rule_parameter_940_futa_rate_basic" model="hr.rule.parameter">
|
||||
<field name="name">Federal 940 FUTA Rate Basic</field>
|
||||
<field name="code">fed_940_futa_rate_basic</field>
|
||||
<field name="country_id" ref="base.us"/>
|
||||
</record>
|
||||
<record id="rule_parameter_940_futa_rate_basic_2016" model="hr.rule.parameter.value">
|
||||
<field name="parameter_value">6.0</field>
|
||||
<field name="rule_parameter_id" ref="rule_parameter_940_futa_rate_basic"/>
|
||||
<field name="date_from" eval="datetime(2016, 1, 1).date()"/>
|
||||
</record>
|
||||
|
||||
<record id="rule_parameter_940_futa_rate_normal" model="hr.rule.parameter">
|
||||
<field name="name">Federal 940 FUTA Rate Normal</field>
|
||||
<field name="code">fed_940_futa_rate_normal</field>
|
||||
<field name="country_id" ref="base.us"/>
|
||||
</record>
|
||||
<record id="rule_parameter_940_futa_rate_normal_2016" model="hr.rule.parameter.value">
|
||||
<field name="parameter_value">0.6</field>
|
||||
<field name="rule_parameter_id" ref="rule_parameter_940_futa_rate_normal"/>
|
||||
<field name="date_from" eval="datetime(2016, 1, 1).date()"/>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
34
l10n_us_hr_payroll/data/federal/fed_940_futa_rules.xml
Normal file
34
l10n_us_hr_payroll/data/federal/fed_940_futa_rules.xml
Normal file
@@ -0,0 +1,34 @@
|
||||
<?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="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>
|
||||
|
||||
<!-- Category to increase when reducing wage for Unemployment Insurance/Tax -->
|
||||
<record id="hr_payroll_category_wage_fed_940_futa_exempt" model="hr.salary.rule.category">
|
||||
<field name="name">WAGE: Federal 940 FUTA Exempt</field>
|
||||
<field name="code">WAGE_US_940_FUTA_EXEMPT</field>
|
||||
</record>
|
||||
|
||||
<record id="hr_payroll_rule_er_fed_940" model="hr.salary.rule">
|
||||
<field name="sequence" eval="440"/>
|
||||
<field name="struct_id" ref="hr_payroll_structure"/>
|
||||
<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="partner_id" ref="res_partner_eftps_940"/>
|
||||
<field name="appears_on_payslip" eval="False"/>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
88
l10n_us_hr_payroll/data/federal/fed_941_fica_parameters.xml
Normal file
88
l10n_us_hr_payroll/data/federal/fed_941_fica_parameters.xml
Normal file
@@ -0,0 +1,88 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
<!-- Social Security -->
|
||||
<!-- Wage Base -->
|
||||
<record id="rule_parameter_941_fica_ss_wage_base" model="hr.rule.parameter">
|
||||
<field name="name">Federal 941 FICA Social Security Wage Base</field>
|
||||
<field name="code">fed_941_fica_ss_wage_base</field>
|
||||
<field name="country_id" ref="base.us"/>
|
||||
</record>
|
||||
<record id="rule_parameter_941_fica_ss_wage_base_2016" model="hr.rule.parameter.value">
|
||||
<field name="parameter_value">128400.0</field>
|
||||
<field name="rule_parameter_id" ref="rule_parameter_941_fica_ss_wage_base"/>
|
||||
<field name="date_from" eval="datetime(2016, 1, 1).date()"/>
|
||||
</record>
|
||||
<record id="rule_parameter_941_fica_ss_wage_base_2019" model="hr.rule.parameter.value">
|
||||
<field name="parameter_value">132900.0</field>
|
||||
<field name="rule_parameter_id" ref="rule_parameter_941_fica_ss_wage_base"/>
|
||||
<field name="date_from" eval="datetime(2019, 1, 1).date()"/>
|
||||
</record>
|
||||
<record id="rule_parameter_941_fica_ss_wage_base_2020" model="hr.rule.parameter.value">
|
||||
<field name="parameter_value">137700.0</field>
|
||||
<field name="rule_parameter_id" ref="rule_parameter_941_fica_ss_wage_base"/>
|
||||
<field name="date_from" eval="datetime(2020, 1, 1).date()"/>
|
||||
</record>
|
||||
|
||||
<!-- Rate -->
|
||||
<record id="rule_parameter_941_fica_ss_rate" model="hr.rule.parameter">
|
||||
<field name="name">Federal 941 FICA Rate</field>
|
||||
<field name="code">fed_941_fica_ss_rate</field>
|
||||
<field name="country_id" ref="base.us"/>
|
||||
</record>
|
||||
<record id="rule_parameter_941_fica_ss_rate_2016" model="hr.rule.parameter.value">
|
||||
<field name="parameter_value">6.2</field>
|
||||
<field name="rule_parameter_id" ref="rule_parameter_941_fica_ss_rate"/>
|
||||
<field name="date_from" eval="datetime(2016, 1, 1).date()"/>
|
||||
</record>
|
||||
|
||||
<!-- Medicare -->
|
||||
<!-- Wage Base -->
|
||||
<record id="rule_parameter_941_fica_m_wage_base" model="hr.rule.parameter">
|
||||
<field name="name">Federal 941 FICA Medicare Wage Base</field>
|
||||
<field name="code">fed_941_fica_m_wage_base</field>
|
||||
<field name="country_id" ref="base.us"/>
|
||||
</record>
|
||||
<record id="rule_parameter_941_fica_m_wage_base_2016" model="hr.rule.parameter.value">
|
||||
<field name="parameter_value">"inf"</field>
|
||||
<field name="rule_parameter_id" ref="rule_parameter_941_fica_m_wage_base"/>
|
||||
<field name="date_from" eval="datetime(2016, 1, 1).date()"/>
|
||||
</record>
|
||||
|
||||
<!-- Rate -->
|
||||
<record id="rule_parameter_941_fica_m_rate" model="hr.rule.parameter">
|
||||
<field name="name">Federal 941 FICA Rate</field>
|
||||
<field name="code">fed_941_fica_m_rate</field>
|
||||
<field name="country_id" ref="base.us"/>
|
||||
</record>
|
||||
<record id="rule_parameter_941_fica_m_rate_2016" model="hr.rule.parameter.value">
|
||||
<field name="parameter_value">1.45</field>
|
||||
<field name="rule_parameter_id" ref="rule_parameter_941_fica_m_rate"/>
|
||||
<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" model="hr.rule.parameter">
|
||||
<field name="name">Federal 941 FICA Medicare Additional Wage Start</field>
|
||||
<field name="code">fed_941_fica_m_add_wage_start</field>
|
||||
<field name="country_id" ref="base.us"/>
|
||||
</record>
|
||||
<record id="rule_parameter_941_fica_m_add_wage_start_2016" model="hr.rule.parameter.value">
|
||||
<field name="parameter_value">200000.0</field>
|
||||
<field name="rule_parameter_id" ref="rule_parameter_941_fica_m_add_wage_start"/>
|
||||
<field name="date_from" eval="datetime(2016, 1, 1).date()"/>
|
||||
</record>
|
||||
|
||||
<!-- Rate -->
|
||||
<record id="rule_parameter_941_fica_m_add_rate" model="hr.rule.parameter">
|
||||
<field name="name">Federal 941 FICA Medicare Additional Rate</field>
|
||||
<field name="code">fed_941_fica_m_add_rate</field>
|
||||
<field name="country_id" ref="base.us"/>
|
||||
</record>
|
||||
<record id="rule_parameter_941_fica_m_add_rate_2016" model="hr.rule.parameter.value">
|
||||
<field name="parameter_value">0.9</field>
|
||||
<field name="rule_parameter_id" ref="rule_parameter_941_fica_m_add_rate"/>
|
||||
<field name="date_from" eval="datetime(2016, 1, 1).date()"/>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
100
l10n_us_hr_payroll/data/federal/fed_941_fica_rules.xml
Normal file
100
l10n_us_hr_payroll/data/federal/fed_941_fica_rules.xml
Normal file
@@ -0,0 +1,100 @@
|
||||
<?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="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>
|
||||
|
||||
<!-- Category to increase when reducing wage for FICA -->
|
||||
<record id="hr_payroll_category_wage_fed_941_fica_exempt" model="hr.salary.rule.category">
|
||||
<field name="name">WAGE: Federal 941 FICA Exempt</field>
|
||||
<field name="code">WAGE_US_941_FICA_EXEMPT</field>
|
||||
</record>
|
||||
|
||||
|
||||
<!-- Social Security -->
|
||||
<record id="hr_payroll_rule_ee_fed_941_ss" model="hr.salary.rule">
|
||||
<field name="sequence" eval="190"/>
|
||||
<field name="struct_id" ref="hr_payroll_structure"/>
|
||||
<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="partner_id" ref="res_partner_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="struct_id" ref="hr_payroll_structure"/>
|
||||
<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="partner_id" ref="res_partner_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="struct_id" ref="hr_payroll_structure"/>
|
||||
<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="partner_id" ref="res_partner_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="struct_id" ref="hr_payroll_structure"/>
|
||||
<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="partner_id" ref="res_partner_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="struct_id" ref="hr_payroll_structure"/>
|
||||
<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="partner_id" ref="res_partner_eftps_941"/>
|
||||
<field name="appears_on_payslip" eval="True"/>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
504
l10n_us_hr_payroll/data/federal/fed_941_fit_parameters.xml
Normal file
504
l10n_us_hr_payroll/data/federal/fed_941_fit_parameters.xml
Normal file
@@ -0,0 +1,504 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
|
||||
<!-- Shared -->
|
||||
<record id="rule_parameter_941_fit_allowance" model="hr.rule.parameter">
|
||||
<field name="name">Federal 941 FIT Allowance</field>
|
||||
<field name="code">fed_941_fit_allowance</field>
|
||||
<field name="country_id" ref="base.us"/>
|
||||
</record>
|
||||
<record id="rule_parameter_941_fit_allowance_2018" model="hr.rule.parameter.value">
|
||||
<!-- 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="rule_parameter_id" ref="rule_parameter_941_fit_allowance"/>
|
||||
<field name="date_from" eval="datetime(2018, 1, 1).date()"/>
|
||||
</record>
|
||||
<record id="rule_parameter_941_fit_allowance_2019" model="hr.rule.parameter.value">
|
||||
<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="rule_parameter_id" ref="rule_parameter_941_fit_allowance"/>
|
||||
<field name="date_from" eval="datetime(2019, 1, 1).date()"/>
|
||||
</record>
|
||||
<record id="rule_parameter_941_fit_allowance_2020" model="hr.rule.parameter.value">
|
||||
<!-- Warning, major change to allowance in 2020 -->
|
||||
<field name="parameter_value">4300.0</field>
|
||||
<field name="rule_parameter_id" ref="rule_parameter_941_fit_allowance"/>
|
||||
<field name="date_from" eval="datetime(2020, 1, 1).date()"/>
|
||||
</record>
|
||||
|
||||
<record id="rule_parameter_941_fit_nra_additional" model="hr.rule.parameter">
|
||||
<field name="name">Federal 941 FIT NRA Additional</field>
|
||||
<field name="code">fed_941_fit_nra_additional</field>
|
||||
<field name="country_id" ref="base.us"/>
|
||||
</record>
|
||||
<record id="rule_parameter_941_fit_nra_additional_2018" model="hr.rule.parameter.value">
|
||||
<!-- 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="rule_parameter_id" ref="rule_parameter_941_fit_nra_additional"/>
|
||||
<field name="date_from" eval="datetime(2018, 1, 1).date()"/>
|
||||
</record>
|
||||
<record id="rule_parameter_941_fit_nra_additional_2019" model="hr.rule.parameter.value">
|
||||
<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="rule_parameter_id" ref="rule_parameter_941_fit_nra_additional"/>
|
||||
<field name="date_from" eval="datetime(2019, 1, 1).date()"/>
|
||||
</record>
|
||||
<record id="rule_parameter_941_fit_nra_additional_2020" model="hr.rule.parameter.value">
|
||||
<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="rule_parameter_id" ref="rule_parameter_941_fit_nra_additional"/>
|
||||
<field name="date_from" eval="datetime(2020, 1, 1).date()"/>
|
||||
</record>
|
||||
|
||||
<!-- Single and Married Single Rate -->
|
||||
<record id="rule_parameter_941_fit_table_single" model="hr.rule.parameter">
|
||||
<field name="name">Federal 941 FIT Table Single</field>
|
||||
<field name="code">fed_941_fit_table_single</field>
|
||||
<field name="country_id" ref="base.us"/>
|
||||
</record>
|
||||
<record id="rule_parameter_941_fit_table_single_2018" model="hr.rule.parameter.value">
|
||||
<!-- 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="rule_parameter_id" ref="rule_parameter_941_fit_table_single"/>
|
||||
<field name="date_from" eval="datetime(2018, 1, 1).date()"/>
|
||||
</record>
|
||||
<record id="rule_parameter_941_fit_table_single_2019" model="hr.rule.parameter.value">
|
||||
<!-- 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="rule_parameter_id" ref="rule_parameter_941_fit_table_single"/>
|
||||
<field name="date_from" eval="datetime(2019, 1, 1).date()"/>
|
||||
</record>
|
||||
<record id="rule_parameter_941_fit_table_single_2020" model="hr.rule.parameter.value">
|
||||
<!-- 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="rule_parameter_id" ref="rule_parameter_941_fit_table_single"/>
|
||||
<field name="date_from" eval="datetime(2020, 1, 1).date()"/>
|
||||
</record>
|
||||
|
||||
<!-- Married -->
|
||||
<record id="rule_parameter_941_fit_table_married" model="hr.rule.parameter">
|
||||
<field name="name">Federal 941 FIT Table Married</field>
|
||||
<field name="code">fed_941_fit_table_married</field>
|
||||
<field name="country_id" ref="base.us"/>
|
||||
</record>
|
||||
<record id="rule_parameter_941_fit_table_married_2018" model="hr.rule.parameter.value">
|
||||
<!-- 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="rule_parameter_id" ref="rule_parameter_941_fit_table_married"/>
|
||||
<field name="date_from" eval="datetime(2018, 1, 1).date()"/>
|
||||
</record>
|
||||
<record id="rule_parameter_941_fit_table_married_2019" model="hr.rule.parameter.value">
|
||||
<!-- 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="rule_parameter_id" ref="rule_parameter_941_fit_table_married"/>
|
||||
<field name="date_from" eval="datetime(2019, 1, 1).date()"/>
|
||||
</record>
|
||||
<record id="rule_parameter_941_fit_table_married_2020" model="hr.rule.parameter.value">
|
||||
<!-- 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="rule_parameter_id" ref="rule_parameter_941_fit_table_married"/>
|
||||
<field name="date_from" eval="datetime(2020, 1, 1).date()"/>
|
||||
</record>
|
||||
|
||||
<record id="rule_parameter_941_fit_table_hh" model="hr.rule.parameter">
|
||||
<field name="name">Federal 941 FIT Table Head of Household</field>
|
||||
<field name="code">fed_941_fit_table_hh</field>
|
||||
<field name="country_id" ref="base.us"/>
|
||||
</record>
|
||||
<record id="rule_parameter_941_fit_table_hh_2020" model="hr.rule.parameter.value">
|
||||
<!-- 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="rule_parameter_id" ref="rule_parameter_941_fit_table_hh"/>
|
||||
<field name="date_from" eval="datetime(2020, 1, 1).date()"/>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
29
l10n_us_hr_payroll/data/federal/fed_941_fit_rules.xml
Normal file
29
l10n_us_hr_payroll/data/federal/fed_941_fit_rules.xml
Normal file
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
<!-- Category to increase when reducing wage for Federal Income Tax (e.g. 401k) -->
|
||||
<record id="hr_payroll_category_wage_fed_941_fit_exempt" model="hr.salary.rule.category">
|
||||
<field name="name">WAGE: Federal 941 Income Tax Exempt</field>
|
||||
<field name="code">WAGE_US_941_FIT_EXEMPT</field>
|
||||
</record>
|
||||
|
||||
<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="struct_id" ref="hr_payroll_structure"/>
|
||||
<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="partner_id" ref="res_partner_eftps_941"/>
|
||||
<field name="appears_on_payslip" eval="True"/>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
29
l10n_us_hr_payroll/data/integration_rules.xml
Normal file
29
l10n_us_hr_payroll/data/integration_rules.xml
Normal file
@@ -0,0 +1,29 @@
|
||||
<?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"/>
|
||||
<field name="struct_id" ref="l10n_us_hr_payroll.hr_payroll_structure"/>
|
||||
</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"/>
|
||||
<field name="struct_id" ref="l10n_us_hr_payroll.hr_payroll_structure"/>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
5
l10n_us_hr_payroll/models/__init__.py
Normal file
5
l10n_us_hr_payroll/models/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
|
||||
|
||||
from . import hr_contract
|
||||
from . import hr_payslip
|
||||
from . import us_payroll_config
|
||||
1
l10n_us_hr_payroll/models/federal/__init__.py
Normal file
1
l10n_us_hr_payroll/models/federal/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
|
||||
37
l10n_us_hr_payroll/models/federal/fed_940.py
Normal file
37
l10n_us_hr_payroll/models/federal/fed_940.py
Normal 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 - WAGE_US_940_FUTA_EXEMPT
|
||||
:return: result, result_rate (wage, percent)
|
||||
"""
|
||||
|
||||
# Determine Rate.
|
||||
if payslip.contract_id.futa_type == payslip.contract_id.FUTA_TYPE_EXEMPT:
|
||||
# Exit early
|
||||
return 0.0, 0.0
|
||||
elif payslip.contract_id.futa_type == payslip.contract_id.FUTA_TYPE_BASIC:
|
||||
result_rate = -payslip.rule_parameter('fed_940_futa_rate_basic')
|
||||
else:
|
||||
result_rate = -payslip.rule_parameter('fed_940_futa_rate_normal')
|
||||
|
||||
# Determine Wage
|
||||
year = payslip.dict.get_year()
|
||||
ytd_wage = payslip.sum_category('GROSS', str(year) + '-01-01', str(year+1) + '-01-01')
|
||||
ytd_wage -= payslip.sum_category('WAGE_US_940_FUTA_EXEMPT', str(year) + '-01-01', str(year+1) + '-01-01')
|
||||
ytd_wage += payslip.contract_id.external_wages
|
||||
|
||||
wage_base = payslip.rule_parameter('fed_940_futa_wage_base')
|
||||
remaining = wage_base - ytd_wage
|
||||
|
||||
wage = categories.GROSS - categories.WAGE_US_940_FUTA_EXEMPT
|
||||
|
||||
if remaining < 0.0:
|
||||
result = 0.0
|
||||
elif remaining < wage:
|
||||
result = remaining
|
||||
else:
|
||||
result = wage
|
||||
|
||||
return result, result_rate
|
||||
239
l10n_us_hr_payroll/models/federal/fed_941.py
Normal file
239
l10n_us_hr_payroll/models/federal/fed_941.py
Normal 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 - WAGE_US_941_FICA_EXEMPT
|
||||
:return: result, result_rate (wage, percent)
|
||||
"""
|
||||
exempt = payslip.contract_id.us_payroll_config_value('fed_941_fica_exempt')
|
||||
if exempt:
|
||||
return 0.0, 0.0
|
||||
|
||||
# Determine Rate.
|
||||
result_rate = -payslip.rule_parameter('fed_941_fica_ss_rate')
|
||||
|
||||
# Determine Wage
|
||||
year = payslip.dict.get_year()
|
||||
ytd_wage = payslip.sum_category('GROSS', str(year) + '-01-01', str(year+1) + '-01-01')
|
||||
ytd_wage -= payslip.sum_category('WAGE_US_941_FICA_EXEMPT', str(year) + '-01-01', str(year+1) + '-01-01')
|
||||
ytd_wage += payslip.contract_id.external_wages
|
||||
|
||||
wage_base = payslip.rule_parameter('fed_941_fica_ss_wage_base')
|
||||
remaining = wage_base - ytd_wage
|
||||
|
||||
wage = categories.GROSS - categories.WAGE_US_941_FICA_EXEMPT
|
||||
|
||||
if remaining < 0.0:
|
||||
result = 0.0
|
||||
elif remaining < wage:
|
||||
result = remaining
|
||||
else:
|
||||
result = wage
|
||||
|
||||
return result, result_rate
|
||||
|
||||
|
||||
er_us_941_fica_ss = ee_us_941_fica_ss
|
||||
|
||||
|
||||
def ee_us_941_fica_m(payslip, categories, worked_days, inputs):
|
||||
"""
|
||||
Returns FICA Medicare eligible wage and rate.
|
||||
WAGE = GROSS - WAGE_US_941_FICA_EXEMPT
|
||||
:return: result, result_rate (wage, percent)
|
||||
"""
|
||||
exempt = payslip.contract_id.us_payroll_config_value('fed_941_fica_exempt')
|
||||
if exempt:
|
||||
return 0.0, 0.0
|
||||
|
||||
# Determine Rate.
|
||||
result_rate = -payslip.rule_parameter('fed_941_fica_m_rate')
|
||||
|
||||
# Determine Wage
|
||||
year = payslip.dict.get_year()
|
||||
ytd_wage = payslip.sum_category('GROSS', str(year) + '-01-01', str(year+1) + '-01-01')
|
||||
ytd_wage -= payslip.sum_category('WAGE_US_941_FICA_EXEMPT', str(year) + '-01-01', str(year+1) + '-01-01')
|
||||
ytd_wage += payslip.contract_id.external_wages
|
||||
|
||||
wage_base = float(payslip.rule_parameter('fed_941_fica_m_wage_base')) # inf
|
||||
remaining = wage_base - ytd_wage
|
||||
|
||||
wage = categories.GROSS - categories.WAGE_US_941_FICA_EXEMPT
|
||||
|
||||
if remaining < 0.0:
|
||||
result = 0.0
|
||||
elif remaining < wage:
|
||||
result = remaining
|
||||
else:
|
||||
result = wage
|
||||
|
||||
return result, result_rate
|
||||
|
||||
|
||||
er_us_941_fica_m = ee_us_941_fica_m
|
||||
|
||||
|
||||
def ee_us_941_fica_m_add(payslip, categories, worked_days, inputs):
|
||||
"""
|
||||
Returns FICA Medicare Additional eligible wage and rate.
|
||||
Note that this wage is not capped like the above rules.
|
||||
WAGE = GROSS - WAGE_FICA_EXEMPT
|
||||
:return: result, result_rate (wage, percent)
|
||||
"""
|
||||
exempt = payslip.contract_id.us_payroll_config_value('fed_941_fica_exempt')
|
||||
if exempt:
|
||||
return 0.0, 0.0
|
||||
|
||||
# Determine Rate.
|
||||
result_rate = -payslip.rule_parameter('fed_941_fica_m_add_rate')
|
||||
|
||||
# Determine Wage
|
||||
year = payslip.dict.get_year()
|
||||
ytd_wage = payslip.sum_category('GROSS', str(year) + '-01-01', str(year+1) + '-01-01')
|
||||
ytd_wage -= payslip.sum_category('WAGE_US_941_FICA_EXEMPT', str(year) + '-01-01', str(year+1) + '-01-01')
|
||||
ytd_wage += payslip.contract_id.external_wages
|
||||
|
||||
wage_start = payslip.rule_parameter('fed_941_fica_m_add_wage_start')
|
||||
existing_wage = ytd_wage - wage_start
|
||||
|
||||
wage = categories.GROSS - categories.WAGE_US_941_FICA_EXEMPT
|
||||
|
||||
if existing_wage >= 0.0:
|
||||
result = wage
|
||||
elif wage + existing_wage > 0.0:
|
||||
result = wage + existing_wage
|
||||
else:
|
||||
result = 0.0
|
||||
|
||||
return result, result_rate
|
||||
|
||||
|
||||
# Federal Income Tax
|
||||
def ee_us_941_fit(payslip, categories, worked_days, inputs):
|
||||
"""
|
||||
Returns Wage and rate that is computed given the amount to withhold.
|
||||
WAGE = GROSS - WAGE_US_941_FIT_EXEMPT
|
||||
:return: result, result_rate (wage, percent)
|
||||
"""
|
||||
filing_status = payslip.contract_id.us_payroll_config_value('fed_941_fit_w4_filing_status')
|
||||
if not filing_status:
|
||||
return 0.0, 0.0
|
||||
|
||||
schedule_pay = payslip.contract_id.schedule_pay
|
||||
wage = categories.GROSS - categories.WAGE_US_941_FIT_EXEMPT
|
||||
#_logger.warn('initial gross wage: ' + str(wage))
|
||||
year = payslip.dict.get_year()
|
||||
if year >= 2020:
|
||||
# Large changes in Federal Income Tax in 2020 and the W4
|
||||
# We will assume that your W4 is the 2020 version
|
||||
# Steps are from IRS Publication 15-T
|
||||
#
|
||||
# Step 1
|
||||
working_wage = wage
|
||||
is_nra = payslip.contract_id.us_payroll_config_value('fed_941_fit_w4_is_nonresident_alien')
|
||||
if is_nra:
|
||||
nra_table = payslip.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.contract_id.us_payroll_config_value('fed_941_fit_w4_other_income')
|
||||
#_logger.warn(' after other income: ' + str(wage_annual))
|
||||
|
||||
deductions = payslip.contract_id.us_payroll_config_value('fed_941_fit_w4_deductions')
|
||||
#_logger.warn('deductions from W4: ' + str(deductions))
|
||||
|
||||
higher_rate_type = payslip.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.rule_parameter('fed_941_fit_table_single')
|
||||
elif filing_status == 'married':
|
||||
tax_tables = payslip.rule_parameter('fed_941_fit_table_married')
|
||||
else:
|
||||
# married_as_single for historic reasons
|
||||
tax_tables = payslip.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.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.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.contract_id.us_payroll_config_value('fed_941_fit_w4_is_nonresident_alien')
|
||||
if is_nra:
|
||||
nra_table = payslip.rule_parameter('fed_941_fit_nra_additional')
|
||||
working_wage += nra_table[schedule_pay]
|
||||
|
||||
allowance_table = payslip.rule_parameter('fed_941_fit_allowance')
|
||||
allowances = payslip.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.rule_parameter('fed_941_fit_table_married')
|
||||
else:
|
||||
tax_table = payslip.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.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)
|
||||
28
l10n_us_hr_payroll/models/hr_contract.py
Normal file
28
l10n_us_hr_payroll/models/hr_contract.py
Normal file
@@ -0,0 +1,28 @@
|
||||
# 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 HrPayrollStructure(models.Model):
|
||||
_inherit = 'hr.payroll.structure'
|
||||
schedule_pay = fields.Selection(selection_add=[('semi-monthly', 'Semi-monthly')])
|
||||
|
||||
|
||||
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
|
||||
|
||||
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]
|
||||
48
l10n_us_hr_payroll/models/hr_payslip.py
Normal file
48
l10n_us_hr_payroll/models/hr_payslip.py
Normal file
@@ -0,0 +1,48 @@
|
||||
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models
|
||||
|
||||
from .federal.fed_940 import er_us_940_futa
|
||||
from .federal.fed_941 import ee_us_941_fica_ss, \
|
||||
ee_us_941_fica_m, \
|
||||
ee_us_941_fica_m_add,\
|
||||
er_us_941_fica_ss, \
|
||||
er_us_941_fica_m, \
|
||||
ee_us_941_fit
|
||||
|
||||
|
||||
class HRPayslip(models.Model):
|
||||
_inherit = 'hr.payslip'
|
||||
|
||||
# From IRS Publication 15-T or logically (annually, bi-monthly)
|
||||
PAY_PERIODS_IN_YEAR = {
|
||||
'annually': 1,
|
||||
'semi-annually': 2,
|
||||
'quarterly': 4,
|
||||
'bi-monthly': 6,
|
||||
'monthly': 12,
|
||||
'semi-monthly': 24,
|
||||
'bi-weekly': 26,
|
||||
'weekly': 52,
|
||||
'daily': 260,
|
||||
}
|
||||
|
||||
def _get_base_local_dict(self):
|
||||
res = super()._get_base_local_dict()
|
||||
res.update({
|
||||
'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,
|
||||
})
|
||||
return res
|
||||
|
||||
def get_year(self):
|
||||
# Helper method to get the year (normalized between Odoo Versions)
|
||||
return self.date_to.year
|
||||
|
||||
def get_pay_periods_in_year(self):
|
||||
return self.PAY_PERIODS_IN_YEAR.get(self.contract_id.schedule_pay, 0)
|
||||
45
l10n_us_hr_payroll/models/us_payroll_config.py
Normal file
45
l10n_us_hr_payroll/models/us_payroll_config.py
Normal file
@@ -0,0 +1,45 @@
|
||||
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models
|
||||
|
||||
FUTA_TYPE_EXEMPT = 'exempt'
|
||||
FUTA_TYPE_BASIC = 'basic'
|
||||
FUTA_TYPE_NORMAL = 'normal'
|
||||
|
||||
|
||||
class HRContractUSPayrollConfig(models.Model):
|
||||
_name = 'hr.contract.us_payroll_config'
|
||||
_description = 'Contract US Payroll Forms'
|
||||
|
||||
name = fields.Char(string="Description")
|
||||
employee_id = fields.Many2one('hr.employee', string="Employee", required=True)
|
||||
state_id = fields.Many2one('res.country.state', string="Applied State")
|
||||
|
||||
fed_940_type = fields.Selection([
|
||||
(FUTA_TYPE_EXEMPT, 'Exempt (0%)'),
|
||||
(FUTA_TYPE_NORMAL, 'Normal Net Rate (0.6%)'),
|
||||
(FUTA_TYPE_BASIC, 'Basic Rate (6%)'),
|
||||
], string="Federal Unemployment Tax Type (FUTA)", default='normal')
|
||||
|
||||
fed_941_fica_exempt = fields.Boolean(string='FICA Exempt', help="Exempt from Social Security and "
|
||||
"Medicare e.g. F1 Student Visa")
|
||||
|
||||
fed_941_fit_w4_filing_status = fields.Selection([
|
||||
('', 'Exempt'),
|
||||
('single', 'Single or Married filing separately'),
|
||||
('married', 'Married filing jointly'),
|
||||
('married_as_single', 'Head of Household'),
|
||||
], string='Federal W4 Filing Status [1(c)]', default='single')
|
||||
fed_941_fit_w4_allowances = fields.Integer(string='Federal W4 Allowances (before 2020)')
|
||||
fed_941_fit_w4_is_nonresident_alien = fields.Boolean(string='Federal W4 Is Nonresident Alien')
|
||||
fed_941_fit_w4_multiple_jobs_higher = fields.Boolean(string='Federal W4 Multiple Jobs Higher [2(c)]',
|
||||
help='Form W4 (2020+) 2(c) Checkbox. '
|
||||
'Uses Higher Withholding tables.')
|
||||
fed_941_fit_w4_dependent_credit = fields.Float(string='Federal W4 Dependent Credit [3]',
|
||||
help='Form W4 (2020+) Line 3')
|
||||
fed_941_fit_w4_other_income = fields.Float(string='Federal W4 Other Income [4(a)]',
|
||||
help='Form W4 (2020+) 4(a)')
|
||||
fed_941_fit_w4_deductions = fields.Float(string='Federal W4 Deductions [4(b)]',
|
||||
help='Form W4 (2020+) 4(b)')
|
||||
fed_941_fit_w4_additional_withholding = fields.Float(string='Federal W4 Additional Withholding [4(c)]',
|
||||
help='Form W4 (2020+) 4(c)')
|
||||
2
l10n_us_hr_payroll/security/ir.model.access.csv
Normal file
2
l10n_us_hr_payroll/security/ir.model.access.csv
Normal 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
|
||||
|
BIN
l10n_us_hr_payroll/static/description/icon.png
Normal file
BIN
l10n_us_hr_payroll/static/description/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.6 KiB |
5
l10n_us_hr_payroll/tests/__init__.py
Executable file
5
l10n_us_hr_payroll/tests/__init__.py
Executable file
@@ -0,0 +1,5 @@
|
||||
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
|
||||
|
||||
from . import common
|
||||
from . import test_us_payslip_2019
|
||||
from . import test_us_payslip_2020
|
||||
150
l10n_us_hr_payroll/tests/common.py
Executable file
150
l10n_us_hr_payroll/tests/common.py
Executable file
@@ -0,0 +1,150 @@
|
||||
# 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 odoo.tests import common
|
||||
from odoo.tools.float_utils import float_round as odoo_float_round
|
||||
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
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('structure_type_id'):
|
||||
contract_values['structure_type_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)
|
||||
|
||||
# Compatibility with Odoo 13
|
||||
contract.structure_type_id.default_struct_id.schedule_pay = schedule_pay
|
||||
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
|
||||
})
|
||||
slip._onchange_employee()
|
||||
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 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()
|
||||
338
l10n_us_hr_payroll/tests/test_us_payslip_2019.py
Normal file
338
l10n_us_hr_payroll/tests/test_us_payslip_2019.py
Normal file
@@ -0,0 +1,338 @@
|
||||
# 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 TestUsPayslip2019(TestUsPayslip):
|
||||
# FUTA Constants
|
||||
FUTA_RATE_NORMAL = 0.6
|
||||
FUTA_RATE_BASIC = 6.0
|
||||
FUTA_RATE_EXEMPT = 0.0
|
||||
|
||||
# Wage caps
|
||||
FICA_SS_MAX_WAGE = 132900.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
|
||||
|
||||
###
|
||||
# 2019 Taxes and Rates
|
||||
###
|
||||
|
||||
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()
|
||||
|
||||
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)
|
||||
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('2019 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['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(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('2019 tax fourth payslip:')
|
||||
payslip = self._createPayslip(employee, '2019-04-01', '2019-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_2019_fed_income_withholding_single(self):
|
||||
self.debug = False
|
||||
|
||||
salary = 6000.00
|
||||
schedule_pay = 'monthly'
|
||||
w4_allowances = 3
|
||||
w4_allowance_amt = 350.00 * 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(-(378.52 + ((adjusted_salary - 3606) * 0.22)), self.payroll_digits)
|
||||
|
||||
employee = self._createEmployee()
|
||||
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_941_FIT'], expected_withholding)
|
||||
|
||||
def test_2019_fed_income_withholding_married_as_single(self):
|
||||
salary = 500.00
|
||||
schedule_pay = 'weekly'
|
||||
w4_allowances = 1
|
||||
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
|
||||
###
|
||||
expected_withholding = self.float_round(-(18.70 + ((adjusted_salary - 260) * 0.12)), self.payroll_digits)
|
||||
|
||||
employee = self._createEmployee()
|
||||
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_941_FIT'], expected_withholding)
|
||||
|
||||
def test_2019_fed_income_withholding_married(self):
|
||||
salary = 14000.00
|
||||
schedule_pay = 'bi-weekly'
|
||||
w4_allowances = 2
|
||||
w4_allowance_amt = 161.50 * 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(-(2519.06 + ((adjusted_salary - 12817) * 0.32)), self.payroll_digits)
|
||||
|
||||
employee = self._createEmployee()
|
||||
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)
|
||||
# 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, 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)
|
||||
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):
|
||||
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('2019 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['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, 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['ER_US_940_FUTA'], 0.0)
|
||||
|
||||
def test_2019_fed_income_withholding_nonresident_alien(self):
|
||||
salary = 3500.00
|
||||
schedule_pay = 'quarterly'
|
||||
w4_allowances = 1
|
||||
w4_allowance_amt = 1050.0 * w4_allowances
|
||||
nra_adjustment = 2000.0 # for quarterly
|
||||
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()
|
||||
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()
|
||||
rules = self._getRules(payslip)
|
||||
self.assertPayrollEqual(rules['EE_US_941_FIT'], expected_withholding)
|
||||
|
||||
def test_2019_fed_income_additional_withholding(self):
|
||||
salary = 50000.00
|
||||
schedule_pay = 'annually'
|
||||
w4_additional_withholding = 5000.0
|
||||
w4_allowances = 2
|
||||
w4_allowance_amt = 4200.0 * w4_allowances
|
||||
adjusted_salary = salary - w4_allowance_amt # 41700
|
||||
|
||||
###
|
||||
# Single ANNUAL form Publication 15
|
||||
expected_withholding = \
|
||||
self.float_round(-((1940.00 + ((adjusted_salary - 31200) * 0.12)) + w4_additional_withholding),
|
||||
self.payroll_digits)
|
||||
|
||||
employee = self._createEmployee()
|
||||
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()
|
||||
|
||||
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()
|
||||
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()
|
||||
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'
|
||||
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('2019 tax w4 exempt payslip:')
|
||||
payslip = self._createPayslip(employee, '2019-01-01', '2019-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)
|
||||
302
l10n_us_hr_payroll/tests/test_us_payslip_2020.py
Normal file
302
l10n_us_hr_payroll/tests/test_us_payslip_2020.py
Normal 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,
|
||||
)
|
||||
19
l10n_us_hr_payroll/views/hr_contract_views.xml
Normal file
19
l10n_us_hr_payroll/views/hr_contract_views.xml
Normal 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='structure_type_id']" position="after">
|
||||
<field name="us_payroll_config_id"
|
||||
domain="[('employee_id', '=', employee_id)]"
|
||||
attrs="{'invisible': [('structure_type_id', '!=', %(l10n_us_hr_payroll.structure_type_employee)s)],
|
||||
'required': [('structure_type_id', '=', %(l10n_us_hr_payroll.structure_type_employee)s)]}"
|
||||
context="{'default_employee_id': employee_id}"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
75
l10n_us_hr_payroll/views/us_payroll_config_views.xml
Normal file
75
l10n_us_hr_payroll/views/us_payroll_config_views.xml
Normal file
@@ -0,0 +1,75 @@
|
||||
<?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">
|
||||
<field name="state_id" domain="[('country_id', '=', %(base.us)s)]" options="{'no_create': True}"/>
|
||||
<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"/>
|
||||
</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="50" parent="hr_payroll.menu_hr_payroll_employees_root"/>
|
||||
</odoo>
|
||||
Reference in New Issue
Block a user