mirror of
https://gitlab.com/hibou-io/hibou-odoo/suite.git
synced 2025-01-20 12:37:31 +02:00
Adding all from 11.0 https://github.com/hibou-io/odoo-hr-payroll
hr_payroll_holidays hr_payroll_input_name_report hr_payroll_input_report hr_payroll_payment hr_payroll_timesheet hr_payslip_line_date l10n_us_fl_hr_payroll l10n_us_hr_payroll l10n_us_mo_hr_payroll l10n_us_oh_hr_payroll l10n_us_va_hr_payroll
This commit is contained in:
3
hr_payroll_holidays/__init__.py
Executable file
3
hr_payroll_holidays/__init__.py
Executable file
@@ -0,0 +1,3 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from . import hr_payslip
|
||||||
17
hr_payroll_holidays/__manifest__.py
Executable file
17
hr_payroll_holidays/__manifest__.py
Executable file
@@ -0,0 +1,17 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
{
|
||||||
|
'name': 'Payroll Holidays',
|
||||||
|
'author': 'Hibou Corp. <hello@hibou.io>',
|
||||||
|
'version': '11.0.0.0.0',
|
||||||
|
'category': 'Human Resources',
|
||||||
|
'sequence': 95,
|
||||||
|
'summary': 'Register payments for Payroll Payslips',
|
||||||
|
'description': """
|
||||||
|
Simplifies getting approved Holiday Leaves onto an employee Payslip.
|
||||||
|
""",
|
||||||
|
'website': 'https://hibou.io/',
|
||||||
|
'depends': ['hr_payroll', 'hr_holidays'],
|
||||||
|
'installable': True,
|
||||||
|
'application': False,
|
||||||
|
}
|
||||||
53
hr_payroll_holidays/hr_payslip.py
Executable file
53
hr_payroll_holidays/hr_payslip.py
Executable file
@@ -0,0 +1,53 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from odoo import models, api
|
||||||
|
from odoo.addons.hr_holidays.models.hr_holidays import HOURS_PER_DAY
|
||||||
|
|
||||||
|
|
||||||
|
class HrPayslip(models.Model):
|
||||||
|
_inherit = 'hr.payslip'
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def get_worked_day_lines(self, contracts, date_from, date_to):
|
||||||
|
leaves = {}
|
||||||
|
|
||||||
|
for contract in contracts:
|
||||||
|
for leave in self._fetch_valid_leaves(contract.employee_id.id, date_from, date_to):
|
||||||
|
leave_code = self._create_leave_code(leave.holiday_status_id.name)
|
||||||
|
if leave_code in leaves:
|
||||||
|
leaves[leave_code]['number_of_days'] += leave.number_of_days_temp
|
||||||
|
leaves[leave_code]['number_of_hours'] += leave.number_of_days_temp * HOURS_PER_DAY
|
||||||
|
else:
|
||||||
|
leaves[leave_code] = {
|
||||||
|
'name': leave.holiday_status_id.name,
|
||||||
|
'sequence': 15,
|
||||||
|
'code': leave_code,
|
||||||
|
'number_of_days': leave.number_of_days_temp,
|
||||||
|
'number_of_hours': leave.number_of_days_temp * HOURS_PER_DAY,
|
||||||
|
'contract_id': contract.id,
|
||||||
|
}
|
||||||
|
|
||||||
|
res = super(HrPayslip, self).get_worked_day_lines(contracts, date_from, date_to)
|
||||||
|
res.extend(leaves.values())
|
||||||
|
return res
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def action_payslip_done(self):
|
||||||
|
for slip in self:
|
||||||
|
leaves = self._fetch_valid_leaves(slip.employee_id.id, slip.date_from, slip.date_to)
|
||||||
|
leaves.write({'payslip_status': True})
|
||||||
|
return super(HrPayslip, self).action_payslip_done()
|
||||||
|
|
||||||
|
def _fetch_valid_leaves(self, employee_id, date_from, date_to):
|
||||||
|
valid_leaves = [
|
||||||
|
('employee_id', '=', employee_id),
|
||||||
|
('state', '=', 'validate'),
|
||||||
|
('date_from', '>=', date_from),
|
||||||
|
('date_to', '<=', date_to),
|
||||||
|
('payslip_status', '=', False),
|
||||||
|
('type', '=', 'remove'),
|
||||||
|
]
|
||||||
|
|
||||||
|
return self.env['hr.holidays'].search(valid_leaves)
|
||||||
|
|
||||||
|
def _create_leave_code(self, name):
|
||||||
|
return 'L_' + name.replace(' ', '_')
|
||||||
0
hr_payroll_input_name_report/__init__.py
Normal file
0
hr_payroll_input_name_report/__init__.py
Normal file
19
hr_payroll_input_name_report/__manifest__.py
Normal file
19
hr_payroll_input_name_report/__manifest__.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
{
|
||||||
|
'name': 'Payroll Input Name Report',
|
||||||
|
'author': 'Hibou Corp. <hello@hibou.io>',
|
||||||
|
'version': '11.0.0.0.0',
|
||||||
|
'category': 'Human Resources',
|
||||||
|
'sequence': 95,
|
||||||
|
'summary': 'Improves slip reports by using your own Input\'s description',
|
||||||
|
'description': """
|
||||||
|
If a Salary Rule\'s Code is identical to an Input's Code, then the input's description
|
||||||
|
will appear on the payslip report where the rule is displayed.
|
||||||
|
""",
|
||||||
|
'website': 'https://hibou.io/',
|
||||||
|
'depends': ['hr_payroll'],
|
||||||
|
'data': ['hr_payroll_input_name_report.xml'],
|
||||||
|
'installable': True,
|
||||||
|
'application': False,
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<record id="report_payslip" model="ir.ui.view">
|
||||||
|
<field name="name">hr_payroll_input_name_report.report_payslip</field>
|
||||||
|
<field name="inherit_id" ref="hr_payroll.report_payslip"/>
|
||||||
|
<field name="priority" eval="2"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//tbody[last()]/tr/td[2]" position="replace">
|
||||||
|
<td t-if="o.input_line_ids.filtered(lambda i: i.code == line.code)"><span t-field="o.input_line_ids.filtered(lambda i: i.code == line.code)[0].name"/></td>
|
||||||
|
<td t-if="not o.input_line_ids.filtered(lambda i: i.code == line.code)"><span t-field="line.name"/></td>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="report_payslipdetails" model="ir.ui.view">
|
||||||
|
<field name="name">hr_payroll_input_name_report.report_payslipdetails</field>
|
||||||
|
<field name="inherit_id" ref="hr_payroll.report_payslipdetails"/>
|
||||||
|
<field name="priority" eval="2"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//tbody[last()]/tr/td[2]" position="replace">
|
||||||
|
<td t-if="o.input_line_ids.filtered(lambda i: i.code == line.code)"><span t-field="o.input_line_ids.filtered(lambda i: i.code == line.code)[0].name"/></td>
|
||||||
|
<td t-if="not o.input_line_ids.filtered(lambda i: i.code == line.code)"><span t-field="line.name"/></td>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</odoo>
|
||||||
0
hr_payroll_input_report/__init__.py
Normal file
0
hr_payroll_input_report/__init__.py
Normal file
18
hr_payroll_input_report/__manifest__.py
Normal file
18
hr_payroll_input_report/__manifest__.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
{
|
||||||
|
'name': 'Payroll Input Report',
|
||||||
|
'author': 'Hibou Corp. <hello@hibou.io>',
|
||||||
|
'version': '11.0.0.0.0',
|
||||||
|
'category': 'Human Resources',
|
||||||
|
'sequence': 95,
|
||||||
|
'summary': 'Adds "Worked Days" and "Other Inputs" to the payslip reports.',
|
||||||
|
'description': """
|
||||||
|
Adds "Worked Days" and "Other Inputs" to the payslip reports.
|
||||||
|
""",
|
||||||
|
'website': 'https://hibou.io/',
|
||||||
|
'depends': ['hr_payroll'],
|
||||||
|
'data': ['hr_payroll_input_report.xml'],
|
||||||
|
'installable': True,
|
||||||
|
'application': False,
|
||||||
|
}
|
||||||
96
hr_payroll_input_report/hr_payroll_input_report.xml
Normal file
96
hr_payroll_input_report/hr_payroll_input_report.xml
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<record id="report_payslip" model="ir.ui.view">
|
||||||
|
<field name="name">hr_payroll_input_report.report_payslip</field>
|
||||||
|
<field name="inherit_id" ref="hr_payroll.report_payslip"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//table" position="after">
|
||||||
|
<h3 t-if="o.worked_days_line_ids">Worked Days</h3>
|
||||||
|
<table t-if="o.worked_days_line_ids" class="table table-condensed">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Code</th>
|
||||||
|
<th>Days</th>
|
||||||
|
<th>Hours</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr t-foreach="o.worked_days_line_ids" t-as="line">
|
||||||
|
<td><span t-field="line.name" /></td>
|
||||||
|
<td><span t-field="line.code" /></td>
|
||||||
|
<td><span t-field="line.number_of_days" /></td>
|
||||||
|
<td><span t-field="line.number_of_hours" /></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h3 t-if="o.input_line_ids">Other Inputs</h3>
|
||||||
|
<table t-if="o.input_line_ids" class="table table-condensed">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Code</th>
|
||||||
|
<th>Amount</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr t-foreach="o.input_line_ids" t-as="line">
|
||||||
|
<td><span t-field="line.name" /></td>
|
||||||
|
<td><span t-field="line.code" /></td>
|
||||||
|
<td><span t-field="line.amount" /></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="report_payslipdetails" model="ir.ui.view">
|
||||||
|
<field name="name">hr_payroll_input_report.report_payslipdetails</field>
|
||||||
|
<field name="inherit_id" ref="hr_payroll.report_payslipdetails"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//table" position="after">
|
||||||
|
<h3 t-if="o.worked_days_line_ids">Worked Days</h3>
|
||||||
|
<table t-if="o.worked_days_line_ids" class="table table-condensed">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Code</th>
|
||||||
|
<th>Days</th>
|
||||||
|
<th>Hours</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr t-foreach="o.worked_days_line_ids" t-as="line">
|
||||||
|
<td><span t-field="line.name" /></td>
|
||||||
|
<td><span t-field="line.code" /></td>
|
||||||
|
<td><span t-field="line.number_of_days" /></td>
|
||||||
|
<td><span t-field="line.number_of_hours" /></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h3 t-if="o.input_line_ids">Other Inputs</h3>
|
||||||
|
<table t-if="o.input_line_ids" class="table table-condensed">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Code</th>
|
||||||
|
<th>Amount</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr t-foreach="o.input_line_ids" t-as="line">
|
||||||
|
<td><span t-field="line.name" /></td>
|
||||||
|
<td><span t-field="line.code" /></td>
|
||||||
|
<td><span t-field="line.amount" /></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</odoo>
|
||||||
3
hr_payroll_payment/__init__.py
Executable file
3
hr_payroll_payment/__init__.py
Executable file
@@ -0,0 +1,3 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from . import hr_payroll_register_payment
|
||||||
20
hr_payroll_payment/__manifest__.py
Executable file
20
hr_payroll_payment/__manifest__.py
Executable file
@@ -0,0 +1,20 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
{
|
||||||
|
'name': 'Payroll Payments',
|
||||||
|
'author': 'Hibou Corp. <hello@hibou.io>',
|
||||||
|
'version': '11.0.0.0.0',
|
||||||
|
'category': 'Human Resources',
|
||||||
|
'sequence': 95,
|
||||||
|
'summary': 'Register payments for Payroll Payslips',
|
||||||
|
'description': """
|
||||||
|
Adds the ability to register a payment on a payslip.
|
||||||
|
""",
|
||||||
|
'website': 'https://hibou.io/',
|
||||||
|
'depends': ['hr_payroll_account'],
|
||||||
|
'data': [
|
||||||
|
'hr_payroll_register_payment.xml',
|
||||||
|
],
|
||||||
|
'installable': True,
|
||||||
|
'application': False,
|
||||||
|
}
|
||||||
124
hr_payroll_payment/hr_payroll_register_payment.py
Executable file
124
hr_payroll_payment/hr_payroll_register_payment.py
Executable file
@@ -0,0 +1,124 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from odoo import api, fields, models, _
|
||||||
|
from odoo.exceptions import ValidationError
|
||||||
|
|
||||||
|
|
||||||
|
class HrPayslip(models.Model):
|
||||||
|
_inherit = 'hr.payslip'
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
@api.depends('move_id', 'move_id.line_ids.full_reconcile_id')
|
||||||
|
def _is_paid(self):
|
||||||
|
for payslip in self:
|
||||||
|
payslip.is_paid = (
|
||||||
|
payslip.move_id and len(payslip.move_id.line_ids.filtered(lambda l: (
|
||||||
|
l.partner_id.id == payslip.employee_id.address_home_id.id and
|
||||||
|
l.account_id.internal_type == 'payable' and
|
||||||
|
not l.reconciled
|
||||||
|
))) == 0
|
||||||
|
)
|
||||||
|
|
||||||
|
is_paid = fields.Boolean(string="Has been Paid", compute='_is_paid', store=True)
|
||||||
|
|
||||||
|
|
||||||
|
class HrPayrollRegisterPaymentWizard(models.TransientModel):
|
||||||
|
|
||||||
|
_name = "hr.payroll.register.payment.wizard"
|
||||||
|
_description = "Hr Payroll Register Payment wizard"
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _default_partner_id(self):
|
||||||
|
context = dict(self._context or {})
|
||||||
|
active_ids = context.get('active_ids', [])
|
||||||
|
payslip = self.env['hr.payslip'].browse(active_ids)
|
||||||
|
return payslip.employee_id.address_home_id.id
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _default_amount(self):
|
||||||
|
context = dict(self._context or {})
|
||||||
|
active_ids = context.get('active_ids', [])
|
||||||
|
payslip = self.env['hr.payslip'].browse(active_ids)
|
||||||
|
amount = 0.0
|
||||||
|
for line in payslip.move_id.line_ids:
|
||||||
|
if line.account_id.internal_type == 'payable' and line.partner_id.id == payslip.employee_id.address_home_id.id:
|
||||||
|
amount += abs(line.balance)
|
||||||
|
return amount
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _default_communication(self):
|
||||||
|
context = dict(self._context or {})
|
||||||
|
active_ids = context.get('active_ids', [])
|
||||||
|
payslip = self.env['hr.payslip'].browse(active_ids)
|
||||||
|
return payslip.number
|
||||||
|
|
||||||
|
partner_id = fields.Many2one('res.partner', string='Partner', required=True, default=_default_partner_id)
|
||||||
|
journal_id = fields.Many2one('account.journal', string='Payment Method', required=True, domain=[('type', 'in', ('bank', 'cash'))])
|
||||||
|
company_id = fields.Many2one('res.company', related='journal_id.company_id', string='Company', readonly=True, required=True)
|
||||||
|
payment_method_id = fields.Many2one('account.payment.method', string='Payment Type', required=True)
|
||||||
|
amount = fields.Monetary(string='Payment Amount', required=True, default=_default_amount)
|
||||||
|
currency_id = fields.Many2one('res.currency', string='Currency', required=True, default=lambda self: self.env.user.company_id.currency_id)
|
||||||
|
payment_date = fields.Date(string='Payment Date', default=fields.Date.context_today, required=True)
|
||||||
|
communication = fields.Char(string='Memo', default=_default_communication)
|
||||||
|
hide_payment_method = fields.Boolean(compute='_compute_hide_payment_method',
|
||||||
|
help="Technical field used to hide the payment method if the selected journal has only one available which is 'manual'")
|
||||||
|
|
||||||
|
@api.one
|
||||||
|
@api.constrains('amount')
|
||||||
|
def _check_amount(self):
|
||||||
|
if not self.amount > 0.0:
|
||||||
|
raise ValidationError('The payment amount must be strictly positive.')
|
||||||
|
|
||||||
|
@api.one
|
||||||
|
@api.depends('journal_id')
|
||||||
|
def _compute_hide_payment_method(self):
|
||||||
|
if not self.journal_id:
|
||||||
|
self.hide_payment_method = True
|
||||||
|
return
|
||||||
|
journal_payment_methods = self.journal_id.outbound_payment_method_ids
|
||||||
|
self.hide_payment_method = len(journal_payment_methods) == 1 and journal_payment_methods[0].code == 'manual'
|
||||||
|
|
||||||
|
@api.onchange('journal_id')
|
||||||
|
def _onchange_journal(self):
|
||||||
|
if self.journal_id:
|
||||||
|
# Set default payment method (we consider the first to be the default one)
|
||||||
|
payment_methods = self.journal_id.outbound_payment_method_ids
|
||||||
|
self.payment_method_id = payment_methods and payment_methods[0] or False
|
||||||
|
# Set payment method domain (restrict to methods enabled for the journal and to selected payment type)
|
||||||
|
return {'domain': {'payment_method_id': [('payment_type', '=', 'outbound'), ('id', 'in', payment_methods.ids)]}}
|
||||||
|
return {}
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def payroll_post_payment(self):
|
||||||
|
self.ensure_one()
|
||||||
|
context = dict(self._context or {})
|
||||||
|
active_ids = context.get('active_ids', [])
|
||||||
|
payslip = self.env['hr.payslip'].browse(active_ids)
|
||||||
|
|
||||||
|
# Create payment and post it
|
||||||
|
payment = self.env['account.payment'].create({
|
||||||
|
'partner_type': 'supplier',
|
||||||
|
'payment_type': 'outbound',
|
||||||
|
'partner_id': self.partner_id.id,
|
||||||
|
'journal_id': self.journal_id.id,
|
||||||
|
'company_id': self.company_id.id,
|
||||||
|
'payment_method_id': self.payment_method_id.id,
|
||||||
|
'amount': self.amount,
|
||||||
|
'currency_id': self.currency_id.id,
|
||||||
|
'payment_date': self.payment_date,
|
||||||
|
'communication': self.communication
|
||||||
|
})
|
||||||
|
payment.post()
|
||||||
|
|
||||||
|
# Reconcile the payment and the payroll, i.e. lookup on the payable account move lines
|
||||||
|
account_move_lines_to_reconcile = self.env['account.move.line']
|
||||||
|
for line in payment.move_line_ids:
|
||||||
|
if line.account_id.internal_type == 'payable':
|
||||||
|
account_move_lines_to_reconcile |= line
|
||||||
|
for line in payslip.move_id.line_ids:
|
||||||
|
if line.account_id.internal_type == 'payable' and line.partner_id.id == self.partner_id.id:
|
||||||
|
account_move_lines_to_reconcile |= line
|
||||||
|
|
||||||
|
account_move_lines_to_reconcile.reconcile()
|
||||||
|
|
||||||
|
return {'type': 'ir.actions.act_window_close'}
|
||||||
82
hr_payroll_payment/hr_payroll_register_payment.xml
Executable file
82
hr_payroll_payment/hr_payroll_register_payment.xml
Executable file
@@ -0,0 +1,82 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<!-- Add Filter for "Needs Payment" -->
|
||||||
|
<record id="payslip_filter_needs_payment" model="ir.ui.view">
|
||||||
|
<field name="name">hr.payslip.select</field>
|
||||||
|
<field name="model">hr.payslip</field>
|
||||||
|
<field name="priority">20</field>
|
||||||
|
<field name="inherit_id" ref="hr_payroll.view_hr_payslip_filter"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<data>
|
||||||
|
<xpath expr="//filter[last()]" position="after">
|
||||||
|
<filter string="Needs Payment" domain="[('is_paid', '=', False)]" help="Needs Payment Slip"/>
|
||||||
|
</xpath>
|
||||||
|
</data>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Wizard view to register payment -->
|
||||||
|
<record id="hr_payroll_register_payment_view_form" model="ir.ui.view">
|
||||||
|
<field name="name">hr.payroll.register.payment.wizard.form</field>
|
||||||
|
<field name="model">hr.payroll.register.payment.wizard</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form string="Register Payment">
|
||||||
|
<sheet>
|
||||||
|
<field name="id" invisible="1"/>
|
||||||
|
<div class="oe_title">
|
||||||
|
<h1>Draft Payment</h1>
|
||||||
|
</div>
|
||||||
|
<group>
|
||||||
|
<group>
|
||||||
|
<field name="partner_id" required="1" context="{'default_is_company': True, 'default_supplier': True}"/>
|
||||||
|
<field name="journal_id" widget="selection"/>
|
||||||
|
<field name="hide_payment_method" invisible="1"/>
|
||||||
|
<field name="payment_method_id" widget="radio" attrs="{'invisible': [('hide_payment_method', '=', True)]}"/>
|
||||||
|
<label for="amount"/>
|
||||||
|
<div name="amount_div" class="o_row">
|
||||||
|
<field name="amount"/>
|
||||||
|
<field name="currency_id" options="{'no_create': True, 'no_open': True}" groups="base.group_multi_currency"/>
|
||||||
|
</div>
|
||||||
|
</group>
|
||||||
|
<group>
|
||||||
|
<field name="payment_date"/>
|
||||||
|
<field name="communication"/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
</sheet>
|
||||||
|
<footer>
|
||||||
|
<button string='Validate' name="payroll_post_payment" type="object" class="btn-primary"/>
|
||||||
|
<button string="Cancel" class="btn-default" special="cancel"/>
|
||||||
|
</footer>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Action to launch Wizard for register payment -->
|
||||||
|
<record id="hr_payroll_register_payment_wizard_action" model="ir.actions.act_window">
|
||||||
|
<field name="name">Register Payment</field>
|
||||||
|
<field name="res_model">hr.payroll.register.payment.wizard</field>
|
||||||
|
<field name="view_type">form</field>
|
||||||
|
<field name="view_mode">form</field>
|
||||||
|
<field name="view_id" ref="hr_payroll_register_payment_view_form"/>
|
||||||
|
<field name="target">new</field>
|
||||||
|
<field name="context">{'default_payment_type': 'inbound'}</field>
|
||||||
|
<field name="domain">[('partner_type', '=', 'customer')]</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Button on Payslip to launch Wizard for register payment -->
|
||||||
|
<record id="hr_payslip_form" model="ir.ui.view">
|
||||||
|
<field name="name">hr.payslip.form</field>
|
||||||
|
<field name="model">hr.payslip</field>
|
||||||
|
<field name="inherit_id" ref="hr_payroll.view_hr_payslip_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//button[@name='refund_sheet']" position="before">
|
||||||
|
<button name="%(hr_payroll_payment.hr_payroll_register_payment_wizard_action)d" type="action" string="Register Payment" class="oe_highlight" attrs="{'invisible': ['|', ('state', '!=', 'done'), ('is_paid', '=', True)]}" groups="account.group_account_user"/>
|
||||||
|
</xpath>
|
||||||
|
<xpath expr="//field[@name='credit_note']" position="after">
|
||||||
|
<field name="is_paid" readonly="1"/>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
</odoo>
|
||||||
3
hr_payroll_timesheet/__init__.py
Executable file
3
hr_payroll_timesheet/__init__.py
Executable file
@@ -0,0 +1,3 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from . import hr_payslip
|
||||||
|
from . import hr_contract
|
||||||
18
hr_payroll_timesheet/__manifest__.py
Executable file
18
hr_payroll_timesheet/__manifest__.py
Executable file
@@ -0,0 +1,18 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
{
|
||||||
|
'name': 'Timesheets on Payslips',
|
||||||
|
'description': 'Get Timesheet and Attendence numbers onto Employee Payslips.',
|
||||||
|
'version': '11.0.0.0.0',
|
||||||
|
'website': 'https://hibou.io/',
|
||||||
|
'author': 'Hibou Corp. <hello@hibou.io>',
|
||||||
|
'license': 'AGPL-3',
|
||||||
|
'category': 'Human Resources',
|
||||||
|
'data': [
|
||||||
|
'hr_contract_view.xml',
|
||||||
|
],
|
||||||
|
'depends': [
|
||||||
|
'hr_payroll',
|
||||||
|
'hr_timesheet_attendance',
|
||||||
|
],
|
||||||
|
}
|
||||||
8
hr_payroll_timesheet/hr_contract.py
Executable file
8
hr_payroll_timesheet/hr_contract.py
Executable file
@@ -0,0 +1,8 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from odoo import models, fields
|
||||||
|
|
||||||
|
|
||||||
|
class HrContract(models.Model):
|
||||||
|
_inherit = 'hr.contract'
|
||||||
|
|
||||||
|
paid_hourly = fields.Boolean(string="Paid Hourly", default=False)
|
||||||
18
hr_payroll_timesheet/hr_contract_view.xml
Executable file
18
hr_payroll_timesheet/hr_contract_view.xml
Executable file
@@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<data>
|
||||||
|
<record id="hr_contract_form_inherit" model="ir.ui.view">
|
||||||
|
<field name="name">hr.contract.form.inherit</field>
|
||||||
|
<field name="model">hr.contract</field>
|
||||||
|
<field name="priority">20</field>
|
||||||
|
<field name="inherit_id" ref="hr_contract.hr_contract_view_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<data>
|
||||||
|
<xpath expr="//field[@name='wage']" position="after">
|
||||||
|
<field name="paid_hourly"/>
|
||||||
|
</xpath>
|
||||||
|
</data>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
</data>
|
||||||
|
</odoo>
|
||||||
50
hr_payroll_timesheet/hr_payslip.py
Executable file
50
hr_payroll_timesheet/hr_payslip.py
Executable file
@@ -0,0 +1,50 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from odoo import api, models
|
||||||
|
|
||||||
|
|
||||||
|
class HrPayslip(models.Model):
|
||||||
|
_inherit = 'hr.payslip'
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def get_worked_day_lines(self, contracts, date_from, date_to):
|
||||||
|
def create_empty_worked_lines(employee_id, contract_id, date_from, date_to):
|
||||||
|
attendance = {
|
||||||
|
'name': 'Timesheet Attendance',
|
||||||
|
'sequence': 10,
|
||||||
|
'code': 'ATTN',
|
||||||
|
'number_of_days': 0.0,
|
||||||
|
'number_of_hours': 0.0,
|
||||||
|
'contract_id': contract_id,
|
||||||
|
}
|
||||||
|
|
||||||
|
valid_days = [
|
||||||
|
('sheet_id.employee_id', '=', employee_id),
|
||||||
|
('sheet_id.state', '=', 'done'),
|
||||||
|
('sheet_id.date_from', '>=', date_from),
|
||||||
|
('sheet_id.date_to', '<=', date_to),
|
||||||
|
]
|
||||||
|
return attendance, valid_days
|
||||||
|
|
||||||
|
attendances = []
|
||||||
|
|
||||||
|
for contract in contracts:
|
||||||
|
attendance, valid_days = create_empty_worked_lines(
|
||||||
|
contract.employee_id.id,
|
||||||
|
contract.id,
|
||||||
|
date_from,
|
||||||
|
date_to
|
||||||
|
)
|
||||||
|
|
||||||
|
for day in self.env['hr_timesheet_sheet.sheet.day'].search(valid_days):
|
||||||
|
if day.total_attendance >= 0.0:
|
||||||
|
attendance['number_of_days'] += 1
|
||||||
|
attendance['number_of_hours'] += day.total_attendance
|
||||||
|
|
||||||
|
# needed so that the shown hours matches any calculations you use them for
|
||||||
|
attendance['number_of_hours'] = round(attendance['number_of_hours'], 2)
|
||||||
|
attendances.append(attendance)
|
||||||
|
|
||||||
|
res = super(HrPayslip, self).get_worked_day_lines(contracts, date_from, date_to)
|
||||||
|
res.extend(attendances)
|
||||||
|
return res
|
||||||
|
|
||||||
2
hr_payslip_line_date/__init__.py
Executable file
2
hr_payslip_line_date/__init__.py
Executable file
@@ -0,0 +1,2 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from . import payslip
|
||||||
24
hr_payslip_line_date/__manifest__.py
Executable file
24
hr_payslip_line_date/__manifest__.py
Executable file
@@ -0,0 +1,24 @@
|
|||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
{
|
||||||
|
'name': 'Date on Payslip Lines',
|
||||||
|
'author': 'Hibou Corp. <hello@hibou.io>',
|
||||||
|
'license': 'AGPL-3',
|
||||||
|
'category': 'Human Resources',
|
||||||
|
'depends': ['hr_payroll_account'],
|
||||||
|
'version': '11.0.0.0.0',
|
||||||
|
'description': """
|
||||||
|
Date on Payslip Lines
|
||||||
|
=====================
|
||||||
|
|
||||||
|
* Adds "Date Account" (date) field to payslip line from payslip
|
||||||
|
* Adds group by date to Payslip Line search view
|
||||||
|
* Allows filtering by "Date Account" for easy period reporting
|
||||||
|
""",
|
||||||
|
|
||||||
|
'auto_install': True,
|
||||||
|
'website': 'https://hibou.io/',
|
||||||
|
'data':[
|
||||||
|
'payslip_view.xml',
|
||||||
|
],
|
||||||
|
'installable': True
|
||||||
|
}
|
||||||
7
hr_payslip_line_date/payslip.py
Executable file
7
hr_payslip_line_date/payslip.py
Executable file
@@ -0,0 +1,7 @@
|
|||||||
|
from odoo import models, fields
|
||||||
|
|
||||||
|
|
||||||
|
class PayslipLine(models.Model):
|
||||||
|
_inherit = 'hr.payslip.line'
|
||||||
|
|
||||||
|
date = fields.Date(string="Date Account", related='slip_id.date', store=True)
|
||||||
18
hr_payslip_line_date/payslip_view.xml
Executable file
18
hr_payslip_line_date/payslip_view.xml
Executable file
@@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<data>
|
||||||
|
<record id="payslip_line_filter_date" model="ir.ui.view">
|
||||||
|
<field name="name">hr.payslip.line.select</field>
|
||||||
|
<field name="model">hr.payslip.line</field>
|
||||||
|
<field name="priority">20</field>
|
||||||
|
<field name="inherit_id" ref="hr_payroll.view_hr_payslip_line_filter"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<data>
|
||||||
|
<xpath expr="//group" position="inside">
|
||||||
|
<filter string="Account Date" name="date" context="{'group_by':'date'}"/>
|
||||||
|
</xpath>
|
||||||
|
</data>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
</data>
|
||||||
|
</odoo>
|
||||||
0
l10n_ch_hr_payroll_input_name_report/__init__.py
Normal file
0
l10n_ch_hr_payroll_input_name_report/__init__.py
Normal file
19
l10n_ch_hr_payroll_input_name_report/__manifest__.py
Normal file
19
l10n_ch_hr_payroll_input_name_report/__manifest__.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
{
|
||||||
|
'name': 'Swiss Payroll Input Name Report',
|
||||||
|
'author': 'Hibou Corp. <hello@hibou.io>',
|
||||||
|
'version': '11.0.0.0.0',
|
||||||
|
'category': 'Human Resources',
|
||||||
|
'sequence': 95,
|
||||||
|
'summary': 'Improves slip reports by using your own Input\'s description',
|
||||||
|
'description': """
|
||||||
|
If a Salary Rule\'s Code is identical to an Input's Code, then the input's description
|
||||||
|
will appear on the payslip report where the rule is displayed.
|
||||||
|
""",
|
||||||
|
'website': 'https://hibou.io/',
|
||||||
|
'depends': ['l10n_ch_hr_payroll_report'],
|
||||||
|
'data': ['hr_payroll_input_name_report.xml'],
|
||||||
|
'installable': True,
|
||||||
|
'application': False,
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<record id="report_payslip" model="ir.ui.view">
|
||||||
|
<field name="name">l10n_ch_hr_payroll_input_name_report.report_payslip</field>
|
||||||
|
<field name="inherit_id" ref="l10n_ch_hr_payroll_report.report_payslip"/>
|
||||||
|
<field name="priority" eval="2"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//tr/td/span[t-field='line.name']" position="replace">
|
||||||
|
<span t-field="line.name" t-if="not line.salary_rule_id.note and not o.input_line_ids.filtered(lambda i: i.code == line.code)"/>
|
||||||
|
<span t-field="o.input_line_ids.filtered(lambda i: i.code == line.code)[0].name" t-if="not line.salary_rule_id.note and o.input_line_ids.filtered(lambda i: i.code == line.code)"/>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</odoo>
|
||||||
3
l10n_us_fl_hr_payroll/__init__.py
Executable file
3
l10n_us_fl_hr_payroll/__init__.py
Executable file
@@ -0,0 +1,3 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from . import us_fl_hr_payroll
|
||||||
29
l10n_us_fl_hr_payroll/__manifest__.py
Executable file
29
l10n_us_fl_hr_payroll/__manifest__.py
Executable file
@@ -0,0 +1,29 @@
|
|||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
{
|
||||||
|
'name': 'USA - Florida - Payroll',
|
||||||
|
'author': 'Hibou Corp. <hello@hibou.io>',
|
||||||
|
'license': 'AGPL-3',
|
||||||
|
'category': 'Localization',
|
||||||
|
'depends': ['l10n_us_hr_payroll'],
|
||||||
|
'version': '11.0.2017.0.0',
|
||||||
|
'description': """
|
||||||
|
USA::Florida Payroll Rules.
|
||||||
|
===========================
|
||||||
|
|
||||||
|
* Florida Department of Revenue partner
|
||||||
|
* Contribution register for Florida DoR
|
||||||
|
* Company level Florida Unemployment Rate
|
||||||
|
""",
|
||||||
|
|
||||||
|
'auto_install': False,
|
||||||
|
'website': 'https://hibou.io/',
|
||||||
|
'data':[
|
||||||
|
'us_fl_hr_payroll_view.xml',
|
||||||
|
'data/base.xml',
|
||||||
|
'data/rules_2016.xml',
|
||||||
|
'data/rules_2017.xml',
|
||||||
|
'data/rules_2018.xml',
|
||||||
|
'data/final.xml',
|
||||||
|
],
|
||||||
|
'installable': True
|
||||||
|
}
|
||||||
31
l10n_us_fl_hr_payroll/data/base.xml
Executable file
31
l10n_us_fl_hr_payroll/data/base.xml
Executable file
@@ -0,0 +1,31 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<data>
|
||||||
|
|
||||||
|
<!-- CONTRIBUTION REGISTERS -->
|
||||||
|
<record id="res_partner_fldor" model="res.partner">
|
||||||
|
<field name="name">Florida Department of Revenue</field>
|
||||||
|
<field name="supplier">1</field>
|
||||||
|
<field eval="0" name="customer"/>
|
||||||
|
</record>
|
||||||
|
<record id="contrib_register_fldor" model="hr.contribution.register">
|
||||||
|
<field name="name">Florida Unemployment</field>
|
||||||
|
<field name="note">Florida Department of Revenue - Unemployment</field>
|
||||||
|
<field name="partner_id" ref="res_partner_fldor"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- HR SALARY RULE CATEGORIES-->
|
||||||
|
<record id="hr_payroll_fl_unemp_wages" model="hr.salary.rule.category">
|
||||||
|
<field name="name">Florida Unemployment - Wages</field>
|
||||||
|
<field name="code">FL_UNEMP_WAGES</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="hr_payroll_fl_unemp" model="hr.salary.rule.category">
|
||||||
|
<field name="name">Florida Unemployment</field>
|
||||||
|
<field name="code">FL_UNEMP</field>
|
||||||
|
<field name="parent_id" ref="hr_payroll.COMP"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</data>
|
||||||
|
</odoo>
|
||||||
24
l10n_us_fl_hr_payroll/data/final.xml
Executable file
24
l10n_us_fl_hr_payroll/data/final.xml
Executable file
@@ -0,0 +1,24 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<data>
|
||||||
|
|
||||||
|
<!-- HR PAYROLL STRUCTURE -->
|
||||||
|
<record id="hr_payroll_salary_structure_us_fl_employee" model="hr.payroll.structure">
|
||||||
|
<field name="code">US_FL_EMP</field>
|
||||||
|
<field name="name">USA Florida Employee</field>
|
||||||
|
<field eval="[(6, 0, [
|
||||||
|
ref('hr_payroll_rules_fl_unemp_wages_2016'),
|
||||||
|
ref('hr_payroll_rules_fl_unemp_2016'),
|
||||||
|
|
||||||
|
ref('hr_payroll_rules_fl_unemp_wages_2017'),
|
||||||
|
ref('hr_payroll_rules_fl_unemp_2017'),
|
||||||
|
|
||||||
|
ref('hr_payroll_rules_fl_unemp_wages_2018'),
|
||||||
|
ref('hr_payroll_rules_fl_unemp_2018'),
|
||||||
|
])]" name="rule_ids"/>
|
||||||
|
<field name="company_id" ref="base.main_company"/>
|
||||||
|
<field name="parent_id" ref="l10n_us_hr_payroll.hr_payroll_salary_structure_us_employee"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</data>
|
||||||
|
</odoo>
|
||||||
49
l10n_us_fl_hr_payroll/data/rules_2016.xml
Executable file
49
l10n_us_fl_hr_payroll/data/rules_2016.xml
Executable file
@@ -0,0 +1,49 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<data>
|
||||||
|
|
||||||
|
<!-- HR SALARY RULES-->
|
||||||
|
<record id="hr_payroll_rules_fl_unemp_wages_2016" model="hr.salary.rule">
|
||||||
|
<field name="sequence" eval="423"/>
|
||||||
|
<field name="category_id" ref="hr_payroll_fl_unemp_wages"/>
|
||||||
|
<field name="name">Florida Unemployment - Wages (2016)</field>
|
||||||
|
<field name="code">FL_UNEMP_WAGES_2016</field>
|
||||||
|
<field name="condition_select">python</field>
|
||||||
|
<field name="condition_python">result = (payslip.date_to[:4] == '2016')</field>
|
||||||
|
<field name="amount_select">code</field>
|
||||||
|
<field name="amount_python_compute">
|
||||||
|
###
|
||||||
|
ytd = payslip.sum('FL_UNEMP_WAGES_2016', '2016-01-01', '2017-01-01')
|
||||||
|
ytd += contract.external_wages
|
||||||
|
remaining = 7000.0 - ytd
|
||||||
|
if remaining <= 0.0:
|
||||||
|
result = 0
|
||||||
|
elif remaining < categories.GROSS:
|
||||||
|
result = remaining
|
||||||
|
else:
|
||||||
|
result = categories.GROSS
|
||||||
|
</field>
|
||||||
|
<field name="appears_on_payslip" eval="False"/>
|
||||||
|
</record>
|
||||||
|
<record id="hr_payroll_rules_fl_unemp_2016" model="hr.salary.rule">
|
||||||
|
<field name="sequence" eval="443"/>
|
||||||
|
<field name="category_id" ref="hr_payroll_fl_unemp"/>
|
||||||
|
<field name="name">Florida Unemployment (2016)</field>
|
||||||
|
<field name="code">FL_UNEMP_2016</field>
|
||||||
|
<field name="condition_select">python</field>
|
||||||
|
<field name="condition_python">result = (payslip.date_to[:4] == '2016')</field>
|
||||||
|
<field name="amount_select">code</field>
|
||||||
|
<field name="amount_python_compute">
|
||||||
|
result_rate = -contract.fl_unemp_rate(2016)
|
||||||
|
result = categories.FL_UNEMP_WAGES
|
||||||
|
|
||||||
|
# result_rate of 0 implies 100% due to bug
|
||||||
|
if result_rate == 0.0:
|
||||||
|
result = 0.0
|
||||||
|
</field>
|
||||||
|
<field name="register_id" ref="contrib_register_fldor"/>
|
||||||
|
<field name="appears_on_payslip" eval="False"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</data>
|
||||||
|
</odoo>
|
||||||
49
l10n_us_fl_hr_payroll/data/rules_2017.xml
Executable file
49
l10n_us_fl_hr_payroll/data/rules_2017.xml
Executable file
@@ -0,0 +1,49 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<data>
|
||||||
|
|
||||||
|
<!-- HR SALARY RULES-->
|
||||||
|
<record id="hr_payroll_rules_fl_unemp_wages_2017" model="hr.salary.rule">
|
||||||
|
<field name="sequence" eval="423"/>
|
||||||
|
<field name="category_id" ref="hr_payroll_fl_unemp_wages"/>
|
||||||
|
<field name="name">Florida Unemployment - Wages (2017)</field>
|
||||||
|
<field name="code">FL_UNEMP_WAGES_2017</field>
|
||||||
|
<field name="condition_select">python</field>
|
||||||
|
<field name="condition_python">result = (payslip.date_to[:4] == '2017')</field>
|
||||||
|
<field name="amount_select">code</field>
|
||||||
|
<field name="amount_python_compute">
|
||||||
|
###
|
||||||
|
ytd = payslip.sum('FL_UNEMP_WAGES_2017', '2017-01-01', '2018-01-01')
|
||||||
|
ytd += contract.external_wages
|
||||||
|
remaining = 7000.0 - ytd
|
||||||
|
if remaining <= 0.0:
|
||||||
|
result = 0
|
||||||
|
elif remaining < categories.GROSS:
|
||||||
|
result = remaining
|
||||||
|
else:
|
||||||
|
result = categories.GROSS
|
||||||
|
</field>
|
||||||
|
<field name="appears_on_payslip" eval="False"/>
|
||||||
|
</record>
|
||||||
|
<record id="hr_payroll_rules_fl_unemp_2017" model="hr.salary.rule">
|
||||||
|
<field name="sequence" eval="443"/>
|
||||||
|
<field name="category_id" ref="hr_payroll_fl_unemp"/>
|
||||||
|
<field name="name">Florida Unemployment (2017)</field>
|
||||||
|
<field name="code">FL_UNEMP_2017</field>
|
||||||
|
<field name="condition_select">python</field>
|
||||||
|
<field name="condition_python">result = (payslip.date_to[:4] == '2017')</field>
|
||||||
|
<field name="amount_select">code</field>
|
||||||
|
<field name="amount_python_compute">
|
||||||
|
result_rate = -contract.fl_unemp_rate(2017)
|
||||||
|
result = categories.FL_UNEMP_WAGES
|
||||||
|
|
||||||
|
# result_rate of 0 implies 100% due to bug
|
||||||
|
if result_rate == 0.0:
|
||||||
|
result = 0.0
|
||||||
|
</field>
|
||||||
|
<field name="register_id" ref="contrib_register_fldor"/>
|
||||||
|
<field name="appears_on_payslip" eval="False"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</data>
|
||||||
|
</odoo>
|
||||||
49
l10n_us_fl_hr_payroll/data/rules_2018.xml
Executable file
49
l10n_us_fl_hr_payroll/data/rules_2018.xml
Executable file
@@ -0,0 +1,49 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<data>
|
||||||
|
|
||||||
|
<!-- HR SALARY RULES-->
|
||||||
|
<record id="hr_payroll_rules_fl_unemp_wages_2018" model="hr.salary.rule">
|
||||||
|
<field name="sequence" eval="423"/>
|
||||||
|
<field name="category_id" ref="hr_payroll_fl_unemp_wages"/>
|
||||||
|
<field name="name">Florida Unemployment - Wages (2018)</field>
|
||||||
|
<field name="code">FL_UNEMP_WAGES_2018</field>
|
||||||
|
<field name="condition_select">python</field>
|
||||||
|
<field name="condition_python">result = (payslip.date_to[:4] == '2018')</field>
|
||||||
|
<field name="amount_select">code</field>
|
||||||
|
<field name="amount_python_compute">
|
||||||
|
###
|
||||||
|
ytd = payslip.sum('FL_UNEMP_WAGES_2018', '2018-01-01', '2019-01-01')
|
||||||
|
ytd += contract.external_wages
|
||||||
|
remaining = 7000.0 - ytd
|
||||||
|
if remaining <= 0.0:
|
||||||
|
result = 0
|
||||||
|
elif remaining < categories.GROSS:
|
||||||
|
result = remaining
|
||||||
|
else:
|
||||||
|
result = categories.GROSS
|
||||||
|
</field>
|
||||||
|
<field name="appears_on_payslip" eval="False"/>
|
||||||
|
</record>
|
||||||
|
<record id="hr_payroll_rules_fl_unemp_2018" model="hr.salary.rule">
|
||||||
|
<field name="sequence" eval="443"/>
|
||||||
|
<field name="category_id" ref="hr_payroll_fl_unemp"/>
|
||||||
|
<field name="name">Florida Unemployment (2018)</field>
|
||||||
|
<field name="code">FL_UNEMP_2018</field>
|
||||||
|
<field name="condition_select">python</field>
|
||||||
|
<field name="condition_python">result = (payslip.date_to[:4] == '2018')</field>
|
||||||
|
<field name="amount_select">code</field>
|
||||||
|
<field name="amount_python_compute">
|
||||||
|
result_rate = -contract.fl_unemp_rate(2018)
|
||||||
|
result = categories.FL_UNEMP_WAGES
|
||||||
|
|
||||||
|
# result_rate of 0 implies 100% due to bug
|
||||||
|
if result_rate == 0.0:
|
||||||
|
result = 0.0
|
||||||
|
</field>
|
||||||
|
<field name="register_id" ref="contrib_register_fldor"/>
|
||||||
|
<field name="appears_on_payslip" eval="False"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</data>
|
||||||
|
</odoo>
|
||||||
5
l10n_us_fl_hr_payroll/tests/__init__.py
Executable file
5
l10n_us_fl_hr_payroll/tests/__init__.py
Executable file
@@ -0,0 +1,5 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from . import test_us_fl_payslip_2016
|
||||||
|
from . import test_us_fl_payslip_2017
|
||||||
|
from . import test_us_fl_payslip_2018
|
||||||
104
l10n_us_fl_hr_payroll/tests/test_us_fl_payslip_2016.py
Executable file
104
l10n_us_fl_hr_payroll/tests/test_us_fl_payslip_2016.py
Executable file
@@ -0,0 +1,104 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from odoo.addons.l10n_us_hr_payroll.tests.test_us_payslip import TestUsPayslip, process_payslip
|
||||||
|
from odoo.addons.l10n_us_hr_payroll.l10n_us_hr_payroll import USHrContract
|
||||||
|
|
||||||
|
|
||||||
|
class TestUsFlPayslip(TestUsPayslip):
|
||||||
|
###
|
||||||
|
# 2016 Taxes and Rates
|
||||||
|
###
|
||||||
|
|
||||||
|
def test_2016_taxes(self):
|
||||||
|
salary = 5000.0;
|
||||||
|
|
||||||
|
## tax maximums
|
||||||
|
FL_UNEMP_MAX_WAGE = 7000.0
|
||||||
|
|
||||||
|
|
||||||
|
employee = self._createEmployee()
|
||||||
|
employee.company_id.fl_unemp_rate_2016 = 2.7
|
||||||
|
|
||||||
|
contract = self._createContract(employee, salary, struct_id=self.ref('l10n_us_fl_hr_payroll.hr_payroll_salary_structure_us_fl_employee'))
|
||||||
|
|
||||||
|
## tax rates
|
||||||
|
FL_UNEMP = contract.fl_unemp_rate(2016) / -100.0
|
||||||
|
|
||||||
|
self._log('2016 Florida tax first payslip:')
|
||||||
|
payslip = self._createPayslip(employee, '2016-01-01', '2016-01-31')
|
||||||
|
|
||||||
|
payslip.compute_sheet()
|
||||||
|
|
||||||
|
cats = self._getCategories(payslip)
|
||||||
|
|
||||||
|
self.assertPayrollEqual(cats['FL_UNEMP_WAGES'], salary)
|
||||||
|
self.assertPayrollEqual(cats['FL_UNEMP'], cats['FL_UNEMP_WAGES'] * FL_UNEMP)
|
||||||
|
|
||||||
|
process_payslip(payslip)
|
||||||
|
|
||||||
|
# Make a new payslip, this one will have maximums
|
||||||
|
|
||||||
|
remaining_fl_unemp_wages = FL_UNEMP_MAX_WAGE - salary if (FL_UNEMP_MAX_WAGE - 2*salary < salary) else salary
|
||||||
|
|
||||||
|
self._log('2016 Florida tax second payslip:')
|
||||||
|
payslip = self._createPayslip(employee, '2016-02-01', '2016-02-29') # 2016 is a leap year
|
||||||
|
|
||||||
|
payslip.compute_sheet()
|
||||||
|
|
||||||
|
cats = self._getCategories(payslip)
|
||||||
|
|
||||||
|
self.assertPayrollEqual(cats['FL_UNEMP_WAGES'], remaining_fl_unemp_wages)
|
||||||
|
self.assertPayrollEqual(cats['FL_UNEMP'], remaining_fl_unemp_wages * FL_UNEMP)
|
||||||
|
|
||||||
|
|
||||||
|
def test_2016_taxes_with_external(self):
|
||||||
|
salary = 5000.0;
|
||||||
|
external_wages = 6000.0
|
||||||
|
|
||||||
|
employee = self._createEmployee()
|
||||||
|
employee.company_id.fl_unemp_rate_2016 = 2.7
|
||||||
|
|
||||||
|
contract = self._createContract(employee, salary, external_wages=external_wages, struct_id=self.ref('l10n_us_fl_hr_payroll.hr_payroll_salary_structure_us_fl_employee'))
|
||||||
|
## tax maximums
|
||||||
|
FL_UNEMP_MAX_WAGE = 7000.0
|
||||||
|
|
||||||
|
## tax rates
|
||||||
|
FL_UNEMP = contract.fl_unemp_rate(2016) / -100.0
|
||||||
|
|
||||||
|
self._log('2016 Forida_external tax first payslip:')
|
||||||
|
payslip = self._createPayslip(employee, '2016-01-01', '2016-01-31')
|
||||||
|
|
||||||
|
payslip.compute_sheet()
|
||||||
|
|
||||||
|
cats = self._getCategories(payslip)
|
||||||
|
|
||||||
|
self.assertPayrollEqual(cats['FL_UNEMP_WAGES'], FL_UNEMP_MAX_WAGE - external_wages)
|
||||||
|
self.assertPayrollEqual(cats['FL_UNEMP'], cats['FL_UNEMP_WAGES'] * FL_UNEMP)
|
||||||
|
|
||||||
|
|
||||||
|
def test_2016_taxes_with_state_exempt(self):
|
||||||
|
salary = 5000.0;
|
||||||
|
external_wages = 6000.0
|
||||||
|
|
||||||
|
employee = self._createEmployee()
|
||||||
|
employee.company_id.fl_unemp_rate_2016 = 2.7
|
||||||
|
|
||||||
|
contract = self._createContract(employee, salary, external_wages=external_wages, struct_id=self.ref(
|
||||||
|
'l10n_us_fl_hr_payroll.hr_payroll_salary_structure_us_fl_employee'), futa_type=USHrContract.FUTA_TYPE_BASIC)
|
||||||
|
## tax maximums
|
||||||
|
FL_UNEMP_MAX_WAGE = 7000.0
|
||||||
|
|
||||||
|
## tax rates
|
||||||
|
FL_UNEMP = contract.fl_unemp_rate(2016) / -100.0
|
||||||
|
|
||||||
|
self.assertPayrollEqual(FL_UNEMP, 0.0)
|
||||||
|
|
||||||
|
self._log('2016 Forida_external tax first payslip:')
|
||||||
|
payslip = self._createPayslip(employee, '2016-01-01', '2016-01-31')
|
||||||
|
|
||||||
|
payslip.compute_sheet()
|
||||||
|
|
||||||
|
cats = self._getCategories(payslip)
|
||||||
|
|
||||||
|
self.assertPayrollEqual(cats['FL_UNEMP_WAGES'], FL_UNEMP_MAX_WAGE - external_wages)
|
||||||
|
self.assertPayrollEqual(cats['FL_UNEMP'], cats['FL_UNEMP_WAGES'] * FL_UNEMP)
|
||||||
97
l10n_us_fl_hr_payroll/tests/test_us_fl_payslip_2017.py
Executable file
97
l10n_us_fl_hr_payroll/tests/test_us_fl_payslip_2017.py
Executable file
@@ -0,0 +1,97 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from odoo.addons.l10n_us_hr_payroll.tests.test_us_payslip import TestUsPayslip, process_payslip
|
||||||
|
from odoo.addons.l10n_us_hr_payroll.l10n_us_hr_payroll import USHrContract
|
||||||
|
|
||||||
|
|
||||||
|
class TestUsFlPayslip(TestUsPayslip):
|
||||||
|
###
|
||||||
|
# 2017 Taxes and Rates
|
||||||
|
###
|
||||||
|
FL_UNEMP_MAX_WAGE = 7000.0
|
||||||
|
|
||||||
|
def test_2017_taxes(self):
|
||||||
|
salary = 5000.0
|
||||||
|
|
||||||
|
employee = self._createEmployee()
|
||||||
|
employee.company_id.fl_unemp_rate_2017 = 2.7
|
||||||
|
|
||||||
|
contract = self._createContract(employee, salary, struct_id=self.ref('l10n_us_fl_hr_payroll.hr_payroll_salary_structure_us_fl_employee'))
|
||||||
|
|
||||||
|
# tax rates
|
||||||
|
fl_unemp = contract.fl_unemp_rate(2017) / -100.0
|
||||||
|
|
||||||
|
self._log('2017 Florida tax first payslip:')
|
||||||
|
payslip = self._createPayslip(employee, '2017-01-01', '2017-01-31')
|
||||||
|
|
||||||
|
payslip.compute_sheet()
|
||||||
|
|
||||||
|
cats = self._getCategories(payslip)
|
||||||
|
|
||||||
|
self.assertPayrollEqual(cats['FL_UNEMP_WAGES'], salary)
|
||||||
|
self.assertPayrollEqual(cats['FL_UNEMP'], cats['FL_UNEMP_WAGES'] * fl_unemp)
|
||||||
|
|
||||||
|
process_payslip(payslip)
|
||||||
|
|
||||||
|
# Make a new payslip, this one will have maximums
|
||||||
|
|
||||||
|
remaining_fl_unemp_wages = self.FL_UNEMP_MAX_WAGE - salary if (self.FL_UNEMP_MAX_WAGE - 2*salary < salary) \
|
||||||
|
else salary
|
||||||
|
|
||||||
|
self._log('2017 Florida tax second payslip:')
|
||||||
|
payslip = self._createPayslip(employee, '2017-02-01', '2017-02-28')
|
||||||
|
|
||||||
|
payslip.compute_sheet()
|
||||||
|
|
||||||
|
cats = self._getCategories(payslip)
|
||||||
|
|
||||||
|
self.assertPayrollEqual(cats['FL_UNEMP_WAGES'], remaining_fl_unemp_wages)
|
||||||
|
self.assertPayrollEqual(cats['FL_UNEMP'], remaining_fl_unemp_wages * fl_unemp)
|
||||||
|
|
||||||
|
def test_2017_taxes_with_external(self):
|
||||||
|
salary = 5000.0
|
||||||
|
external_wages = 6000.0
|
||||||
|
|
||||||
|
employee = self._createEmployee()
|
||||||
|
employee.company_id.fl_unemp_rate_2017 = 2.7
|
||||||
|
|
||||||
|
contract = self._createContract(employee, salary, external_wages=external_wages,
|
||||||
|
struct_id=self.ref('l10n_us_fl_hr_payroll.hr_payroll_salary_structure_us_fl_employee'))
|
||||||
|
|
||||||
|
# tax rates
|
||||||
|
fl_unemp = contract.fl_unemp_rate(2017) / -100.0
|
||||||
|
|
||||||
|
self._log('2017 Forida_external tax first payslip:')
|
||||||
|
payslip = self._createPayslip(employee, '2017-01-01', '2017-01-31')
|
||||||
|
|
||||||
|
payslip.compute_sheet()
|
||||||
|
|
||||||
|
cats = self._getCategories(payslip)
|
||||||
|
|
||||||
|
self.assertPayrollEqual(cats['FL_UNEMP_WAGES'], self.FL_UNEMP_MAX_WAGE - external_wages)
|
||||||
|
self.assertPayrollEqual(cats['FL_UNEMP'], cats['FL_UNEMP_WAGES'] * fl_unemp)
|
||||||
|
|
||||||
|
def test_2017_taxes_with_state_exempt(self):
|
||||||
|
salary = 5000.0
|
||||||
|
external_wages = 6000.0
|
||||||
|
|
||||||
|
employee = self._createEmployee()
|
||||||
|
employee.company_id.fl_unemp_rate_2017 = 2.7
|
||||||
|
|
||||||
|
contract = self._createContract(employee, salary, external_wages=external_wages, struct_id=self.ref(
|
||||||
|
'l10n_us_fl_hr_payroll.hr_payroll_salary_structure_us_fl_employee'), futa_type=USHrContract.FUTA_TYPE_BASIC)
|
||||||
|
|
||||||
|
# tax rates
|
||||||
|
fl_unemp = contract.fl_unemp_rate(2017) / -100.0
|
||||||
|
|
||||||
|
self.assertPayrollEqual(fl_unemp, 0.0)
|
||||||
|
|
||||||
|
self._log('2017 Forida_external tax first payslip:')
|
||||||
|
payslip = self._createPayslip(employee, '2017-01-01', '2017-01-31')
|
||||||
|
|
||||||
|
payslip.compute_sheet()
|
||||||
|
|
||||||
|
cats = self._getCategories(payslip)
|
||||||
|
|
||||||
|
self.assertPayrollEqual(cats['FL_UNEMP_WAGES'], self.FL_UNEMP_MAX_WAGE - external_wages)
|
||||||
|
self.assertPayrollEqual(cats['FL_UNEMP'], cats['FL_UNEMP_WAGES'] * fl_unemp)
|
||||||
97
l10n_us_fl_hr_payroll/tests/test_us_fl_payslip_2018.py
Executable file
97
l10n_us_fl_hr_payroll/tests/test_us_fl_payslip_2018.py
Executable file
@@ -0,0 +1,97 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from odoo.addons.l10n_us_hr_payroll.tests.test_us_payslip import TestUsPayslip, process_payslip
|
||||||
|
from odoo.addons.l10n_us_hr_payroll.l10n_us_hr_payroll import USHrContract
|
||||||
|
|
||||||
|
|
||||||
|
class TestUsFlPayslip(TestUsPayslip):
|
||||||
|
###
|
||||||
|
# 2018 Taxes and Rates
|
||||||
|
###
|
||||||
|
FL_UNEMP_MAX_WAGE = 7000.0
|
||||||
|
|
||||||
|
def test_2018_taxes(self):
|
||||||
|
salary = 5000.0
|
||||||
|
|
||||||
|
employee = self._createEmployee()
|
||||||
|
employee.company_id.fl_unemp_rate_2018 = 2.7
|
||||||
|
|
||||||
|
contract = self._createContract(employee, salary, struct_id=self.ref('l10n_us_fl_hr_payroll.hr_payroll_salary_structure_us_fl_employee'))
|
||||||
|
|
||||||
|
# tax rates
|
||||||
|
fl_unemp = contract.fl_unemp_rate(2018) / -100.0
|
||||||
|
|
||||||
|
self._log('2018 Florida tax first payslip:')
|
||||||
|
payslip = self._createPayslip(employee, '2018-01-01', '2018-01-31')
|
||||||
|
|
||||||
|
payslip.compute_sheet()
|
||||||
|
|
||||||
|
cats = self._getCategories(payslip)
|
||||||
|
|
||||||
|
self.assertPayrollEqual(cats['FL_UNEMP_WAGES'], salary)
|
||||||
|
self.assertPayrollEqual(cats['FL_UNEMP'], cats['FL_UNEMP_WAGES'] * fl_unemp)
|
||||||
|
|
||||||
|
process_payslip(payslip)
|
||||||
|
|
||||||
|
# Make a new payslip, this one will have maximums
|
||||||
|
|
||||||
|
remaining_fl_unemp_wages = self.FL_UNEMP_MAX_WAGE - salary if (self.FL_UNEMP_MAX_WAGE - 2*salary < salary) \
|
||||||
|
else salary
|
||||||
|
|
||||||
|
self._log('2018 Florida tax second payslip:')
|
||||||
|
payslip = self._createPayslip(employee, '2018-02-01', '2018-02-28')
|
||||||
|
|
||||||
|
payslip.compute_sheet()
|
||||||
|
|
||||||
|
cats = self._getCategories(payslip)
|
||||||
|
|
||||||
|
self.assertPayrollEqual(cats['FL_UNEMP_WAGES'], remaining_fl_unemp_wages)
|
||||||
|
self.assertPayrollEqual(cats['FL_UNEMP'], remaining_fl_unemp_wages * fl_unemp)
|
||||||
|
|
||||||
|
def test_2018_taxes_with_external(self):
|
||||||
|
salary = 5000.0
|
||||||
|
external_wages = 6000.0
|
||||||
|
|
||||||
|
employee = self._createEmployee()
|
||||||
|
employee.company_id.fl_unemp_rate_2018 = 2.7
|
||||||
|
|
||||||
|
contract = self._createContract(employee, salary, external_wages=external_wages,
|
||||||
|
struct_id=self.ref('l10n_us_fl_hr_payroll.hr_payroll_salary_structure_us_fl_employee'))
|
||||||
|
|
||||||
|
# tax rates
|
||||||
|
fl_unemp = contract.fl_unemp_rate(2018) / -100.0
|
||||||
|
|
||||||
|
self._log('2018 Forida_external tax first payslip:')
|
||||||
|
payslip = self._createPayslip(employee, '2018-01-01', '2018-01-31')
|
||||||
|
|
||||||
|
payslip.compute_sheet()
|
||||||
|
|
||||||
|
cats = self._getCategories(payslip)
|
||||||
|
|
||||||
|
self.assertPayrollEqual(cats['FL_UNEMP_WAGES'], self.FL_UNEMP_MAX_WAGE - external_wages)
|
||||||
|
self.assertPayrollEqual(cats['FL_UNEMP'], cats['FL_UNEMP_WAGES'] * fl_unemp)
|
||||||
|
|
||||||
|
def test_2018_taxes_with_state_exempt(self):
|
||||||
|
salary = 5000.0
|
||||||
|
external_wages = 6000.0
|
||||||
|
|
||||||
|
employee = self._createEmployee()
|
||||||
|
employee.company_id.fl_unemp_rate_2018 = 2.7
|
||||||
|
|
||||||
|
contract = self._createContract(employee, salary, external_wages=external_wages, struct_id=self.ref(
|
||||||
|
'l10n_us_fl_hr_payroll.hr_payroll_salary_structure_us_fl_employee'), futa_type=USHrContract.FUTA_TYPE_BASIC)
|
||||||
|
|
||||||
|
# tax rates
|
||||||
|
fl_unemp = contract.fl_unemp_rate(2018) / -100.0
|
||||||
|
|
||||||
|
self.assertPayrollEqual(fl_unemp, 0.0)
|
||||||
|
|
||||||
|
self._log('2018 Forida_external tax first payslip:')
|
||||||
|
payslip = self._createPayslip(employee, '2018-01-01', '2018-01-31')
|
||||||
|
|
||||||
|
payslip.compute_sheet()
|
||||||
|
|
||||||
|
cats = self._getCategories(payslip)
|
||||||
|
|
||||||
|
self.assertPayrollEqual(cats['FL_UNEMP_WAGES'], self.FL_UNEMP_MAX_WAGE - external_wages)
|
||||||
|
self.assertPayrollEqual(cats['FL_UNEMP'], cats['FL_UNEMP_WAGES'] * fl_unemp)
|
||||||
25
l10n_us_fl_hr_payroll/us_fl_hr_payroll.py
Executable file
25
l10n_us_fl_hr_payroll/us_fl_hr_payroll.py
Executable file
@@ -0,0 +1,25 @@
|
|||||||
|
from odoo import models, fields, api
|
||||||
|
|
||||||
|
|
||||||
|
class USFLHrContract(models.Model):
|
||||||
|
_inherit = 'hr.contract'
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def fl_unemp_rate(self, year):
|
||||||
|
self.ensure_one()
|
||||||
|
if self.futa_type == self.FUTA_TYPE_BASIC:
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
if hasattr(self.employee_id.company_id, 'fl_unemp_rate_' + str(year)):
|
||||||
|
return self.employee_id.company_id['fl_unemp_rate_' + str(year)]
|
||||||
|
|
||||||
|
raise NotImplemented('Year (' + str(year) + ') Not implemented for US Florida.')
|
||||||
|
|
||||||
|
|
||||||
|
class FLCompany(models.Model):
|
||||||
|
_inherit = 'res.company'
|
||||||
|
|
||||||
|
# Defaults from :: http://floridarevenue.com/dor/taxes/reemployment.html
|
||||||
|
fl_unemp_rate_2016 = fields.Float(string="Florida Unemployment Rate 2016", default=2.7)
|
||||||
|
fl_unemp_rate_2017 = fields.Float(string="Florida Unemployment Rate 2017", default=2.7)
|
||||||
|
fl_unemp_rate_2018 = fields.Float(string="Florida Unemployment Rate 2018", default=2.7)
|
||||||
20
l10n_us_fl_hr_payroll/us_fl_hr_payroll_view.xml
Executable file
20
l10n_us_fl_hr_payroll/us_fl_hr_payroll_view.xml
Executable file
@@ -0,0 +1,20 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<data>
|
||||||
|
<record id="us_fl_view_company_form" model="ir.ui.view">
|
||||||
|
<field name="name">res.company.form</field>
|
||||||
|
<field name="model">res.company</field>
|
||||||
|
<field name="priority">20</field>
|
||||||
|
<field name="inherit_id" ref="base.view_company_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<data>
|
||||||
|
<xpath expr="//field[@name='currency_id']" position="after">
|
||||||
|
<field name="fl_unemp_rate_2016"/>
|
||||||
|
<field name="fl_unemp_rate_2017"/>
|
||||||
|
<field name="fl_unemp_rate_2018"/>
|
||||||
|
</xpath>
|
||||||
|
</data>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
</data>
|
||||||
|
</odoo>
|
||||||
3
l10n_us_hr_payroll/__init__.py
Executable file
3
l10n_us_hr_payroll/__init__.py
Executable file
@@ -0,0 +1,3 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from . import l10n_us_hr_payroll
|
||||||
32
l10n_us_hr_payroll/__manifest__.py
Executable file
32
l10n_us_hr_payroll/__manifest__.py
Executable file
@@ -0,0 +1,32 @@
|
|||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
{
|
||||||
|
'name': 'USA - Payroll',
|
||||||
|
'author': 'Hibou Corp. <hello@hibou.io>',
|
||||||
|
'license': 'AGPL-3',
|
||||||
|
'category': 'Localization',
|
||||||
|
'depends': ['hr_payroll'],
|
||||||
|
'version': '11.0.2017.0.0',
|
||||||
|
'description': """
|
||||||
|
USA Payroll Rules.
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Contract W4 Filing Status & Allowances
|
||||||
|
* FICA Social Security (with wages cap)
|
||||||
|
* FICA Medicare
|
||||||
|
* FICA Additioal Medicare Wages & Tax
|
||||||
|
* FUTA Federal Unemployment (with wages cap)
|
||||||
|
* Federal Income Tax Withholdings based on W4 values
|
||||||
|
""",
|
||||||
|
|
||||||
|
'auto_install': False,
|
||||||
|
'website': 'https://hibou.io/',
|
||||||
|
'data': [
|
||||||
|
'l10n_us_hr_payroll_view.xml',
|
||||||
|
'data/base.xml',
|
||||||
|
'data/rules_2016.xml',
|
||||||
|
'data/rules_2017.xml',
|
||||||
|
'data/rules_2018.xml',
|
||||||
|
'data/final.xml',
|
||||||
|
],
|
||||||
|
'installable': True
|
||||||
|
}
|
||||||
136
l10n_us_hr_payroll/data/base.xml
Executable file
136
l10n_us_hr_payroll/data/base.xml
Executable file
@@ -0,0 +1,136 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<data>
|
||||||
|
<!-- CONTRIBUTION REGISTERS -->
|
||||||
|
<record id="res_partner_eftps_941" model="res.partner">
|
||||||
|
<field name="name">EFTPS - Form 941</field>
|
||||||
|
<field name="supplier">1</field>
|
||||||
|
<field eval="0" name="customer"/>
|
||||||
|
</record>
|
||||||
|
<record id="res_partner_eftps_940" model="res.partner">
|
||||||
|
<field name="name">EFTPS - Form 940</field>
|
||||||
|
<field name="supplier">1</field>
|
||||||
|
<field eval="0" name="customer"/>
|
||||||
|
</record>
|
||||||
|
<record id="contrib_register_eftps_941" model="hr.contribution.register">
|
||||||
|
<field name="name">EFTPS - 941 (FICA + Federal Witholding)</field>
|
||||||
|
<field name="note">Electronic Federal Tax Payment System - Form 941</field>
|
||||||
|
<field name="partner_id" ref="res_partner_eftps_941"/>
|
||||||
|
</record>
|
||||||
|
<record id="contrib_register_eftps_940" model="hr.contribution.register">
|
||||||
|
<field name="name">EFTPS - 940 (FUTA)</field>
|
||||||
|
<field name="note">Electronic Federal Tax Payment System - Form 940</field>
|
||||||
|
<field name="partner_id" ref="res_partner_eftps_940"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- HR SALARY RULE CATEGORIES-->
|
||||||
|
<record id="hr_payroll_fica_emp_ss_wages" model="hr.salary.rule.category">
|
||||||
|
<field name="name">FICA Employee Social Security - Wages</field>
|
||||||
|
<field name="code">FICA_EMP_SS_WAGES</field>
|
||||||
|
</record>
|
||||||
|
<record id="hr_payroll_fica_emp_m_wages" model="hr.salary.rule.category">
|
||||||
|
<field name="name">FICA Employee Medicare - Wages</field>
|
||||||
|
<field name="code">FICA_EMP_M_WAGES</field>
|
||||||
|
</record>
|
||||||
|
<record id="hr_payroll_fica_emp_m_add_wages" model="hr.salary.rule.category">
|
||||||
|
<field name="name">FICA Employee Medicare Additional - Wages</field>
|
||||||
|
<field name="code">FICA_EMP_M_ADD_WAGES</field>
|
||||||
|
</record>
|
||||||
|
<record id="hr_payroll_futa_wages" model="hr.salary.rule.category">
|
||||||
|
<field name="name">FUTA Federal Unemployment - Wages</field>
|
||||||
|
<field name="code">FUTA_WAGES</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="hr_payroll_fica_emp_ss" model="hr.salary.rule.category">
|
||||||
|
<field name="name">FICA Employee Social Security</field>
|
||||||
|
<field name="code">FICA_EMP_SS</field>
|
||||||
|
<field name="parent_id" ref="hr_payroll.DED"/>
|
||||||
|
</record>
|
||||||
|
<record id="hr_payroll_fica_emp_m" model="hr.salary.rule.category">
|
||||||
|
<field name="name">FICA Employee Medicare</field>
|
||||||
|
<field name="code">FICA_EMP_M</field>
|
||||||
|
<field name="parent_id" ref="hr_payroll.DED"/>
|
||||||
|
</record>
|
||||||
|
<record id="hr_payroll_fica_emp_m_add" model="hr.salary.rule.category">
|
||||||
|
<field name="name">FICA Employee Medicare Additional</field>
|
||||||
|
<field name="code">FICA_EMP_M_ADD</field>
|
||||||
|
<field name="parent_id" ref="hr_payroll.DED"/>
|
||||||
|
</record>
|
||||||
|
<record id="hr_payroll_fed_income_withhold" model="hr.salary.rule.category">
|
||||||
|
<field name="name">Federal Income Withholding</field>
|
||||||
|
<field name="code">FED_INC_WITHHOLD</field>
|
||||||
|
<field name="parent_id" ref="hr_payroll.DED"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="hr_payroll_fica_comp_ss" model="hr.salary.rule.category">
|
||||||
|
<field name="name">FICA Company Social Security</field>
|
||||||
|
<field name="code">FICA_COMP_SS</field>
|
||||||
|
<field name="parent_id" ref="hr_payroll.COMP"/>
|
||||||
|
</record>
|
||||||
|
<record id="hr_payroll_fica_comp_m" model="hr.salary.rule.category">
|
||||||
|
<field name="name">FICA Company Medicare</field>
|
||||||
|
<field name="code">FICA_COMP_M</field>
|
||||||
|
<field name="parent_id" ref="hr_payroll.COMP"/>
|
||||||
|
</record>
|
||||||
|
<record id="hr_payroll_futa" model="hr.salary.rule.category">
|
||||||
|
<field name="name">FUTA Federal Unemployment</field>
|
||||||
|
<field name="code">FUTA</field>
|
||||||
|
<field name="parent_id" ref="hr_payroll.COMP"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- HR SALARY RULES-->
|
||||||
|
<!-- Employee Deductions and Calculations -->
|
||||||
|
<!-- Company Contributions -->
|
||||||
|
<record id="hr_payroll_rules_fica_comp_ss" model="hr.salary.rule">
|
||||||
|
<field name="sequence" eval="440"/>
|
||||||
|
<field name="category_id" ref="hr_payroll_fica_comp_ss"/>
|
||||||
|
<field name="name">FICA Company Social Security</field>
|
||||||
|
<field name="code">FICA_COMP_SS</field>
|
||||||
|
<field name="condition_select">none</field>
|
||||||
|
<field name="amount_select">code</field>
|
||||||
|
<field name="amount_python_compute">result = categories.FICA_EMP_SS</field>
|
||||||
|
<field name="register_id" ref="contrib_register_eftps_941"/>
|
||||||
|
<field name="appears_on_payslip" eval="False"/>
|
||||||
|
</record>
|
||||||
|
<record id="hr_payroll_rules_fica_comp_m" model="hr.salary.rule">
|
||||||
|
<field name="sequence" eval="441"/>
|
||||||
|
<field name="category_id" ref="hr_payroll_fica_comp_m"/>
|
||||||
|
<field name="name">FICA Company Medicare</field>
|
||||||
|
<field name="code">FICA_COMP_M</field>
|
||||||
|
<field name="condition_select">none</field>
|
||||||
|
<field name="amount_select">code</field>
|
||||||
|
<field name="amount_python_compute">result = categories.FICA_EMP_M</field>
|
||||||
|
<field name="register_id" ref="contrib_register_eftps_941"/>
|
||||||
|
<field name="appears_on_payslip" eval="False"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Salary Rules -->
|
||||||
|
<!-- Company Contributions -->
|
||||||
|
<record id="hr_payroll_rules_fica_comp_ss" model="hr.salary.rule">
|
||||||
|
<field name="sequence" eval="440"/>
|
||||||
|
<field name="category_id" ref="hr_payroll_fica_comp_ss"/>
|
||||||
|
<field name="name">FICA Company Social Security</field>
|
||||||
|
<field name="code">FICA_COMP_SS</field>
|
||||||
|
<field name="condition_select">none</field>
|
||||||
|
<field name="amount_select">code</field>
|
||||||
|
<field name="amount_python_compute">result = categories.FICA_EMP_SS</field>
|
||||||
|
<field name="register_id" ref="contrib_register_eftps_941"/>
|
||||||
|
<field name="appears_on_payslip" eval="False"/>
|
||||||
|
</record>
|
||||||
|
<record id="hr_payroll_rules_fica_comp_m" model="hr.salary.rule">
|
||||||
|
<field name="sequence" eval="441"/>
|
||||||
|
<field name="category_id" ref="hr_payroll_fica_comp_m"/>
|
||||||
|
<field name="name">FICA Company Medicare</field>
|
||||||
|
<field name="code">FICA_COMP_M</field>
|
||||||
|
<field name="condition_select">none</field>
|
||||||
|
<field name="amount_select">code</field>
|
||||||
|
<field name="amount_python_compute">result = categories.FICA_EMP_M</field>
|
||||||
|
<field name="register_id" ref="contrib_register_eftps_941"/>
|
||||||
|
<field name="appears_on_payslip" eval="False"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</data>
|
||||||
|
</odoo>
|
||||||
51
l10n_us_hr_payroll/data/final.xml
Executable file
51
l10n_us_hr_payroll/data/final.xml
Executable file
@@ -0,0 +1,51 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<data>
|
||||||
|
|
||||||
|
<!-- HR PAYROLL STRUCTURE -->
|
||||||
|
<record id="hr_payroll_salary_structure_us_employee" model="hr.payroll.structure">
|
||||||
|
<field name="code">US_EMP</field>
|
||||||
|
<field name="name">USA Employee</field>
|
||||||
|
<field eval="[(6, 0, [
|
||||||
|
ref('hr_payroll_rules_fica_comp_ss'),
|
||||||
|
ref('hr_payroll_rules_fica_comp_m'),
|
||||||
|
|
||||||
|
ref('hr_payroll_rules_fica_emp_ss_wages_2016'),
|
||||||
|
ref('hr_payroll_rules_fica_emp_m_wages_2016'),
|
||||||
|
ref('hr_payroll_rules_fica_emp_m_add_wages_2016'),
|
||||||
|
ref('hr_payroll_rules_fica_emp_ss_2016'),
|
||||||
|
ref('hr_payroll_rules_fica_emp_m_2016'),
|
||||||
|
ref('hr_payroll_rules_fica_emp_m_add_2016'),
|
||||||
|
ref('hr_payroll_rules_futa_wages_2016'),
|
||||||
|
ref('hr_payroll_rules_futa_2016'),
|
||||||
|
ref('hr_payroll_rules_fed_inc_withhold_2016_single'),
|
||||||
|
ref('hr_payroll_rules_fed_inc_withhold_2016_married'),
|
||||||
|
|
||||||
|
ref('hr_payroll_rules_fica_emp_ss_wages_2017'),
|
||||||
|
ref('hr_payroll_rules_fica_emp_m_wages_2017'),
|
||||||
|
ref('hr_payroll_rules_fica_emp_m_add_wages_2017'),
|
||||||
|
ref('hr_payroll_rules_fica_emp_ss_2017'),
|
||||||
|
ref('hr_payroll_rules_fica_emp_m_2017'),
|
||||||
|
ref('hr_payroll_rules_fica_emp_m_add_2017'),
|
||||||
|
ref('hr_payroll_rules_futa_wages_2017'),
|
||||||
|
ref('hr_payroll_rules_futa_2017'),
|
||||||
|
ref('hr_payroll_rules_fed_inc_withhold_2017_single'),
|
||||||
|
ref('hr_payroll_rules_fed_inc_withhold_2017_married'),
|
||||||
|
|
||||||
|
ref('hr_payroll_rules_fica_emp_ss_wages_2018'),
|
||||||
|
ref('hr_payroll_rules_fica_emp_m_wages_2018'),
|
||||||
|
ref('hr_payroll_rules_fica_emp_m_add_wages_2018'),
|
||||||
|
ref('hr_payroll_rules_fica_emp_ss_2018'),
|
||||||
|
ref('hr_payroll_rules_fica_emp_m_2018'),
|
||||||
|
ref('hr_payroll_rules_fica_emp_m_add_2018'),
|
||||||
|
ref('hr_payroll_rules_futa_wages_2018'),
|
||||||
|
ref('hr_payroll_rules_futa_2018'),
|
||||||
|
ref('hr_payroll_rules_fed_inc_withhold_2018_single'),
|
||||||
|
ref('hr_payroll_rules_fed_inc_withhold_2018_married'),
|
||||||
|
])]" name="rule_ids"/>
|
||||||
|
<field name="company_id" ref="base.main_company"/>
|
||||||
|
<field name="parent_id" ref="hr_payroll.structure_base"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</data>
|
||||||
|
</odoo>
|
||||||
604
l10n_us_hr_payroll/data/rules_2016.xml
Executable file
604
l10n_us_hr_payroll/data/rules_2016.xml
Executable file
@@ -0,0 +1,604 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<data>
|
||||||
|
|
||||||
|
<!-- HR SALARY RULES-->
|
||||||
|
<!-- Employee Deductions and Calculations -->
|
||||||
|
<record id="hr_payroll_rules_fica_emp_ss_wages_2016" model="hr.salary.rule">
|
||||||
|
<field name="sequence" eval="120"/>
|
||||||
|
<field name="category_id" ref="hr_payroll_fica_emp_ss_wages"/>
|
||||||
|
<field name="name">FICA Employee Social Security Wages (2016)</field>
|
||||||
|
<field name="code">FICA_EMP_SS_WAGES_2016</field>
|
||||||
|
<field name="condition_select">python</field>
|
||||||
|
<field name="condition_python">result = (payslip.date_to[:4] == '2016')</field>
|
||||||
|
<field name="amount_select">code</field>
|
||||||
|
<field name="amount_python_compute">
|
||||||
|
###
|
||||||
|
ytd = payslip.sum('FICA_EMP_SS_WAGES_2016', '2016-01-01', '2017-01-01')
|
||||||
|
ytd += contract.external_wages
|
||||||
|
remaining = 118500.0 - ytd
|
||||||
|
if remaining <= 0.0:
|
||||||
|
result = 0
|
||||||
|
elif remaining < categories.GROSS:
|
||||||
|
result = remaining
|
||||||
|
else:
|
||||||
|
result = categories.GROSS
|
||||||
|
</field>
|
||||||
|
<field name="appears_on_payslip" eval="False"/>
|
||||||
|
</record>
|
||||||
|
<record id="hr_payroll_rules_fica_emp_m_wages_2016" model="hr.salary.rule">
|
||||||
|
<field name="sequence" eval="121"/>
|
||||||
|
<field name="category_id" ref="hr_payroll_fica_emp_m_wages"/>
|
||||||
|
<field name="name">FICA Employee Medicare Wages (2016)</field>
|
||||||
|
<field name="code">FICA_EMP_M_WAGES_2016</field>
|
||||||
|
<field name="condition_select">python</field>
|
||||||
|
<field name="condition_python">result = (payslip.date_to[:4] == '2016')</field>
|
||||||
|
<field name="amount_select">code</field>
|
||||||
|
<field name="amount_python_compute">result = categories.GROSS</field>
|
||||||
|
<field name="appears_on_payslip" eval="False"/>
|
||||||
|
</record>
|
||||||
|
<record id="hr_payroll_rules_fica_emp_m_add_wages_2016" model="hr.salary.rule">
|
||||||
|
<field name="sequence" eval="122"/>
|
||||||
|
<field name="category_id" ref="hr_payroll_fica_emp_m_add_wages"/>
|
||||||
|
<field name="name">FICA Employee Medicare Additional Wages (2016)</field>
|
||||||
|
<field name="code">FICA_EMP_M_ADD_WAGES_2016</field>
|
||||||
|
<field name="condition_select">python</field>
|
||||||
|
<field name="condition_python">result = (payslip.date_to[:4] == '2016')</field>
|
||||||
|
<field name="amount_select">code</field>
|
||||||
|
<field name="amount_python_compute">
|
||||||
|
###
|
||||||
|
ADD_M = 200000.0
|
||||||
|
norm_med_ytd = payslip.sum('FICA_EMP_M_WAGES_2016', '2016-01-01', '2017-01-01')
|
||||||
|
norm_med_cur = categories.FICA_EMP_M_WAGES
|
||||||
|
if ADD_M > norm_med_ytd:
|
||||||
|
diff = ADD_M - norm_med_ytd
|
||||||
|
if norm_med_cur > diff:
|
||||||
|
result = norm_med_cur - diff
|
||||||
|
else:
|
||||||
|
result = 0 # normal condition
|
||||||
|
else:
|
||||||
|
result = norm_med_cur # after YTD wages have passed the max
|
||||||
|
</field>
|
||||||
|
<field name="appears_on_payslip" eval="False"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="hr_payroll_rules_fica_emp_ss_2016" model="hr.salary.rule">
|
||||||
|
<field name="sequence" eval="140"/>
|
||||||
|
<field name="category_id" ref="hr_payroll_fica_emp_ss"/>
|
||||||
|
<field name="name">FICA Employee Social Security (2016)</field>
|
||||||
|
<field name="code">FICA_EMP_SS_2016</field>
|
||||||
|
<field name="condition_select">python</field>
|
||||||
|
<field name="condition_python">result = (payslip.date_to[:4] == '2016')</field>
|
||||||
|
<field name="amount_select">code</field>
|
||||||
|
<field name="amount_python_compute">
|
||||||
|
### this should be "rules.FICA_EMP_SS_WAGES_2016", but it doesn't work
|
||||||
|
result_rate = -6.2
|
||||||
|
result = categories.FICA_EMP_SS_WAGES
|
||||||
|
</field>
|
||||||
|
<field name="register_id" ref="contrib_register_eftps_941"/>
|
||||||
|
</record>
|
||||||
|
<record id="hr_payroll_rules_fica_emp_m_2016" model="hr.salary.rule">
|
||||||
|
<field name="sequence" eval="141"/>
|
||||||
|
<field name="category_id" ref="hr_payroll_fica_emp_m"/>
|
||||||
|
<field name="name">FICA Employee Medicare (2016)</field>
|
||||||
|
<field name="code">FICA_EMP_M_2016</field>
|
||||||
|
<field name="condition_select">python</field>
|
||||||
|
<field name="condition_python">result = (payslip.date_to[:4] == '2016')</field>
|
||||||
|
<field name="amount_select">code</field>
|
||||||
|
<field name="amount_python_compute">
|
||||||
|
### this should be "rules.FICA_EMP_M_WAGES_2016", but it doesn't work
|
||||||
|
result_rate = -1.45
|
||||||
|
result = categories.FICA_EMP_M_WAGES
|
||||||
|
</field>
|
||||||
|
<field name="register_id" ref="contrib_register_eftps_941"/>
|
||||||
|
</record>
|
||||||
|
<record id="hr_payroll_rules_fica_emp_m_add_2016" model="hr.salary.rule">
|
||||||
|
<field name="sequence" eval="142"/>
|
||||||
|
<field name="category_id" ref="hr_payroll_fica_emp_m_add"/>
|
||||||
|
<field name="name">FICA Employee Medicare Additional (2016)</field>
|
||||||
|
<field name="code">FICA_EMP_M_ADD_2016</field>
|
||||||
|
<field name="condition_select">python</field>
|
||||||
|
<field name="condition_python">result = (payslip.date_to[:4] == '2016')</field>
|
||||||
|
<field name="amount_select">code</field>
|
||||||
|
<field name="amount_python_compute">
|
||||||
|
### this should be "rules.FICA_EMP_M_ADD_WAGES_2016", but it doesn't work
|
||||||
|
result_rate = -0.9
|
||||||
|
result = categories.FICA_EMP_M_ADD_WAGES
|
||||||
|
</field>
|
||||||
|
<field name="register_id" ref="contrib_register_eftps_941"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Federal Income Tax Withholding -->
|
||||||
|
<record id="hr_payroll_rules_fed_inc_withhold_2016_single" model="hr.salary.rule">
|
||||||
|
<field name="sequence" eval="145"/>
|
||||||
|
<field name="category_id" ref="hr_payroll_fed_income_withhold"/>
|
||||||
|
<field name="name">Federal Income Withholding - Single (2016)</field>
|
||||||
|
<field name="code">FED_INC_WITHHOLD_2016_S</field>
|
||||||
|
<field name="condition_select">python</field>
|
||||||
|
<field name="condition_python">result = (payslip.date_to[:4] == '2016' and contract.w4_filing_status != 'married' and contract.w4_filing_status)</field>
|
||||||
|
<field name="amount_select">code</field>
|
||||||
|
<field name="amount_python_compute">
|
||||||
|
wages = categories.GROSS
|
||||||
|
allowances = contract.w4_allowances
|
||||||
|
is_nra = contract.w4_is_nonresident_alien
|
||||||
|
schedule_pay = contract.schedule_pay
|
||||||
|
val = 0.00
|
||||||
|
additional = contract.w4_additional_withholding
|
||||||
|
|
||||||
|
###
|
||||||
|
# Single WEEKLY
|
||||||
|
###
|
||||||
|
if 'weekly' == schedule_pay:
|
||||||
|
wages -= allowances * 77.90
|
||||||
|
if is_nra:
|
||||||
|
wages += 43.30
|
||||||
|
|
||||||
|
if wages > 43 and wages <= 222:
|
||||||
|
val = 0.00 + ((wages - 43) * 0.10)
|
||||||
|
|
||||||
|
elif wages > 222 and wages <= 767:
|
||||||
|
val = 17.90 + ((wages - 222) * 0.15)
|
||||||
|
|
||||||
|
elif wages > 767 and wages <= 1796:
|
||||||
|
val = 99.65 + ((wages - 767) * 0.25)
|
||||||
|
|
||||||
|
elif wages > 1796 and wages <= 3700:
|
||||||
|
val = 356.90 + ((wages - 1796) * 0.28)
|
||||||
|
|
||||||
|
elif wages > 3700 and wages <= 7992:
|
||||||
|
val = 890.02 + ((wages - 3700) * 0.33)
|
||||||
|
|
||||||
|
elif wages > 7992 and wages <= 8025:
|
||||||
|
val = 2306.38 + ((wages - 7992) * 0.35)
|
||||||
|
|
||||||
|
elif wages > 8025:
|
||||||
|
val = 2317.93 + ((wages - 8025) * 0.396)
|
||||||
|
|
||||||
|
###
|
||||||
|
# Single BIWEEKLY
|
||||||
|
###
|
||||||
|
elif 'bi-weekly' == schedule_pay:
|
||||||
|
wages -= allowances * 155.80
|
||||||
|
if is_nra:
|
||||||
|
wages += 86.50
|
||||||
|
|
||||||
|
if wages > 87 and wages <= 443:
|
||||||
|
val = 0.00 + ((wages - 87) * 0.10)
|
||||||
|
|
||||||
|
elif wages > 443 and wages <= 1535:
|
||||||
|
val = 35.60 + ((wages - 443) * 0.15)
|
||||||
|
|
||||||
|
elif wages > 1535 and wages <= 3592:
|
||||||
|
val = 199.40 + ((wages - 1535) * 0.25)
|
||||||
|
|
||||||
|
elif wages > 3592 and wages <= 7400:
|
||||||
|
val = 713.65 + ((wages - 3592) * 0.28)
|
||||||
|
|
||||||
|
elif wages > 7400 and wages <= 15985:
|
||||||
|
val = 1779.89 + ((wages - 7400) * 0.33)
|
||||||
|
|
||||||
|
elif wages > 15985 and wages <= 16050:
|
||||||
|
val = 4612.94 + ((wages - 15985) * 0.35)
|
||||||
|
|
||||||
|
elif wages > 16050:
|
||||||
|
val = 4635.69 + ((wages - 16050) * 0.396)
|
||||||
|
|
||||||
|
###
|
||||||
|
# Single SEMIMONTHLY
|
||||||
|
###
|
||||||
|
elif 'semi-monthly' == schedule_pay:
|
||||||
|
wages -= allowances * 168.80
|
||||||
|
if is_nra:
|
||||||
|
wages += 93.80
|
||||||
|
|
||||||
|
if wages > 94 and wages <= 480:
|
||||||
|
val = 0.00 + ((wages - 94) * 0.10)
|
||||||
|
|
||||||
|
elif wages > 480 and wages <= 1663:
|
||||||
|
val = 38.60 + ((wages - 480) * 0.15)
|
||||||
|
|
||||||
|
elif wages > 1663 and wages <= 3892:
|
||||||
|
val = 216.05 + ((wages - 1663) * 0.25)
|
||||||
|
|
||||||
|
elif wages > 3892 and wages <= 8017:
|
||||||
|
val = 773.30 + ((wages - 3892) * 0.28)
|
||||||
|
|
||||||
|
elif wages > 8017 and wages <= 17317:
|
||||||
|
val = 1928.30 + ((wages - 8017) * 0.33)
|
||||||
|
|
||||||
|
elif wages > 17317 and wages <= 17388:
|
||||||
|
val = 4997.30 + ((wages - 17317) * 0.35)
|
||||||
|
|
||||||
|
elif wages > 17388:
|
||||||
|
val = 5022.15 + ((wages - 17388) * 0.396)
|
||||||
|
|
||||||
|
###
|
||||||
|
# Single MONTHLY
|
||||||
|
###
|
||||||
|
elif 'monthly' == schedule_pay:
|
||||||
|
wages -= allowances * 337.50
|
||||||
|
if is_nra:
|
||||||
|
wages += 187.50
|
||||||
|
|
||||||
|
if wages > 188 and wages <= 960:
|
||||||
|
val = 0.00 + ((wages - 188) * 0.10)
|
||||||
|
|
||||||
|
elif wages > 960 and wages <= 3325:
|
||||||
|
val = 77.20 + ((wages - 960) * 0.15)
|
||||||
|
|
||||||
|
elif wages > 3325 and wages <= 7783:
|
||||||
|
val = 431.95 + ((wages - 3325) * 0.25)
|
||||||
|
|
||||||
|
elif wages > 7783 and wages <= 16033:
|
||||||
|
val = 1546.45 + ((wages - 7783) * 0.28)
|
||||||
|
|
||||||
|
elif wages > 16033 and wages <= 34633:
|
||||||
|
val = 3856.45 + ((wages - 16033) * 0.33)
|
||||||
|
|
||||||
|
elif wages > 34633 and wages <= 34775:
|
||||||
|
val = 9994.45 + ((wages - 34633) * 0.35)
|
||||||
|
|
||||||
|
elif wages > 34775:
|
||||||
|
val = 10044.15 + ((wages - 34775) * 0.396)
|
||||||
|
|
||||||
|
###
|
||||||
|
# Single QUARTERLY
|
||||||
|
###
|
||||||
|
elif 'quarterly' == schedule_pay:
|
||||||
|
wages -= allowances * 1012.50
|
||||||
|
if is_nra:
|
||||||
|
wages += 562.50
|
||||||
|
|
||||||
|
if wages > 563 and wages <= 2881:
|
||||||
|
val = 0.00 + ((wages - 563) * 0.10)
|
||||||
|
|
||||||
|
elif wages > 2881 and wages <= 9975:
|
||||||
|
val = 231.80 + ((wages - 2881) * 0.15)
|
||||||
|
|
||||||
|
elif wages > 9975 and wages <= 23350:
|
||||||
|
val = 1295.90 + ((wages - 9975) * 0.25)
|
||||||
|
|
||||||
|
elif wages > 23350 and wages <= 48100:
|
||||||
|
val = 4639.65 + ((wages - 23350) * 0.28)
|
||||||
|
|
||||||
|
elif wages > 48100 and wages <= 103900:
|
||||||
|
val = 11569.65 + ((wages - 48100) * 0.33)
|
||||||
|
|
||||||
|
elif wages > 103900 and wages <= 104325:
|
||||||
|
val = 29983.65 + ((wages - 103900) * 0.35)
|
||||||
|
|
||||||
|
elif wages > 104325:
|
||||||
|
val = 30132.40 + ((wages - 104325) * 0.396)
|
||||||
|
|
||||||
|
###
|
||||||
|
# Single SEMIANNUAL
|
||||||
|
###
|
||||||
|
elif 'semi-annually' == schedule_pay:
|
||||||
|
wages -= allowances * 2025.00
|
||||||
|
if is_nra:
|
||||||
|
wages += 1125.0
|
||||||
|
|
||||||
|
if wages > 1125 and wages <= 5763:
|
||||||
|
val = 0.00 + ((wages - 1125) * 0.10)
|
||||||
|
|
||||||
|
elif wages > 5763 and wages <= 19950:
|
||||||
|
val = 463.80 + ((wages - 5763) * 0.15)
|
||||||
|
|
||||||
|
elif wages > 19950 and wages <= 46700:
|
||||||
|
val = 2591.85 + ((wages - 19950) * 0.25)
|
||||||
|
|
||||||
|
elif wages > 46700 and wages <= 96200:
|
||||||
|
val = 9279.35 + ((wages - 46700) * 0.28)
|
||||||
|
|
||||||
|
elif wages > 96200 and wages <= 207800:
|
||||||
|
val = 23139.35 + ((wages - 96200) * 0.33)
|
||||||
|
|
||||||
|
elif wages > 207800 and wages <= 208650:
|
||||||
|
val = 59967.35 + ((wages - 207800) * 0.35)
|
||||||
|
|
||||||
|
elif wages > 208650:
|
||||||
|
val = 60264.85 + ((wages - 208650) * 0.396)
|
||||||
|
|
||||||
|
###
|
||||||
|
# Single ANNUAL
|
||||||
|
###
|
||||||
|
elif 'annually' == schedule_pay:
|
||||||
|
wages -= allowances * 4050.00
|
||||||
|
if is_nra:
|
||||||
|
wages += 2250.0
|
||||||
|
|
||||||
|
if wages > 2250 and wages <= 11525:
|
||||||
|
val = 0.00 + ((wages - 2250) * 0.10)
|
||||||
|
|
||||||
|
elif wages > 11525 and wages <= 39900:
|
||||||
|
val = 927.50 + ((wages - 11525) * 0.15)
|
||||||
|
|
||||||
|
elif wages > 39900 and wages <= 93400:
|
||||||
|
val = 5183.75 + ((wages - 39900) * 0.25)
|
||||||
|
|
||||||
|
elif wages > 93400 and wages <= 192400:
|
||||||
|
val = 18558.75 + ((wages - 93400) * 0.28)
|
||||||
|
|
||||||
|
elif wages > 192400 and wages <= 415600:
|
||||||
|
val = 46278.75 + ((wages - 192400) * 0.33)
|
||||||
|
|
||||||
|
elif wages > 415600 and wages <= 417300:
|
||||||
|
val = 119934.75 + ((wages - 415600) * 0.35)
|
||||||
|
|
||||||
|
elif wages > 417300:
|
||||||
|
val = 120529.75 + ((wages - 417300) * 0.396)
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise Exception('Invalid schedule_pay="' + schedule_pay + '" for W4 Allowance calculation')
|
||||||
|
|
||||||
|
result = -(val + additional)
|
||||||
|
</field>
|
||||||
|
<field name="register_id" ref="contrib_register_eftps_941"/>
|
||||||
|
</record>
|
||||||
|
<record id="hr_payroll_rules_fed_inc_withhold_2016_married" model="hr.salary.rule">
|
||||||
|
<field name="sequence" eval="145"/>
|
||||||
|
<field name="category_id" ref="hr_payroll_fed_income_withhold"/>
|
||||||
|
<field name="name">Federal Income Withholding - Married (2016)</field>
|
||||||
|
<field name="code">FED_INC_WITHHOLD_2016_M</field>
|
||||||
|
<field name="condition_select">python</field>
|
||||||
|
<field name="condition_python">result = (payslip.date_to[:4] == '2016' and contract.w4_filing_status == 'married')</field>
|
||||||
|
<field name="amount_select">code</field>
|
||||||
|
<field name="amount_python_compute">
|
||||||
|
wages = categories.GROSS
|
||||||
|
allowances = contract.w4_allowances
|
||||||
|
is_nra = contract.w4_is_nonresident_alien
|
||||||
|
schedule_pay = contract.schedule_pay
|
||||||
|
val = 0.00
|
||||||
|
additional = contract.w4_additional_withholding
|
||||||
|
|
||||||
|
###
|
||||||
|
# Married WEEKLY
|
||||||
|
###
|
||||||
|
if 'weekly' == schedule_pay:
|
||||||
|
wages -= allowances * 77.90
|
||||||
|
if is_nra:
|
||||||
|
wages += 43.30
|
||||||
|
|
||||||
|
if wages > 164 and wages <= 521:
|
||||||
|
val = 0.00 + ((wages - 164) * 0.10)
|
||||||
|
|
||||||
|
elif wages > 521 and wages <= 1613:
|
||||||
|
val = 35.70 + ((wages - 521) * 0.15)
|
||||||
|
|
||||||
|
elif wages > 1613 and wages <= 3086:
|
||||||
|
val = 199.50 + ((wages - 1613) * 0.25)
|
||||||
|
|
||||||
|
elif wages > 3086 and wages <= 4615:
|
||||||
|
val = 567.75 + ((wages - 3086) * 0.28)
|
||||||
|
|
||||||
|
elif wages > 4615 and wages <= 8113:
|
||||||
|
val = 995.87 + ((wages - 4615) * 0.33)
|
||||||
|
|
||||||
|
elif wages > 8113 and wages <= 9144:
|
||||||
|
val = 2150.21 + ((wages - 8113) * 0.35)
|
||||||
|
|
||||||
|
elif wages > 9144:
|
||||||
|
val = 2511.06 + ((wages - 9144) * 0.396)
|
||||||
|
|
||||||
|
###
|
||||||
|
# Married BIWEEKLY
|
||||||
|
###
|
||||||
|
elif 'bi-weekly' == schedule_pay:
|
||||||
|
wages -= allowances * 155.80
|
||||||
|
if is_nra:
|
||||||
|
wages += 86.50
|
||||||
|
|
||||||
|
if wages > 329 and wages <= 1042:
|
||||||
|
val = 0.00 + ((wages - 329) * 0.10)
|
||||||
|
|
||||||
|
elif wages > 1042 and wages <= 3225:
|
||||||
|
val = 71.30 + ((wages - 1042) * 0.15)
|
||||||
|
|
||||||
|
elif wages > 3225 and wages <= 6171:
|
||||||
|
val = 398.75 + ((wages - 3225) * 0.25)
|
||||||
|
|
||||||
|
elif wages > 6171 and wages <= 9231:
|
||||||
|
val = 1135.25 + ((wages - 6171) * 0.28)
|
||||||
|
|
||||||
|
elif wages > 9231 and wages <= 16227:
|
||||||
|
val = 1992.05 + ((wages - 9231) * 0.33)
|
||||||
|
|
||||||
|
elif wages > 16227 and wages <= 18288:
|
||||||
|
val = 4300.73 + ((wages - 16227) * 0.35)
|
||||||
|
|
||||||
|
elif wages > 18288:
|
||||||
|
val = 5022.08 + ((wages - 18288) * 0.396)
|
||||||
|
|
||||||
|
###
|
||||||
|
# Married SEMIMONTHLY
|
||||||
|
###
|
||||||
|
elif 'semi-monthly' == schedule_pay:
|
||||||
|
wages -= allowances * 168.80
|
||||||
|
if is_nra:
|
||||||
|
wages += 93.80
|
||||||
|
|
||||||
|
if wages > 356 and wages <= 1129:
|
||||||
|
val = 0.00 + ((wages - 356) * 0.10)
|
||||||
|
|
||||||
|
elif wages > 1129 and wages <= 3494:
|
||||||
|
val = 77.30 + ((wages - 1129) * 0.15)
|
||||||
|
|
||||||
|
elif wages > 3494 and wages <= 6685:
|
||||||
|
val = 432.05 + ((wages - 3494) * 0.25)
|
||||||
|
|
||||||
|
elif wages > 6685 and wages <= 10000:
|
||||||
|
val = 1229.80 + ((wages - 6685) * 0.28)
|
||||||
|
|
||||||
|
elif wages > 10000 and wages <= 17579:
|
||||||
|
val = 2158.00 + ((wages - 10000) * 0.33)
|
||||||
|
|
||||||
|
elif wages > 17579 and wages <= 19813:
|
||||||
|
val = 4659.07 + ((wages - 17579) * 0.35)
|
||||||
|
|
||||||
|
elif wages > 19813:
|
||||||
|
val = 5440.97 + ((wages - 19813) * 0.396)
|
||||||
|
|
||||||
|
###
|
||||||
|
# Married MONTHLY
|
||||||
|
###
|
||||||
|
elif 'monthly' == schedule_pay:
|
||||||
|
wages -= allowances * 337.50
|
||||||
|
if is_nra:
|
||||||
|
wages += 187.50
|
||||||
|
|
||||||
|
if wages > 713 and wages <= 2258:
|
||||||
|
val = 0.00 + ((wages - 713) * 0.10)
|
||||||
|
|
||||||
|
elif wages > 2258 and wages <= 6988:
|
||||||
|
val = 154.50 + ((wages - 2258) * 0.15)
|
||||||
|
|
||||||
|
elif wages > 6988 and wages <= 13371:
|
||||||
|
val = 864.00 + ((wages - 6988) * 0.25)
|
||||||
|
|
||||||
|
elif wages > 13371 and wages <= 20000:
|
||||||
|
val = 2459.75 + ((wages - 13371) * 0.28)
|
||||||
|
|
||||||
|
elif wages > 20000 and wages <= 35158:
|
||||||
|
val = 4315.87 + ((wages - 20000) * 0.33)
|
||||||
|
|
||||||
|
elif wages > 35158 and wages <= 39625:
|
||||||
|
val = 9318.01 + ((wages - 35158) * 0.35)
|
||||||
|
|
||||||
|
elif wages > 39625:
|
||||||
|
val = 10881.46 + ((wages - 39625) * 0.396)
|
||||||
|
|
||||||
|
###
|
||||||
|
# Married QUARTERLY
|
||||||
|
###
|
||||||
|
elif 'quarterly' == schedule_pay:
|
||||||
|
wages -= allowances * 1012.50
|
||||||
|
if is_nra:
|
||||||
|
wages += 562.50
|
||||||
|
|
||||||
|
if wages > 2138 and wages <= 6775:
|
||||||
|
val = 0.00 + ((wages - 2138) * 0.10)
|
||||||
|
|
||||||
|
elif wages > 6775 and wages <= 20963:
|
||||||
|
val = 463.70 + ((wages - 6775) * 0.15)
|
||||||
|
|
||||||
|
elif wages > 20963 and wages <= 40113:
|
||||||
|
val = 2591.90 + ((wages - 20963) * 0.25)
|
||||||
|
|
||||||
|
elif wages > 40113 and wages <= 60000:
|
||||||
|
val = 7379.40 + ((wages - 40113) * 0.28)
|
||||||
|
|
||||||
|
elif wages > 60000 and wages <= 105475:
|
||||||
|
val = 12947.76 + ((wages - 60000) * 0.33)
|
||||||
|
|
||||||
|
elif wages > 105475 and wages <= 118875:
|
||||||
|
val = 27954.51 + ((wages - 105475) * 0.35)
|
||||||
|
|
||||||
|
elif wages > 118875:
|
||||||
|
val = 32644.51 + ((wages - 118875) * 0.396)
|
||||||
|
|
||||||
|
###
|
||||||
|
# Married SEMIANNUAL
|
||||||
|
###
|
||||||
|
elif 'semi-annually' == schedule_pay:
|
||||||
|
wages -= allowances * 2025.00
|
||||||
|
if is_nra:
|
||||||
|
wages += 1125.0
|
||||||
|
|
||||||
|
if wages > 4275 and wages <= 13550:
|
||||||
|
val = 0.00 + ((wages - 4275) * 0.10)
|
||||||
|
|
||||||
|
elif wages > 13550 and wages <= 41925:
|
||||||
|
val = 927.50 + ((wages - 13550) * 0.15)
|
||||||
|
|
||||||
|
elif wages > 41925 and wages <= 80225:
|
||||||
|
val = 5183.75 + ((wages - 41925) * 0.25)
|
||||||
|
|
||||||
|
elif wages > 80225 and wages <= 120000:
|
||||||
|
val = 14758.75 + ((wages - 80225) * 0.28)
|
||||||
|
|
||||||
|
elif wages > 120000 and wages <= 210950:
|
||||||
|
val = 25895.75 + ((wages - 120000) * 0.33)
|
||||||
|
|
||||||
|
elif wages > 210950 and wages <= 237750:
|
||||||
|
val = 55909.25 + ((wages - 210950) * 0.35)
|
||||||
|
|
||||||
|
elif wages > 237750:
|
||||||
|
val = 65289.25 + ((wages - 237750) * 0.396)
|
||||||
|
|
||||||
|
###
|
||||||
|
# Married ANNUAL
|
||||||
|
###
|
||||||
|
elif 'annually' == schedule_pay:
|
||||||
|
wages -= allowances * 4050.00
|
||||||
|
if is_nra:
|
||||||
|
wages += 2250.0
|
||||||
|
|
||||||
|
if wages > 8550 and wages <= 27100:
|
||||||
|
val = 0.00 + ((wages - 8550) * 0.10)
|
||||||
|
|
||||||
|
elif wages > 27100 and wages <= 83850:
|
||||||
|
val = 1855.00 + ((wages - 27100) * 0.15)
|
||||||
|
|
||||||
|
elif wages > 83850 and wages <= 160450:
|
||||||
|
val = 10367.50 + ((wages - 83850) * 0.25)
|
||||||
|
|
||||||
|
elif wages > 160450 and wages <= 240000:
|
||||||
|
val = 29517.50 + ((wages - 160450) * 0.28)
|
||||||
|
|
||||||
|
elif wages > 240000 and wages <= 421900:
|
||||||
|
val = 51791.50 + ((wages - 240000) * 0.33)
|
||||||
|
|
||||||
|
elif wages > 421900 and wages <= 475500:
|
||||||
|
val = 111818.50 + ((wages - 421900) * 0.35)
|
||||||
|
|
||||||
|
elif wages > 475500:
|
||||||
|
val = 130578.50 + ((wages - 475500) * 0.396)
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise Exception('Invalid schedule_pay="' + schedule_pay + '" for W4 Allowance calculation')
|
||||||
|
|
||||||
|
result = -(val + additional)
|
||||||
|
</field>
|
||||||
|
<field name="register_id" ref="contrib_register_eftps_941"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="hr_payroll_rules_futa_wages_2016" model="hr.salary.rule">
|
||||||
|
<field name="sequence" eval="422"/>
|
||||||
|
<field name="category_id" ref="hr_payroll_futa_wages"/>
|
||||||
|
<field name="name">FUTA Federal Unemployment - Wages (2016)</field>
|
||||||
|
<field name="code">FUTA_WAGES_2016</field>
|
||||||
|
<field name="condition_select">python</field>
|
||||||
|
<field name="condition_python">result = (payslip.date_to[:4] == '2016' and contract.futa_type != contract.FUTA_TYPE_EXEMPT)</field>
|
||||||
|
<field name="amount_select">code</field>
|
||||||
|
<field name="amount_python_compute">
|
||||||
|
###
|
||||||
|
ytd = payslip.sum('FUTA_WAGES_2016', '2016-01-01', '2017-01-01')
|
||||||
|
ytd += contract.external_wages
|
||||||
|
remaining = 7000.0 - ytd
|
||||||
|
if remaining <= 0.0:
|
||||||
|
result = 0
|
||||||
|
elif remaining < categories.GROSS:
|
||||||
|
result = remaining
|
||||||
|
else:
|
||||||
|
result = categories.GROSS
|
||||||
|
</field>
|
||||||
|
<field name="appears_on_payslip" eval="False"/>
|
||||||
|
</record>
|
||||||
|
<record id="hr_payroll_rules_futa_2016" model="hr.salary.rule">
|
||||||
|
<field name="sequence" eval="442"/>
|
||||||
|
<field name="category_id" ref="hr_payroll_futa"/>
|
||||||
|
<field name="name">FUTA Federal Unemployment (2016)</field>
|
||||||
|
<field name="code">FUTA_2016</field>
|
||||||
|
<field name="condition_select">python</field>
|
||||||
|
<field name="condition_python">result = (payslip.date_to[:4] == '2016' and contract.futa_type != contract.FUTA_TYPE_EXEMPT)</field>
|
||||||
|
<field name="amount_select">code</field>
|
||||||
|
<field name="amount_python_compute">
|
||||||
|
result_rate = -(contract.futa_rate(2016))
|
||||||
|
result = categories.FUTA_WAGES
|
||||||
|
</field>
|
||||||
|
<field name="register_id" ref="contrib_register_eftps_940"/>
|
||||||
|
<field name="appears_on_payslip" eval="False"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</data>
|
||||||
|
</odoo>
|
||||||
604
l10n_us_hr_payroll/data/rules_2017.xml
Executable file
604
l10n_us_hr_payroll/data/rules_2017.xml
Executable file
@@ -0,0 +1,604 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<data>
|
||||||
|
|
||||||
|
<!-- HR SALARY RULES-->
|
||||||
|
<!-- Employee Deductions and Calculations -->
|
||||||
|
<record id="hr_payroll_rules_fica_emp_ss_wages_2017" model="hr.salary.rule">
|
||||||
|
<field name="sequence" eval="120"/>
|
||||||
|
<field name="category_id" ref="hr_payroll_fica_emp_ss_wages"/>
|
||||||
|
<field name="name">FICA Employee Social Security Wages (2017)</field>
|
||||||
|
<field name="code">FICA_EMP_SS_WAGES_2017</field>
|
||||||
|
<field name="condition_select">python</field>
|
||||||
|
<field name="condition_python">result = (payslip.date_to[:4] == '2017')</field>
|
||||||
|
<field name="amount_select">code</field>
|
||||||
|
<field name="amount_python_compute">
|
||||||
|
###
|
||||||
|
ytd = payslip.sum('FICA_EMP_SS_WAGES_2017', '2017-01-01', '2018-01-01')
|
||||||
|
ytd += contract.external_wages
|
||||||
|
remaining = 127200.0 - ytd
|
||||||
|
if remaining <= 0.0:
|
||||||
|
result = 0
|
||||||
|
elif remaining < categories.GROSS:
|
||||||
|
result = remaining
|
||||||
|
else:
|
||||||
|
result = categories.GROSS
|
||||||
|
</field>
|
||||||
|
<field name="appears_on_payslip" eval="False"/>
|
||||||
|
</record>
|
||||||
|
<record id="hr_payroll_rules_fica_emp_m_wages_2017" model="hr.salary.rule">
|
||||||
|
<field name="sequence" eval="121"/>
|
||||||
|
<field name="category_id" ref="hr_payroll_fica_emp_m_wages"/>
|
||||||
|
<field name="name">FICA Employee Medicare Wages (2017)</field>
|
||||||
|
<field name="code">FICA_EMP_M_WAGES_2017</field>
|
||||||
|
<field name="condition_select">python</field>
|
||||||
|
<field name="condition_python">result = (payslip.date_to[:4] == '2017')</field>
|
||||||
|
<field name="amount_select">code</field>
|
||||||
|
<field name="amount_python_compute">result = categories.GROSS</field>
|
||||||
|
<field name="appears_on_payslip" eval="False"/>
|
||||||
|
</record>
|
||||||
|
<record id="hr_payroll_rules_fica_emp_m_add_wages_2017" model="hr.salary.rule">
|
||||||
|
<field name="sequence" eval="122"/>
|
||||||
|
<field name="category_id" ref="hr_payroll_fica_emp_m_add_wages"/>
|
||||||
|
<field name="name">FICA Employee Medicare Additional Wages (2017)</field>
|
||||||
|
<field name="code">FICA_EMP_M_ADD_WAGES_2017</field>
|
||||||
|
<field name="condition_select">python</field>
|
||||||
|
<field name="condition_python">result = (payslip.date_to[:4] == '2017')</field>
|
||||||
|
<field name="amount_select">code</field>
|
||||||
|
<field name="amount_python_compute">
|
||||||
|
###
|
||||||
|
ADD_M = 200000.0
|
||||||
|
norm_med_ytd = payslip.sum('FICA_EMP_M_WAGES_2017', '2017-01-01', '2018-01-01')
|
||||||
|
norm_med_cur = categories.FICA_EMP_M_WAGES
|
||||||
|
if ADD_M > norm_med_ytd:
|
||||||
|
diff = ADD_M - norm_med_ytd
|
||||||
|
if norm_med_cur > diff:
|
||||||
|
result = norm_med_cur - diff
|
||||||
|
else:
|
||||||
|
result = 0 # normal condition
|
||||||
|
else:
|
||||||
|
result = norm_med_cur # after YTD wages have passed the max
|
||||||
|
</field>
|
||||||
|
<field name="appears_on_payslip" eval="False"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="hr_payroll_rules_fica_emp_ss_2017" model="hr.salary.rule">
|
||||||
|
<field name="sequence" eval="140"/>
|
||||||
|
<field name="category_id" ref="hr_payroll_fica_emp_ss"/>
|
||||||
|
<field name="name">FICA Employee Social Security (2017)</field>
|
||||||
|
<field name="code">FICA_EMP_SS_2017</field>
|
||||||
|
<field name="condition_select">python</field>
|
||||||
|
<field name="condition_python">result = (payslip.date_to[:4] == '2017')</field>
|
||||||
|
<field name="amount_select">code</field>
|
||||||
|
<field name="amount_python_compute">
|
||||||
|
### this should be "rules.FICA_EMP_SS_WAGES_2017", but it doesn't work
|
||||||
|
result_rate = -6.2
|
||||||
|
result = categories.FICA_EMP_SS_WAGES
|
||||||
|
</field>
|
||||||
|
<field name="register_id" ref="contrib_register_eftps_941"/>
|
||||||
|
</record>
|
||||||
|
<record id="hr_payroll_rules_fica_emp_m_2017" model="hr.salary.rule">
|
||||||
|
<field name="sequence" eval="141"/>
|
||||||
|
<field name="category_id" ref="hr_payroll_fica_emp_m"/>
|
||||||
|
<field name="name">FICA Employee Medicare (2017)</field>
|
||||||
|
<field name="code">FICA_EMP_M_2017</field>
|
||||||
|
<field name="condition_select">python</field>
|
||||||
|
<field name="condition_python">result = (payslip.date_to[:4] == '2017')</field>
|
||||||
|
<field name="amount_select">code</field>
|
||||||
|
<field name="amount_python_compute">
|
||||||
|
### this should be "rules.FICA_EMP_M_WAGES_2017", but it doesn't work
|
||||||
|
result_rate = -1.45
|
||||||
|
result = categories.FICA_EMP_M_WAGES
|
||||||
|
</field>
|
||||||
|
<field name="register_id" ref="contrib_register_eftps_941"/>
|
||||||
|
</record>
|
||||||
|
<record id="hr_payroll_rules_fica_emp_m_add_2017" model="hr.salary.rule">
|
||||||
|
<field name="sequence" eval="142"/>
|
||||||
|
<field name="category_id" ref="hr_payroll_fica_emp_m_add"/>
|
||||||
|
<field name="name">FICA Employee Medicare Additional (2017)</field>
|
||||||
|
<field name="code">FICA_EMP_M_ADD_2017</field>
|
||||||
|
<field name="condition_select">python</field>
|
||||||
|
<field name="condition_python">result = (payslip.date_to[:4] == '2017')</field>
|
||||||
|
<field name="amount_select">code</field>
|
||||||
|
<field name="amount_python_compute">
|
||||||
|
### this should be "rules.FICA_EMP_M_ADD_WAGES_2017", but it doesn't work
|
||||||
|
result_rate = -0.9
|
||||||
|
result = categories.FICA_EMP_M_ADD_WAGES
|
||||||
|
</field>
|
||||||
|
<field name="register_id" ref="contrib_register_eftps_941"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Federal Income Tax Withholding -->
|
||||||
|
<record id="hr_payroll_rules_fed_inc_withhold_2017_single" model="hr.salary.rule">
|
||||||
|
<field name="sequence" eval="145"/>
|
||||||
|
<field name="category_id" ref="hr_payroll_fed_income_withhold"/>
|
||||||
|
<field name="name">Federal Income Withholding - Single (2017)</field>
|
||||||
|
<field name="code">FED_INC_WITHHOLD_2017_S</field>
|
||||||
|
<field name="condition_select">python</field>
|
||||||
|
<field name="condition_python">result = (payslip.date_to[:4] == '2017' and contract.w4_filing_status != 'married' and contract.w4_filing_status)</field>
|
||||||
|
<field name="amount_select">code</field>
|
||||||
|
<field name="amount_python_compute">
|
||||||
|
wages = categories.GROSS
|
||||||
|
allowances = contract.w4_allowances
|
||||||
|
is_nra = contract.w4_is_nonresident_alien
|
||||||
|
schedule_pay = contract.schedule_pay
|
||||||
|
val = 0.00
|
||||||
|
additional = contract.w4_additional_withholding
|
||||||
|
|
||||||
|
###
|
||||||
|
# Single WEEKLY
|
||||||
|
###
|
||||||
|
if 'weekly' == schedule_pay:
|
||||||
|
wages -= allowances * 77.90
|
||||||
|
if is_nra:
|
||||||
|
wages += 44.20
|
||||||
|
|
||||||
|
if wages > 44 and wages <= 224:
|
||||||
|
val = 0.00 + ((wages - 44) * 0.10)
|
||||||
|
|
||||||
|
elif wages > 224 and wages <= 774:
|
||||||
|
val = 18.00 + ((wages - 224) * 0.15)
|
||||||
|
|
||||||
|
elif wages > 774 and wages <= 1812:
|
||||||
|
val = 100.50 + ((wages - 774) * 0.25)
|
||||||
|
|
||||||
|
elif wages > 1812 and wages <= 3730:
|
||||||
|
val = 360.00 + ((wages - 1812) * 0.28)
|
||||||
|
|
||||||
|
elif wages > 3730 and wages <= 8058:
|
||||||
|
val = 897.04 + ((wages - 3730) * 0.33)
|
||||||
|
|
||||||
|
elif wages > 8058 and wages <= 8090:
|
||||||
|
val = 2325.28 + ((wages - 8058) * 0.35)
|
||||||
|
|
||||||
|
elif wages > 8090:
|
||||||
|
val = 2336.48 + ((wages - 8090) * 0.396)
|
||||||
|
|
||||||
|
###
|
||||||
|
# Single BIWEEKLY
|
||||||
|
###
|
||||||
|
elif 'bi-weekly' == schedule_pay:
|
||||||
|
wages -= allowances * 155.80
|
||||||
|
if is_nra:
|
||||||
|
wages += 88.50
|
||||||
|
|
||||||
|
if wages > 88 and wages <= 447:
|
||||||
|
val = 0.00 + ((wages - 88) * 0.10)
|
||||||
|
|
||||||
|
elif wages > 447 and wages <= 1548:
|
||||||
|
val = 35.90 + ((wages - 447) * 0.15)
|
||||||
|
|
||||||
|
elif wages > 1548 and wages <= 3623:
|
||||||
|
val = 201.05 + ((wages - 1548) * 0.25)
|
||||||
|
|
||||||
|
elif wages > 3623 and wages <= 7460:
|
||||||
|
val = 719.80 + ((wages - 3623) * 0.28)
|
||||||
|
|
||||||
|
elif wages > 7460 and wages <= 16115:
|
||||||
|
val = 1794.16 + ((wages - 7460) * 0.33)
|
||||||
|
|
||||||
|
elif wages > 16115 and wages <= 16181:
|
||||||
|
val = 4650.31 + ((wages - 16115) * 0.35)
|
||||||
|
|
||||||
|
elif wages > 16181:
|
||||||
|
val = 4673.41 + ((wages - 16181) * 0.396)
|
||||||
|
|
||||||
|
###
|
||||||
|
# Single SEMIMONTHLY
|
||||||
|
###
|
||||||
|
elif 'semi-monthly' == schedule_pay:
|
||||||
|
wages -= allowances * 168.80
|
||||||
|
if is_nra:
|
||||||
|
wages += 95.80
|
||||||
|
|
||||||
|
if wages > 96 and wages <= 484:
|
||||||
|
val = 0.00 + ((wages - 96) * 0.10)
|
||||||
|
|
||||||
|
elif wages > 484 and wages <= 1677:
|
||||||
|
val = 38.80 + ((wages - 484) * 0.15)
|
||||||
|
|
||||||
|
elif wages > 1677 and wages <= 3925:
|
||||||
|
val = 217.75 + ((wages - 1677) * 0.25)
|
||||||
|
|
||||||
|
elif wages > 3925 and wages <= 8081:
|
||||||
|
val = 779.75 + ((wages - 3925) * 0.28)
|
||||||
|
|
||||||
|
elif wages > 8081 and wages <= 17458:
|
||||||
|
val = 1943.43 + ((wages - 8081) * 0.33)
|
||||||
|
|
||||||
|
elif wages > 17458 and wages <= 17529:
|
||||||
|
val = 5037.84 + ((wages - 17458) * 0.35)
|
||||||
|
|
||||||
|
elif wages > 17529:
|
||||||
|
val = 5062.69 + ((wages - 17529) * 0.396)
|
||||||
|
|
||||||
|
###
|
||||||
|
# Single MONTHLY
|
||||||
|
###
|
||||||
|
elif 'monthly' == schedule_pay:
|
||||||
|
wages -= allowances * 337.50
|
||||||
|
if is_nra:
|
||||||
|
wages += 191.70
|
||||||
|
|
||||||
|
if wages > 192 and wages <= 969:
|
||||||
|
val = 0.00 + ((wages - 192) * 0.10)
|
||||||
|
|
||||||
|
elif wages > 969 and wages <= 3354:
|
||||||
|
val = 77.70 + ((wages - 969) * 0.15)
|
||||||
|
|
||||||
|
elif wages > 3354 and wages <= 7850:
|
||||||
|
val = 435.45 + ((wages - 3354) * 0.25)
|
||||||
|
|
||||||
|
elif wages > 7850 and wages <= 16163:
|
||||||
|
val = 1559.45 + ((wages - 7850) * 0.28)
|
||||||
|
|
||||||
|
elif wages > 16163 and wages <= 34917:
|
||||||
|
val = 3887.09 + ((wages - 16163) * 0.33)
|
||||||
|
|
||||||
|
elif wages > 34917 and wages <= 35058:
|
||||||
|
val = 10075.91 + ((wages - 34917) * 0.35)
|
||||||
|
|
||||||
|
elif wages > 35058:
|
||||||
|
val = 10125.26 + ((wages - 35058) * 0.396)
|
||||||
|
|
||||||
|
###
|
||||||
|
# Single QUARTERLY
|
||||||
|
###
|
||||||
|
elif 'quarterly' == schedule_pay:
|
||||||
|
wages -= allowances * 1012.50
|
||||||
|
if is_nra:
|
||||||
|
wages += 575.00
|
||||||
|
|
||||||
|
if wages > 575 and wages <= 2906:
|
||||||
|
val = 0.00 + ((wages - 575) * 0.10)
|
||||||
|
|
||||||
|
elif wages > 2906 and wages <= 10063:
|
||||||
|
val = 233.10 + ((wages - 2906) * 0.15)
|
||||||
|
|
||||||
|
elif wages > 10063 and wages <= 23550:
|
||||||
|
val = 1306.65 + ((wages - 10063) * 0.25)
|
||||||
|
|
||||||
|
elif wages > 23550 and wages <= 48488:
|
||||||
|
val = 4678.40 + ((wages - 23550) * 0.28)
|
||||||
|
|
||||||
|
elif wages > 48488 and wages <= 104750:
|
||||||
|
val = 11661.04 + ((wages - 48488) * 0.33)
|
||||||
|
|
||||||
|
elif wages > 104750 and wages <= 105175:
|
||||||
|
val = 30227.50 + ((wages - 104750) * 0.35)
|
||||||
|
|
||||||
|
elif wages > 105175:
|
||||||
|
val = 30376.25 + ((wages - 105175) * 0.396)
|
||||||
|
|
||||||
|
###
|
||||||
|
# Single SEMIANNUAL
|
||||||
|
###
|
||||||
|
elif 'semi-annually' == schedule_pay:
|
||||||
|
wages -= allowances * 2025.00
|
||||||
|
if is_nra:
|
||||||
|
wages += 1150.0
|
||||||
|
|
||||||
|
if wages > 1150 and wages <= 5813:
|
||||||
|
val = 0.00 + ((wages - 1150) * 0.10)
|
||||||
|
|
||||||
|
elif wages > 5813 and wages <= 20125:
|
||||||
|
val = 466.30 + ((wages - 5813) * 0.15)
|
||||||
|
|
||||||
|
elif wages > 20125 and wages <= 47100:
|
||||||
|
val = 2613.10 + ((wages - 20125) * 0.25)
|
||||||
|
|
||||||
|
elif wages > 47100 and wages <= 96975:
|
||||||
|
val = 9356.85 + ((wages - 47100) * 0.28)
|
||||||
|
|
||||||
|
elif wages > 96975 and wages <= 209500:
|
||||||
|
val = 23321.85 + ((wages - 96975) * 0.33)
|
||||||
|
|
||||||
|
elif wages > 209500 and wages <= 210350:
|
||||||
|
val = 60455.10 + ((wages - 209500) * 0.35)
|
||||||
|
|
||||||
|
elif wages > 210350:
|
||||||
|
val = 60752.60 + ((wages - 210350) * 0.396)
|
||||||
|
|
||||||
|
###
|
||||||
|
# Single ANNUAL
|
||||||
|
###
|
||||||
|
elif 'annually' == schedule_pay:
|
||||||
|
wages -= allowances * 4050.00
|
||||||
|
if is_nra:
|
||||||
|
wages += 2300.0
|
||||||
|
|
||||||
|
if wages > 2300 and wages <= 11625:
|
||||||
|
val = 0.00 + ((wages - 2300) * 0.10)
|
||||||
|
|
||||||
|
elif wages > 11625 and wages <= 40250:
|
||||||
|
val = 932.50 + ((wages - 11625) * 0.15)
|
||||||
|
|
||||||
|
elif wages > 40250 and wages <= 94200:
|
||||||
|
val = 5226.25 + ((wages - 40250) * 0.25)
|
||||||
|
|
||||||
|
elif wages > 94200 and wages <= 193950:
|
||||||
|
val = 18713.75 + ((wages - 94200) * 0.28)
|
||||||
|
|
||||||
|
elif wages > 193950 and wages <= 419000:
|
||||||
|
val = 46643.75 + ((wages - 193950) * 0.33)
|
||||||
|
|
||||||
|
elif wages > 419000 and wages <= 420700:
|
||||||
|
val = 120910.25 + ((wages - 419000) * 0.35)
|
||||||
|
|
||||||
|
elif wages > 420700:
|
||||||
|
val = 121505.25 + ((wages - 420700) * 0.396)
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise Exception('Invalid schedule_pay="' + schedule_pay + '" for W4 Allowance calculation')
|
||||||
|
|
||||||
|
result = -(val + additional)
|
||||||
|
</field>
|
||||||
|
<field name="register_id" ref="contrib_register_eftps_941"/>
|
||||||
|
</record>
|
||||||
|
<record id="hr_payroll_rules_fed_inc_withhold_2017_married" model="hr.salary.rule">
|
||||||
|
<field name="sequence" eval="145"/>
|
||||||
|
<field name="category_id" ref="hr_payroll_fed_income_withhold"/>
|
||||||
|
<field name="name">Federal Income Withholding - Married (2017)</field>
|
||||||
|
<field name="code">FED_INC_WITHHOLD_2017_M</field>
|
||||||
|
<field name="condition_select">python</field>
|
||||||
|
<field name="condition_python">result = (payslip.date_to[:4] == '2017' and contract.w4_filing_status == 'married')</field>
|
||||||
|
<field name="amount_select">code</field>
|
||||||
|
<field name="amount_python_compute">
|
||||||
|
wages = categories.GROSS
|
||||||
|
allowances = contract.w4_allowances
|
||||||
|
is_nra = contract.w4_is_nonresident_alien
|
||||||
|
schedule_pay = contract.schedule_pay
|
||||||
|
val = 0.00
|
||||||
|
additional = contract.w4_additional_withholding
|
||||||
|
|
||||||
|
###
|
||||||
|
# Married WEEKLY
|
||||||
|
###
|
||||||
|
if 'weekly' == schedule_pay:
|
||||||
|
wages -= allowances * 77.90
|
||||||
|
if is_nra:
|
||||||
|
wages += 44.20
|
||||||
|
|
||||||
|
if wages > 166 and wages <= 525:
|
||||||
|
val = 0.00 + ((wages - 166) * 0.10)
|
||||||
|
|
||||||
|
elif wages > 525 and wages <= 1626:
|
||||||
|
val = 35.90 + ((wages - 525) * 0.15)
|
||||||
|
|
||||||
|
elif wages > 1626 and wages <= 3111:
|
||||||
|
val = 201.05 + ((wages - 1626) * 0.25)
|
||||||
|
|
||||||
|
elif wages > 3111 and wages <= 4654:
|
||||||
|
val = 572.30 + ((wages - 3111) * 0.28)
|
||||||
|
|
||||||
|
elif wages > 4654 and wages <= 8180:
|
||||||
|
val = 1004.34 + ((wages - 4654) * 0.33)
|
||||||
|
|
||||||
|
elif wages > 8180 and wages <= 9218:
|
||||||
|
val = 2167.92 + ((wages - 8180) * 0.35)
|
||||||
|
|
||||||
|
elif wages > 9218:
|
||||||
|
val = 2531.22 + ((wages - 9218) * 0.396)
|
||||||
|
|
||||||
|
###
|
||||||
|
# Married BIWEEKLY
|
||||||
|
###
|
||||||
|
elif 'bi-weekly' == schedule_pay:
|
||||||
|
wages -= allowances * 155.80
|
||||||
|
if is_nra:
|
||||||
|
wages += 88.50
|
||||||
|
|
||||||
|
if wages > 333 and wages <= 1050:
|
||||||
|
val = 0.00 + ((wages - 333) * 0.10)
|
||||||
|
|
||||||
|
elif wages > 1050 and wages <= 3252:
|
||||||
|
val = 71.70 + ((wages - 1050) * 0.15)
|
||||||
|
|
||||||
|
elif wages > 3252 and wages <= 6221:
|
||||||
|
val = 402.00 + ((wages - 3252) * 0.25)
|
||||||
|
|
||||||
|
elif wages > 6221 and wages <= 9308:
|
||||||
|
val = 1144.25 + ((wages - 6221) * 0.28)
|
||||||
|
|
||||||
|
elif wages > 9308 and wages <= 16360:
|
||||||
|
val = 2008.61 + ((wages - 9308) * 0.33)
|
||||||
|
|
||||||
|
elif wages > 16360 and wages <= 18437:
|
||||||
|
val = 4335.77 + ((wages - 16360) * 0.35)
|
||||||
|
|
||||||
|
elif wages > 18437:
|
||||||
|
val = 5062.72 + ((wages - 18437) * 0.396)
|
||||||
|
|
||||||
|
###
|
||||||
|
# Married SEMIMONTHLY
|
||||||
|
###
|
||||||
|
elif 'semi-monthly' == schedule_pay:
|
||||||
|
wages -= allowances * 168.80
|
||||||
|
if is_nra:
|
||||||
|
wages += 95.80
|
||||||
|
|
||||||
|
if wages > 360 and wages <= 1138:
|
||||||
|
val = 0.00 + ((wages - 360) * 0.10)
|
||||||
|
|
||||||
|
elif wages > 1138 and wages <= 3523:
|
||||||
|
val = 77.80 + ((wages - 1138) * 0.15)
|
||||||
|
|
||||||
|
elif wages > 3523 and wages <= 6740:
|
||||||
|
val = 435.55 + ((wages - 3523) * 0.25)
|
||||||
|
|
||||||
|
elif wages > 6740 and wages <= 10083:
|
||||||
|
val = 1239.80 + ((wages - 6740) * 0.28)
|
||||||
|
|
||||||
|
elif wages > 10083 and wages <= 17723:
|
||||||
|
val = 2175.84 + ((wages - 10083) * 0.33)
|
||||||
|
|
||||||
|
elif wages > 17723 and wages <= 19973:
|
||||||
|
val = 4697.04 + ((wages - 17723) * 0.35)
|
||||||
|
|
||||||
|
elif wages > 19973:
|
||||||
|
val = 5484.54 + ((wages - 19973) * 0.396)
|
||||||
|
|
||||||
|
###
|
||||||
|
# Married MONTHLY
|
||||||
|
###
|
||||||
|
elif 'monthly' == schedule_pay:
|
||||||
|
wages -= allowances * 337.50
|
||||||
|
if is_nra:
|
||||||
|
wages += 191.70
|
||||||
|
|
||||||
|
if wages > 721 and wages <= 2275:
|
||||||
|
val = 0.00 + ((wages - 721) * 0.10)
|
||||||
|
|
||||||
|
elif wages > 2275 and wages <= 7046:
|
||||||
|
val = 155.40 + ((wages - 2275) * 0.15)
|
||||||
|
|
||||||
|
elif wages > 7046 and wages <= 13479:
|
||||||
|
val = 871.05 + ((wages - 7046) * 0.25)
|
||||||
|
|
||||||
|
elif wages > 13479 and wages <= 20167:
|
||||||
|
val = 2479.30 + ((wages - 13479) * 0.28)
|
||||||
|
|
||||||
|
elif wages > 20167 and wages <= 35446:
|
||||||
|
val = 4351.94 + ((wages - 20167) * 0.33)
|
||||||
|
|
||||||
|
elif wages > 35446 and wages <= 39946:
|
||||||
|
val = 9394.01 + ((wages - 35446) * 0.35)
|
||||||
|
|
||||||
|
elif wages > 39946:
|
||||||
|
val = 10969.01 + ((wages - 39946) * 0.396)
|
||||||
|
|
||||||
|
###
|
||||||
|
# Married QUARTERLY
|
||||||
|
###
|
||||||
|
elif 'quarterly' == schedule_pay:
|
||||||
|
wages -= allowances * 1012.50
|
||||||
|
if is_nra:
|
||||||
|
wages += 575.00
|
||||||
|
|
||||||
|
if wages > 2163 and wages <= 6825:
|
||||||
|
val = 0.00 + ((wages - 2163) * 0.10)
|
||||||
|
|
||||||
|
elif wages > 6825 and wages <= 21138:
|
||||||
|
val = 466.20 + ((wages - 6825) * 0.15)
|
||||||
|
|
||||||
|
elif wages > 21138 and wages <= 40438:
|
||||||
|
val = 2613.15 + ((wages - 21138) * 0.25)
|
||||||
|
|
||||||
|
elif wages > 40438 and wages <= 60500:
|
||||||
|
val = 7438.15 + ((wages - 40438) * 0.28)
|
||||||
|
|
||||||
|
elif wages > 60500 and wages <= 106338:
|
||||||
|
val = 13055.51 + ((wages - 60500) * 0.33)
|
||||||
|
|
||||||
|
elif wages > 106338 and wages <= 119838:
|
||||||
|
val = 28182.05 + ((wages - 106338) * 0.35)
|
||||||
|
|
||||||
|
elif wages > 119838:
|
||||||
|
val = 32907.05 + ((wages - 119838) * 0.396)
|
||||||
|
|
||||||
|
###
|
||||||
|
# Married SEMIANNUAL
|
||||||
|
###
|
||||||
|
elif 'semi-annually' == schedule_pay:
|
||||||
|
wages -= allowances * 2025.00
|
||||||
|
if is_nra:
|
||||||
|
wages += 1150.0
|
||||||
|
|
||||||
|
if wages > 4325 and wages <= 13650:
|
||||||
|
val = 0.00 + ((wages - 4325) * 0.10)
|
||||||
|
|
||||||
|
elif wages > 13650 and wages <= 42275:
|
||||||
|
val = 932.50 + ((wages - 13650) * 0.15)
|
||||||
|
|
||||||
|
elif wages > 42275 and wages <= 80875:
|
||||||
|
val = 5226.25 + ((wages - 42275) * 0.25)
|
||||||
|
|
||||||
|
elif wages > 80875 and wages <= 121000:
|
||||||
|
val = 14876.25 + ((wages - 80875) * 0.28)
|
||||||
|
|
||||||
|
elif wages > 121000 and wages <= 212675:
|
||||||
|
val = 26111.25 + ((wages - 121000) * 0.33)
|
||||||
|
|
||||||
|
elif wages > 212675 and wages <= 239675:
|
||||||
|
val = 56364.00 + ((wages - 212675) * 0.35)
|
||||||
|
|
||||||
|
elif wages > 239675:
|
||||||
|
val = 65814.00 + ((wages - 239675) * 0.396)
|
||||||
|
|
||||||
|
###
|
||||||
|
# Married ANNUAL
|
||||||
|
###
|
||||||
|
elif 'annually' == schedule_pay:
|
||||||
|
wages -= allowances * 4050.00
|
||||||
|
if is_nra:
|
||||||
|
wages += 2300.0
|
||||||
|
|
||||||
|
if wages > 8650 and wages <= 27300:
|
||||||
|
val = 0.00 + ((wages - 8650) * 0.10)
|
||||||
|
|
||||||
|
elif wages > 27300 and wages <= 84550:
|
||||||
|
val = 1865.00 + ((wages - 27300) * 0.15)
|
||||||
|
|
||||||
|
elif wages > 84550 and wages <= 161750:
|
||||||
|
val = 10452.50 + ((wages - 84550) * 0.25)
|
||||||
|
|
||||||
|
elif wages > 161750 and wages <= 242000:
|
||||||
|
val = 29752.50 + ((wages - 161750) * 0.28)
|
||||||
|
|
||||||
|
elif wages > 242000 and wages <= 425350:
|
||||||
|
val = 52222.50 + ((wages - 242000) * 0.33)
|
||||||
|
|
||||||
|
elif wages > 425350 and wages <= 479350:
|
||||||
|
val = 112728.00 + ((wages - 425350) * 0.35)
|
||||||
|
|
||||||
|
elif wages > 479350:
|
||||||
|
val = 131628.00 + ((wages - 479350) * 0.396)
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise Exception('Invalid schedule_pay="' + schedule_pay + '" for W4 Allowance calculation')
|
||||||
|
|
||||||
|
result = -(val + additional)
|
||||||
|
</field>
|
||||||
|
<field name="register_id" ref="contrib_register_eftps_941"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="hr_payroll_rules_futa_wages_2017" model="hr.salary.rule">
|
||||||
|
<field name="sequence" eval="422"/>
|
||||||
|
<field name="category_id" ref="hr_payroll_futa_wages"/>
|
||||||
|
<field name="name">FUTA Federal Unemployment - Wages (2017)</field>
|
||||||
|
<field name="code">FUTA_WAGES_2017</field>
|
||||||
|
<field name="condition_select">python</field>
|
||||||
|
<field name="condition_python">result = (payslip.date_to[:4] == '2017' and contract.futa_type != contract.FUTA_TYPE_EXEMPT)</field>
|
||||||
|
<field name="amount_select">code</field>
|
||||||
|
<field name="amount_python_compute">
|
||||||
|
###
|
||||||
|
ytd = payslip.sum('FUTA_WAGES_2017', '2017-01-01', '2018-01-01')
|
||||||
|
ytd += contract.external_wages
|
||||||
|
remaining = 7000.0 - ytd
|
||||||
|
if remaining <= 0.0:
|
||||||
|
result = 0
|
||||||
|
elif remaining < categories.GROSS:
|
||||||
|
result = remaining
|
||||||
|
else:
|
||||||
|
result = categories.GROSS
|
||||||
|
</field>
|
||||||
|
<field name="appears_on_payslip" eval="False"/>
|
||||||
|
</record>
|
||||||
|
<record id="hr_payroll_rules_futa_2017" model="hr.salary.rule">
|
||||||
|
<field name="sequence" eval="442"/>
|
||||||
|
<field name="category_id" ref="hr_payroll_futa"/>
|
||||||
|
<field name="name">FUTA Federal Unemployment (2017)</field>
|
||||||
|
<field name="code">FUTA_2017</field>
|
||||||
|
<field name="condition_select">python</field>
|
||||||
|
<field name="condition_python">result = (payslip.date_to[:4] == '2017' and contract.futa_type != contract.FUTA_TYPE_EXEMPT)</field>
|
||||||
|
<field name="amount_select">code</field>
|
||||||
|
<field name="amount_python_compute">
|
||||||
|
result_rate = -(contract.futa_rate(2017))
|
||||||
|
result = categories.FUTA_WAGES
|
||||||
|
</field>
|
||||||
|
<field name="register_id" ref="contrib_register_eftps_940"/>
|
||||||
|
<field name="appears_on_payslip" eval="False"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</data>
|
||||||
|
</odoo>
|
||||||
604
l10n_us_hr_payroll/data/rules_2018.xml
Executable file
604
l10n_us_hr_payroll/data/rules_2018.xml
Executable file
@@ -0,0 +1,604 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<data>
|
||||||
|
|
||||||
|
<!-- HR SALARY RULES-->
|
||||||
|
<!-- Employee Deductions and Calculations -->
|
||||||
|
<record id="hr_payroll_rules_fica_emp_ss_wages_2018" model="hr.salary.rule">
|
||||||
|
<field name="sequence" eval="120"/>
|
||||||
|
<field name="category_id" ref="hr_payroll_fica_emp_ss_wages"/>
|
||||||
|
<field name="name">FICA Employee Social Security Wages (2018)</field>
|
||||||
|
<field name="code">FICA_EMP_SS_WAGES_2018</field>
|
||||||
|
<field name="condition_select">python</field>
|
||||||
|
<field name="condition_python">result = (payslip.date_to[:4] == '2018')</field>
|
||||||
|
<field name="amount_select">code</field>
|
||||||
|
<field name="amount_python_compute">
|
||||||
|
###
|
||||||
|
ytd = payslip.sum('FICA_EMP_SS_WAGES_2018', '2018-01-01', '2019-01-01')
|
||||||
|
ytd += contract.external_wages
|
||||||
|
remaining = 128400.0 - ytd
|
||||||
|
if remaining <= 0.0:
|
||||||
|
result = 0
|
||||||
|
elif remaining < categories.GROSS:
|
||||||
|
result = remaining
|
||||||
|
else:
|
||||||
|
result = categories.GROSS
|
||||||
|
</field>
|
||||||
|
<field name="appears_on_payslip" eval="False"/>
|
||||||
|
</record>
|
||||||
|
<record id="hr_payroll_rules_fica_emp_m_wages_2018" model="hr.salary.rule">
|
||||||
|
<field name="sequence" eval="121"/>
|
||||||
|
<field name="category_id" ref="hr_payroll_fica_emp_m_wages"/>
|
||||||
|
<field name="name">FICA Employee Medicare Wages (2018)</field>
|
||||||
|
<field name="code">FICA_EMP_M_WAGES_2018</field>
|
||||||
|
<field name="condition_select">python</field>
|
||||||
|
<field name="condition_python">result = (payslip.date_to[:4] == '2018')</field>
|
||||||
|
<field name="amount_select">code</field>
|
||||||
|
<field name="amount_python_compute">result = categories.GROSS</field>
|
||||||
|
<field name="appears_on_payslip" eval="False"/>
|
||||||
|
</record>
|
||||||
|
<record id="hr_payroll_rules_fica_emp_m_add_wages_2018" model="hr.salary.rule">
|
||||||
|
<field name="sequence" eval="122"/>
|
||||||
|
<field name="category_id" ref="hr_payroll_fica_emp_m_add_wages"/>
|
||||||
|
<field name="name">FICA Employee Medicare Additional Wages (2018)</field>
|
||||||
|
<field name="code">FICA_EMP_M_ADD_WAGES_2018</field>
|
||||||
|
<field name="condition_select">python</field>
|
||||||
|
<field name="condition_python">result = (payslip.date_to[:4] == '2018')</field>
|
||||||
|
<field name="amount_select">code</field>
|
||||||
|
<field name="amount_python_compute">
|
||||||
|
###
|
||||||
|
ADD_M = 200000.0
|
||||||
|
norm_med_ytd = payslip.sum('FICA_EMP_M_WAGES_2018', '2018-01-01', '2019-01-01')
|
||||||
|
norm_med_cur = categories.FICA_EMP_M_WAGES
|
||||||
|
if ADD_M > norm_med_ytd:
|
||||||
|
diff = ADD_M - norm_med_ytd
|
||||||
|
if norm_med_cur > diff:
|
||||||
|
result = norm_med_cur - diff
|
||||||
|
else:
|
||||||
|
result = 0 # normal condition
|
||||||
|
else:
|
||||||
|
result = norm_med_cur # after YTD wages have passed the max
|
||||||
|
</field>
|
||||||
|
<field name="appears_on_payslip" eval="False"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="hr_payroll_rules_fica_emp_ss_2018" model="hr.salary.rule">
|
||||||
|
<field name="sequence" eval="140"/>
|
||||||
|
<field name="category_id" ref="hr_payroll_fica_emp_ss"/>
|
||||||
|
<field name="name">FICA Employee Social Security (2018)</field>
|
||||||
|
<field name="code">FICA_EMP_SS_2018</field>
|
||||||
|
<field name="condition_select">python</field>
|
||||||
|
<field name="condition_python">result = (payslip.date_to[:4] == '2018')</field>
|
||||||
|
<field name="amount_select">code</field>
|
||||||
|
<field name="amount_python_compute">
|
||||||
|
### this should be "rules.FICA_EMP_SS_WAGES_2018", but it doesn't work
|
||||||
|
result_rate = -6.2
|
||||||
|
result = categories.FICA_EMP_SS_WAGES
|
||||||
|
</field>
|
||||||
|
<field name="register_id" ref="contrib_register_eftps_941"/>
|
||||||
|
</record>
|
||||||
|
<record id="hr_payroll_rules_fica_emp_m_2018" model="hr.salary.rule">
|
||||||
|
<field name="sequence" eval="141"/>
|
||||||
|
<field name="category_id" ref="hr_payroll_fica_emp_m"/>
|
||||||
|
<field name="name">FICA Employee Medicare (2018)</field>
|
||||||
|
<field name="code">FICA_EMP_M_2018</field>
|
||||||
|
<field name="condition_select">python</field>
|
||||||
|
<field name="condition_python">result = (payslip.date_to[:4] == '2018')</field>
|
||||||
|
<field name="amount_select">code</field>
|
||||||
|
<field name="amount_python_compute">
|
||||||
|
### this should be "rules.FICA_EMP_M_WAGES_2018", but it doesn't work
|
||||||
|
result_rate = -1.45
|
||||||
|
result = categories.FICA_EMP_M_WAGES
|
||||||
|
</field>
|
||||||
|
<field name="register_id" ref="contrib_register_eftps_941"/>
|
||||||
|
</record>
|
||||||
|
<record id="hr_payroll_rules_fica_emp_m_add_2018" model="hr.salary.rule">
|
||||||
|
<field name="sequence" eval="142"/>
|
||||||
|
<field name="category_id" ref="hr_payroll_fica_emp_m_add"/>
|
||||||
|
<field name="name">FICA Employee Medicare Additional (2018)</field>
|
||||||
|
<field name="code">FICA_EMP_M_ADD_2018</field>
|
||||||
|
<field name="condition_select">python</field>
|
||||||
|
<field name="condition_python">result = (payslip.date_to[:4] == '2018')</field>
|
||||||
|
<field name="amount_select">code</field>
|
||||||
|
<field name="amount_python_compute">
|
||||||
|
### this should be "rules.FICA_EMP_M_ADD_WAGES_2018", but it doesn't work
|
||||||
|
result_rate = -0.9
|
||||||
|
result = categories.FICA_EMP_M_ADD_WAGES
|
||||||
|
</field>
|
||||||
|
<field name="register_id" ref="contrib_register_eftps_941"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Federal Income Tax Withholding -->
|
||||||
|
<record id="hr_payroll_rules_fed_inc_withhold_2018_single" model="hr.salary.rule">
|
||||||
|
<field name="sequence" eval="145"/>
|
||||||
|
<field name="category_id" ref="hr_payroll_fed_income_withhold"/>
|
||||||
|
<field name="name">Federal Income Withholding - Single (2018)</field>
|
||||||
|
<field name="code">FED_INC_WITHHOLD_2018_S</field>
|
||||||
|
<field name="condition_select">python</field>
|
||||||
|
<field name="condition_python">result = (payslip.date_to[:4] == '2018' and contract.w4_filing_status != 'married' and contract.w4_filing_status)</field>
|
||||||
|
<field name="amount_select">code</field>
|
||||||
|
<field name="amount_python_compute">
|
||||||
|
wages = categories.GROSS
|
||||||
|
allowances = contract.w4_allowances
|
||||||
|
is_nra = contract.w4_is_nonresident_alien
|
||||||
|
schedule_pay = contract.schedule_pay
|
||||||
|
val = 0.00
|
||||||
|
additional = contract.w4_additional_withholding
|
||||||
|
|
||||||
|
###
|
||||||
|
# Single WEEKLY
|
||||||
|
###
|
||||||
|
if 'weekly' == schedule_pay:
|
||||||
|
wages -= allowances * 79.80
|
||||||
|
if is_nra:
|
||||||
|
wages += 151.00
|
||||||
|
|
||||||
|
if wages > 71 and wages <= 254:
|
||||||
|
val = 0.00 + ((wages - 71) * 0.10)
|
||||||
|
|
||||||
|
elif wages > 254 and wages <= 815:
|
||||||
|
val = 18.30 + ((wages - 254) * 0.12)
|
||||||
|
|
||||||
|
elif wages > 815 and wages <= 1658:
|
||||||
|
val = 85.62 + ((wages - 815) * 0.22)
|
||||||
|
|
||||||
|
elif wages > 1658 and wages <= 3100:
|
||||||
|
val = 271.08 + ((wages - 1658) * 0.24)
|
||||||
|
|
||||||
|
elif wages > 3100 and wages <= 3917:
|
||||||
|
val = 617.16 + ((wages - 3100) * 0.32)
|
||||||
|
|
||||||
|
elif wages > 3917 and wages <= 9687:
|
||||||
|
val = 878.60 + ((wages - 3917) * 0.35)
|
||||||
|
|
||||||
|
elif wages > 9687:
|
||||||
|
val = 2898.10 + ((wages - 9687) * 0.37)
|
||||||
|
|
||||||
|
###
|
||||||
|
# Single BIWEEKLY
|
||||||
|
###
|
||||||
|
elif 'bi-weekly' == schedule_pay:
|
||||||
|
wages -= allowances * 159.60
|
||||||
|
if is_nra:
|
||||||
|
wages += 301.90
|
||||||
|
|
||||||
|
if wages > 142 and wages <= 509:
|
||||||
|
val = 0.00 + ((wages - 142) * 0.10)
|
||||||
|
|
||||||
|
elif wages > 509 and wages <= 1631:
|
||||||
|
val = 36.70 + ((wages - 509) * 0.12)
|
||||||
|
|
||||||
|
elif wages > 1631 and wages <= 3315:
|
||||||
|
val = 171.34 + ((wages - 1631) * 0.22)
|
||||||
|
|
||||||
|
elif wages > 3315 and wages <= 6200:
|
||||||
|
val = 541.82 + ((wages - 3315) * 0.24)
|
||||||
|
|
||||||
|
elif wages > 6200 and wages <= 7835:
|
||||||
|
val = 1234.22 + ((wages - 6200) * 0.32)
|
||||||
|
|
||||||
|
elif wages > 7835 and wages <= 19373:
|
||||||
|
val = 1757.42 + ((wages - 7835) * 0.35)
|
||||||
|
|
||||||
|
elif wages > 19373:
|
||||||
|
val = 5795.72 + ((wages - 19373) * 0.37)
|
||||||
|
|
||||||
|
###
|
||||||
|
# Single SEMIMONTHLY
|
||||||
|
###
|
||||||
|
elif 'semi-monthly' == schedule_pay:
|
||||||
|
wages -= allowances * 172.90
|
||||||
|
if is_nra:
|
||||||
|
wages += 327.10
|
||||||
|
|
||||||
|
if wages > 154 and wages <= 551:
|
||||||
|
val = 0.00 + ((wages - 154) * 0.10)
|
||||||
|
|
||||||
|
elif wages > 551 and wages <= 1767:
|
||||||
|
val = 39.70 + ((wages - 551) * 0.12)
|
||||||
|
|
||||||
|
elif wages > 1767 and wages <= 3592:
|
||||||
|
val = 185.62 + ((wages - 1767) * 0.22)
|
||||||
|
|
||||||
|
elif wages > 3592 and wages <= 6717:
|
||||||
|
val = 587.12 + ((wages - 3592) * 0.24)
|
||||||
|
|
||||||
|
elif wages > 6717 and wages <= 8488:
|
||||||
|
val = 1337.12 + ((wages - 6717) * 0.32)
|
||||||
|
|
||||||
|
elif wages > 8488 and wages <= 20988:
|
||||||
|
val = 1903.84 + ((wages - 8488) * 0.35)
|
||||||
|
|
||||||
|
elif wages > 20988:
|
||||||
|
val = 6278.84 + ((wages - 20988) * 0.37)
|
||||||
|
|
||||||
|
###
|
||||||
|
# Single MONTHLY
|
||||||
|
###
|
||||||
|
elif 'monthly' == schedule_pay:
|
||||||
|
wages -= allowances * 345.80
|
||||||
|
if is_nra:
|
||||||
|
wages += 654.20
|
||||||
|
|
||||||
|
if wages > 308 and wages <= 1102:
|
||||||
|
val = 0.00 + ((wages - 308) * 0.10)
|
||||||
|
|
||||||
|
elif wages > 1102 and wages <= 3533:
|
||||||
|
val = 79.40 + ((wages - 1102) * 0.12)
|
||||||
|
|
||||||
|
elif wages > 3533 and wages <= 7183:
|
||||||
|
val = 371.12 + ((wages - 3533) * 0.22)
|
||||||
|
|
||||||
|
elif wages > 7183 and wages <= 13433:
|
||||||
|
val = 1174.12 + ((wages - 7183) * 0.24)
|
||||||
|
|
||||||
|
elif wages > 13433 and wages <= 16975:
|
||||||
|
val = 2674.12 + ((wages - 13433) * 0.32)
|
||||||
|
|
||||||
|
elif wages > 16975 and wages <= 41975:
|
||||||
|
val = 3807.56 + ((wages - 16975) * 0.35)
|
||||||
|
|
||||||
|
elif wages > 41975:
|
||||||
|
val = 12557.56 + ((wages - 41975) * 0.37)
|
||||||
|
|
||||||
|
###
|
||||||
|
# Single QUARTERLY
|
||||||
|
###
|
||||||
|
elif 'quarterly' == schedule_pay:
|
||||||
|
wages -= allowances * 1037.50
|
||||||
|
if is_nra:
|
||||||
|
wages += 1962.50
|
||||||
|
|
||||||
|
if wages > 925 and wages <= 3306:
|
||||||
|
val = 0.00 + ((wages - 925) * 0.10)
|
||||||
|
|
||||||
|
elif wages > 3306 and wages <= 10600:
|
||||||
|
val = 238.10 + ((wages - 3306) * 0.12)
|
||||||
|
|
||||||
|
elif wages > 10600 and wages <= 21550:
|
||||||
|
val = 1113.38 + ((wages - 10600) * 0.22)
|
||||||
|
|
||||||
|
elif wages > 21550 and wages <= 40300:
|
||||||
|
val = 3522.38 + ((wages - 21550) * 0.24)
|
||||||
|
|
||||||
|
elif wages > 40300 and wages <= 50925:
|
||||||
|
val = 8022.38 + ((wages - 40300) * 0.32)
|
||||||
|
|
||||||
|
elif wages > 50925 and wages <= 125925:
|
||||||
|
val = 11422.38 + ((wages - 50925) * 0.35)
|
||||||
|
|
||||||
|
elif wages > 125925:
|
||||||
|
val = 37672.38 + ((wages - 125925) * 0.37)
|
||||||
|
|
||||||
|
###
|
||||||
|
# Single SEMIANNUAL
|
||||||
|
###
|
||||||
|
elif 'semi-annually' == schedule_pay:
|
||||||
|
wages -= allowances * 2075.00
|
||||||
|
if is_nra:
|
||||||
|
wages += 3925.00
|
||||||
|
|
||||||
|
if wages > 1850 and wages <= 6613:
|
||||||
|
val = 0.00 + ((wages - 1850) * 0.10)
|
||||||
|
|
||||||
|
elif wages > 6613 and wages <= 21200:
|
||||||
|
val = 476.30 + ((wages - 6613) * 0.12)
|
||||||
|
|
||||||
|
elif wages > 21200 and wages <= 43100:
|
||||||
|
val = 2226.74 + ((wages - 21200) * 0.22)
|
||||||
|
|
||||||
|
elif wages > 43100 and wages <= 80600:
|
||||||
|
val = 7044.74 + ((wages - 43100) * 0.24)
|
||||||
|
|
||||||
|
elif wages > 80600 and wages <= 101850:
|
||||||
|
val = 16044.74 + ((wages - 80600) * 0.32)
|
||||||
|
|
||||||
|
elif wages > 101850 and wages <= 251850:
|
||||||
|
val = 22844.74 + ((wages - 101850) * 0.35)
|
||||||
|
|
||||||
|
elif wages > 251850:
|
||||||
|
val = 75344.74 + ((wages - 251850) * 0.37)
|
||||||
|
|
||||||
|
###
|
||||||
|
# Single ANNUAL
|
||||||
|
###
|
||||||
|
elif 'annually' == schedule_pay:
|
||||||
|
wages -= allowances * 4150.00
|
||||||
|
if is_nra:
|
||||||
|
wages += 7850.00
|
||||||
|
|
||||||
|
if wages > 3700 and wages <= 13225:
|
||||||
|
val = 0.00 + ((wages - 3700) * 0.10)
|
||||||
|
|
||||||
|
elif wages > 13225 and wages <= 42400:
|
||||||
|
val = 952.50 + ((wages - 13225) * 0.12)
|
||||||
|
|
||||||
|
elif wages > 42400 and wages <= 86200:
|
||||||
|
val = 4453.50 + ((wages - 42400) * 0.22)
|
||||||
|
|
||||||
|
elif wages > 86200 and wages <= 161200:
|
||||||
|
val = 14089.50 + ((wages - 86200) * 0.24)
|
||||||
|
|
||||||
|
elif wages > 161200 and wages <= 203700:
|
||||||
|
val = 32089.50 + ((wages - 161200) * 0.32)
|
||||||
|
|
||||||
|
elif wages > 203700 and wages <= 503700:
|
||||||
|
val = 45689.50 + ((wages - 203700) * 0.35)
|
||||||
|
|
||||||
|
elif wages > 503700:
|
||||||
|
val = 150689.50 + ((wages - 503700) * 0.37)
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise Exception('Invalid schedule_pay="' + schedule_pay + '" for W4 Allowance calculation')
|
||||||
|
|
||||||
|
result = -(val + additional)
|
||||||
|
</field>
|
||||||
|
<field name="register_id" ref="contrib_register_eftps_941"/>
|
||||||
|
</record>
|
||||||
|
<record id="hr_payroll_rules_fed_inc_withhold_2018_married" model="hr.salary.rule">
|
||||||
|
<field name="sequence" eval="145"/>
|
||||||
|
<field name="category_id" ref="hr_payroll_fed_income_withhold"/>
|
||||||
|
<field name="name">Federal Income Withholding - Married (2018)</field>
|
||||||
|
<field name="code">FED_INC_WITHHOLD_2018_M</field>
|
||||||
|
<field name="condition_select">python</field>
|
||||||
|
<field name="condition_python">result = (payslip.date_to[:4] == '2018' and contract.w4_filing_status == 'married')</field>
|
||||||
|
<field name="amount_select">code</field>
|
||||||
|
<field name="amount_python_compute">
|
||||||
|
wages = categories.GROSS
|
||||||
|
allowances = contract.w4_allowances
|
||||||
|
is_nra = contract.w4_is_nonresident_alien
|
||||||
|
schedule_pay = contract.schedule_pay
|
||||||
|
val = 0.00
|
||||||
|
additional = contract.w4_additional_withholding
|
||||||
|
|
||||||
|
###
|
||||||
|
# Married WEEKLY
|
||||||
|
###
|
||||||
|
if 'weekly' == schedule_pay:
|
||||||
|
wages -= allowances * 79.80
|
||||||
|
if is_nra:
|
||||||
|
wages += 151.00
|
||||||
|
|
||||||
|
if wages > 222 and wages <= 588:
|
||||||
|
val = 0.00 + ((wages - 222) * 0.10)
|
||||||
|
|
||||||
|
elif wages > 588 and wages <= 1711:
|
||||||
|
val = 36.60 + ((wages - 588) * 0.12)
|
||||||
|
|
||||||
|
elif wages > 1711 and wages <= 3395:
|
||||||
|
val = 171.36 + ((wages - 1711) * 0.22)
|
||||||
|
|
||||||
|
elif wages > 3395 and wages <= 6280:
|
||||||
|
val = 541.84 + ((wages - 3395) * 0.24)
|
||||||
|
|
||||||
|
elif wages > 6280 and wages <= 7914:
|
||||||
|
val = 1234.24 + ((wages - 6280) * 0.32)
|
||||||
|
|
||||||
|
elif wages > 7914 and wages <= 11761:
|
||||||
|
val = 1757.12 + ((wages - 7914) * 0.35)
|
||||||
|
|
||||||
|
elif wages > 11761:
|
||||||
|
val = 3103.57 + ((wages - 11761) * 0.37)
|
||||||
|
|
||||||
|
###
|
||||||
|
# Married BIWEEKLY
|
||||||
|
###
|
||||||
|
elif 'bi-weekly' == schedule_pay:
|
||||||
|
wages -= allowances * 159.60
|
||||||
|
if is_nra:
|
||||||
|
wages += 301.90
|
||||||
|
|
||||||
|
if wages > 444 and wages <= 1177:
|
||||||
|
val = 0.00 + ((wages - 444) * 0.10)
|
||||||
|
|
||||||
|
elif wages > 1177 and wages <= 3421:
|
||||||
|
val = 73.30 + ((wages - 1177) * 0.12)
|
||||||
|
|
||||||
|
elif wages > 3421 and wages <= 6790:
|
||||||
|
val = 342.58 + ((wages - 3421) * 0.22)
|
||||||
|
|
||||||
|
elif wages > 6790 and wages <= 12560:
|
||||||
|
val = 1083.76 + ((wages - 6790) * 0.24)
|
||||||
|
|
||||||
|
elif wages > 12560 and wages <= 15829:
|
||||||
|
val = 2468.56 + ((wages - 12560) * 0.32)
|
||||||
|
|
||||||
|
elif wages > 15829 and wages <= 23521:
|
||||||
|
val = 3514.64 + ((wages - 15829) * 0.35)
|
||||||
|
|
||||||
|
elif wages > 23521:
|
||||||
|
val = 6206.84 + ((wages - 23521) * 0.37)
|
||||||
|
|
||||||
|
###
|
||||||
|
# Married SEMIMONTHLY
|
||||||
|
###
|
||||||
|
elif 'semi-monthly' == schedule_pay:
|
||||||
|
wages -= allowances * 172.90
|
||||||
|
if is_nra:
|
||||||
|
wages += 327.10
|
||||||
|
|
||||||
|
if wages > 481 and wages <= 1275:
|
||||||
|
val = 0.00 + ((wages - 481) * 0.10)
|
||||||
|
|
||||||
|
elif wages > 1275 and wages <= 3706:
|
||||||
|
val = 79.40 + ((wages - 1275) * 0.12)
|
||||||
|
|
||||||
|
elif wages > 3706 and wages <= 7356:
|
||||||
|
val = 371.12 + ((wages - 3706) * 0.22)
|
||||||
|
|
||||||
|
elif wages > 7356 and wages <= 13606:
|
||||||
|
val = 1174.12 + ((wages - 7356) * 0.24)
|
||||||
|
|
||||||
|
elif wages > 13606 and wages <= 17148:
|
||||||
|
val = 2674.12 + ((wages - 13606) * 0.32)
|
||||||
|
|
||||||
|
elif wages > 17148 and wages <= 25481:
|
||||||
|
val = 3807.56 + ((wages - 17148) * 0.35)
|
||||||
|
|
||||||
|
elif wages > 25481:
|
||||||
|
val = 6724.11 + ((wages - 25481) * 0.37)
|
||||||
|
|
||||||
|
###
|
||||||
|
# Married MONTHLY
|
||||||
|
###
|
||||||
|
elif 'monthly' == schedule_pay:
|
||||||
|
wages -= allowances * 345.80
|
||||||
|
if is_nra:
|
||||||
|
wages += 654.20
|
||||||
|
|
||||||
|
if wages > 963 and wages <= 2550:
|
||||||
|
val = 0.00 + ((wages - 963) * 0.10)
|
||||||
|
|
||||||
|
elif wages > 2550 and wages <= 7413:
|
||||||
|
val = 158.70 + ((wages - 2550) * 0.12)
|
||||||
|
|
||||||
|
elif wages > 7413 and wages <= 14713:
|
||||||
|
val = 742.26 + ((wages - 7413) * 0.22)
|
||||||
|
|
||||||
|
elif wages > 14713 and wages <= 27213:
|
||||||
|
val = 2348.26 + ((wages - 14713) * 0.24)
|
||||||
|
|
||||||
|
elif wages > 27213 and wages <= 34296:
|
||||||
|
val = 5348.26 + ((wages - 27213) * 0.32)
|
||||||
|
|
||||||
|
elif wages > 34296 and wages <= 50963:
|
||||||
|
val = 7614.82 + ((wages - 34296) * 0.35)
|
||||||
|
|
||||||
|
elif wages > 50963:
|
||||||
|
val = 13448.27 + ((wages - 50963) * 0.37)
|
||||||
|
|
||||||
|
###
|
||||||
|
# Married QUARTERLY
|
||||||
|
###
|
||||||
|
elif 'quarterly' == schedule_pay:
|
||||||
|
wages -= allowances * 1037.50
|
||||||
|
if is_nra:
|
||||||
|
wages += 1962.50
|
||||||
|
|
||||||
|
if wages > 2888 and wages <= 7650:
|
||||||
|
val = 0.00 + ((wages - 2888) * 0.10)
|
||||||
|
|
||||||
|
elif wages > 7650 and wages <= 22238:
|
||||||
|
val = 476.20 + ((wages - 7650) * 0.12)
|
||||||
|
|
||||||
|
elif wages > 22238 and wages <= 44138:
|
||||||
|
val = 2226.76 + ((wages - 22238) * 0.22)
|
||||||
|
|
||||||
|
elif wages > 44138 and wages <= 81638:
|
||||||
|
val = 7044.76 + ((wages - 44138) * 0.24)
|
||||||
|
|
||||||
|
elif wages > 81638 and wages <= 102888:
|
||||||
|
val = 16044.76 + ((wages - 81638) * 0.32)
|
||||||
|
|
||||||
|
elif wages > 102888 and wages <= 152888:
|
||||||
|
val = 22844.76 + ((wages - 102888) * 0.35)
|
||||||
|
|
||||||
|
elif wages > 152888:
|
||||||
|
val = 40344.76 + ((wages - 152888) * 0.37)
|
||||||
|
|
||||||
|
###
|
||||||
|
# Married SEMIANNUAL
|
||||||
|
###
|
||||||
|
elif 'semi-annually' == schedule_pay:
|
||||||
|
wages -= allowances * 2075.00
|
||||||
|
if is_nra:
|
||||||
|
wages += 3925.00
|
||||||
|
|
||||||
|
if wages > 5775 and wages <= 15300:
|
||||||
|
val = 0.00 + ((wages - 5775) * 0.10)
|
||||||
|
|
||||||
|
elif wages > 15300 and wages <= 44475:
|
||||||
|
val = 952.50 + ((wages - 15300) * 0.12)
|
||||||
|
|
||||||
|
elif wages > 44475 and wages <= 88275:
|
||||||
|
val = 4453.50 + ((wages - 44475) * 0.22)
|
||||||
|
|
||||||
|
elif wages > 88275 and wages <= 163275:
|
||||||
|
val = 14089.50 + ((wages - 88275) * 0.24)
|
||||||
|
|
||||||
|
elif wages > 163275 and wages <= 205775:
|
||||||
|
val = 32089.50 + ((wages - 163275) * 0.32)
|
||||||
|
|
||||||
|
elif wages > 205775 and wages <= 305775:
|
||||||
|
val = 45689.50 + ((wages - 205775) * 0.35)
|
||||||
|
|
||||||
|
elif wages > 305775:
|
||||||
|
val = 80689.50 + ((wages - 305775) * 0.37)
|
||||||
|
|
||||||
|
###
|
||||||
|
# Married ANNUAL
|
||||||
|
###
|
||||||
|
elif 'annually' == schedule_pay:
|
||||||
|
wages -= allowances * 4150.00
|
||||||
|
if is_nra:
|
||||||
|
wages += 7850.00
|
||||||
|
|
||||||
|
if wages > 11550 and wages <= 30600:
|
||||||
|
val = 0.00 + ((wages - 11550) * 0.10)
|
||||||
|
|
||||||
|
elif wages > 30600 and wages <= 88950:
|
||||||
|
val = 1905.00 + ((wages - 30600) * 0.12)
|
||||||
|
|
||||||
|
elif wages > 88950 and wages <= 176550:
|
||||||
|
val = 8907.00 + ((wages - 88950) * 0.22)
|
||||||
|
|
||||||
|
elif wages > 176550 and wages <= 326550:
|
||||||
|
val = 28179.00 + ((wages - 176550) * 0.24)
|
||||||
|
|
||||||
|
elif wages > 326550 and wages <= 411550:
|
||||||
|
val = 64179.00 + ((wages - 326550) * 0.32)
|
||||||
|
|
||||||
|
elif wages > 411550 and wages <= 611550:
|
||||||
|
val = 91379.00 + ((wages - 411550) * 0.35)
|
||||||
|
|
||||||
|
elif wages > 611550:
|
||||||
|
val = 161379.00 + ((wages - 611550) * 0.37)
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise Exception('Invalid schedule_pay="' + schedule_pay + '" for W4 Allowance calculation')
|
||||||
|
|
||||||
|
result = -(val + additional)
|
||||||
|
</field>
|
||||||
|
<field name="register_id" ref="contrib_register_eftps_941"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="hr_payroll_rules_futa_wages_2018" model="hr.salary.rule">
|
||||||
|
<field name="sequence" eval="422"/>
|
||||||
|
<field name="category_id" ref="hr_payroll_futa_wages"/>
|
||||||
|
<field name="name">FUTA Federal Unemployment - Wages (2018)</field>
|
||||||
|
<field name="code">FUTA_WAGES_2018</field>
|
||||||
|
<field name="condition_select">python</field>
|
||||||
|
<field name="condition_python">result = (payslip.date_to[:4] == '2018' and contract.futa_type != contract.FUTA_TYPE_EXEMPT)</field>
|
||||||
|
<field name="amount_select">code</field>
|
||||||
|
<field name="amount_python_compute">
|
||||||
|
###
|
||||||
|
ytd = payslip.sum('FUTA_WAGES_2018', '2018-01-01', '2019-01-01')
|
||||||
|
ytd += contract.external_wages
|
||||||
|
remaining = 7000.0 - ytd
|
||||||
|
if remaining <= 0.0:
|
||||||
|
result = 0
|
||||||
|
elif remaining < categories.GROSS:
|
||||||
|
result = remaining
|
||||||
|
else:
|
||||||
|
result = categories.GROSS
|
||||||
|
</field>
|
||||||
|
<field name="appears_on_payslip" eval="False"/>
|
||||||
|
</record>
|
||||||
|
<record id="hr_payroll_rules_futa_2018" model="hr.salary.rule">
|
||||||
|
<field name="sequence" eval="442"/>
|
||||||
|
<field name="category_id" ref="hr_payroll_futa"/>
|
||||||
|
<field name="name">FUTA Federal Unemployment (2018)</field>
|
||||||
|
<field name="code">FUTA_2018</field>
|
||||||
|
<field name="condition_select">python</field>
|
||||||
|
<field name="condition_python">result = (payslip.date_to[:4] == '2018' and contract.futa_type != contract.FUTA_TYPE_EXEMPT)</field>
|
||||||
|
<field name="amount_select">code</field>
|
||||||
|
<field name="amount_python_compute">
|
||||||
|
result_rate = -(contract.futa_rate(2018))
|
||||||
|
result = categories.FUTA_WAGES
|
||||||
|
</field>
|
||||||
|
<field name="register_id" ref="contrib_register_eftps_940"/>
|
||||||
|
<field name="appears_on_payslip" eval="False"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</data>
|
||||||
|
</odoo>
|
||||||
47
l10n_us_hr_payroll/l10n_us_hr_payroll.py
Executable file
47
l10n_us_hr_payroll/l10n_us_hr_payroll.py
Executable file
@@ -0,0 +1,47 @@
|
|||||||
|
from odoo import models, fields, api
|
||||||
|
|
||||||
|
|
||||||
|
class USHrContract(models.Model):
|
||||||
|
FUTA_TYPE_EXEMPT = 'exempt'
|
||||||
|
FUTA_TYPE_BASIC = 'basic'
|
||||||
|
FUTA_TYPE_NORMAL = 'normal'
|
||||||
|
FUTA_YEARS_VALID = (
|
||||||
|
2016,
|
||||||
|
2017,
|
||||||
|
2018,
|
||||||
|
)
|
||||||
|
|
||||||
|
_inherit = 'hr.contract'
|
||||||
|
|
||||||
|
schedule_pay = fields.Selection(selection_add=[('semi-monthly', 'Semi-monthly')])
|
||||||
|
w4_allowances = fields.Integer(string='Federal W4 Allowances', default=0)
|
||||||
|
w4_filing_status = fields.Selection([
|
||||||
|
('', 'Exempt'),
|
||||||
|
('single', 'Single'),
|
||||||
|
('married', 'Married'),
|
||||||
|
('married_as_single', 'Married but at Single Rate'),
|
||||||
|
], string='Federal W4 Filing Status', default='single')
|
||||||
|
w4_is_nonresident_alien = fields.Boolean(string="Federal W4 Is Nonresident Alien", default=False)
|
||||||
|
w4_additional_withholding = fields.Float(string="Federal W4 Additional Withholding", default=0.0)
|
||||||
|
|
||||||
|
external_wages = fields.Float(string='External Existing Wages', default=0.0)
|
||||||
|
|
||||||
|
futa_type = fields.Selection([
|
||||||
|
(FUTA_TYPE_EXEMPT, 'Exempt (0%)'),
|
||||||
|
(FUTA_TYPE_NORMAL, 'Normal Net Rate (0.6%)'),
|
||||||
|
(FUTA_TYPE_BASIC, 'Basic Rate (6%)'),
|
||||||
|
], string="Federal Unemployment Tax Type (FUTA)", default='normal')
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def futa_rate(self, year):
|
||||||
|
self.ensure_one()
|
||||||
|
|
||||||
|
if year not in self.FUTA_YEARS_VALID:
|
||||||
|
raise NotImplemented('FUTA rate for Year: ' + str(year) + ' not known.')
|
||||||
|
|
||||||
|
if self.futa_type == self.FUTA_TYPE_EXEMPT:
|
||||||
|
return 0.0
|
||||||
|
elif self.futa_type == self.FUTA_TYPE_NORMAL:
|
||||||
|
return 0.6
|
||||||
|
else:
|
||||||
|
return 6.0
|
||||||
27
l10n_us_hr_payroll/l10n_us_hr_payroll_view.xml
Executable file
27
l10n_us_hr_payroll/l10n_us_hr_payroll_view.xml
Executable file
@@ -0,0 +1,27 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<data>
|
||||||
|
<record id="hr_contract_form_l10n_us_inherit" model="ir.ui.view">
|
||||||
|
<field name="name">hr.contract.form.inherit</field>
|
||||||
|
<field name="model">hr.contract</field>
|
||||||
|
<field name="priority">20</field>
|
||||||
|
<field name="inherit_id" ref="hr_contract.hr_contract_view_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<data>
|
||||||
|
<xpath expr="//page[@name='other']" position="before">
|
||||||
|
<page string="US Filing" name="us_filing">
|
||||||
|
<group>
|
||||||
|
<field name="w4_filing_status"/>
|
||||||
|
<field name="w4_allowances"/>
|
||||||
|
<field name="w4_additional_withholding"/>
|
||||||
|
<field name="w4_is_nonresident_alien"/>
|
||||||
|
<field name="external_wages"/>
|
||||||
|
<field name="futa_type"/>
|
||||||
|
</group>
|
||||||
|
</page>
|
||||||
|
</xpath>
|
||||||
|
</data>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
</data>
|
||||||
|
</odoo>
|
||||||
6
l10n_us_hr_payroll/tests/__init__.py
Executable file
6
l10n_us_hr_payroll/tests/__init__.py
Executable file
@@ -0,0 +1,6 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from . import test_us_payslip
|
||||||
|
from . import test_us_payslip_2016
|
||||||
|
from . import test_us_payslip_2017
|
||||||
|
from . import test_us_payslip_2018
|
||||||
113
l10n_us_hr_payroll/tests/test_us_payslip.py
Executable file
113
l10n_us_hr_payroll/tests/test_us_payslip.py
Executable file
@@ -0,0 +1,113 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from logging import getLogger
|
||||||
|
from sys import float_info as sys_float_info
|
||||||
|
|
||||||
|
from odoo.tests import common
|
||||||
|
from odoo.tools.float_utils import float_round as odoo_float_round
|
||||||
|
from odoo.addons.l10n_us_hr_payroll.l10n_us_hr_payroll import USHrContract
|
||||||
|
|
||||||
|
|
||||||
|
def process_payslip(payslip):
|
||||||
|
try:
|
||||||
|
#v9
|
||||||
|
payslip.process_sheet()
|
||||||
|
except AttributeError:
|
||||||
|
payslip.action_payslip_done()
|
||||||
|
|
||||||
|
|
||||||
|
class TestUsPayslip(common.TransactionCase):
|
||||||
|
debug = False
|
||||||
|
_logger = getLogger(__name__)
|
||||||
|
|
||||||
|
float_info = sys_float_info
|
||||||
|
|
||||||
|
def float_round(self, value, digits):
|
||||||
|
return odoo_float_round(value, digits)
|
||||||
|
|
||||||
|
_payroll_digits = -1
|
||||||
|
|
||||||
|
@property
|
||||||
|
def payroll_digits(self):
|
||||||
|
if self._payroll_digits == -1:
|
||||||
|
self._payroll_digits = self.env['decimal.precision'].precision_get('Payroll')
|
||||||
|
return self._payroll_digits
|
||||||
|
|
||||||
|
def _log(self, message):
|
||||||
|
if self.debug:
|
||||||
|
self._logger.warn(message)
|
||||||
|
|
||||||
|
def _createEmployee(self):
|
||||||
|
return self.env['hr.employee'].create({
|
||||||
|
'birthday': '1985-03-14',
|
||||||
|
'country_id': self.ref('base.us'),
|
||||||
|
'department_id': self.ref('hr.dep_rd'),
|
||||||
|
'gender': 'male',
|
||||||
|
'name': 'Jared'
|
||||||
|
})
|
||||||
|
|
||||||
|
def _createContract(self, employee, salary,
|
||||||
|
schedule_pay='monthly',
|
||||||
|
w4_allowances=0,
|
||||||
|
w4_filing_status='single',
|
||||||
|
w4_is_nonresident_alien=False,
|
||||||
|
w4_additional_withholding=0.0,
|
||||||
|
external_wages=0.0,
|
||||||
|
struct_id=False,
|
||||||
|
futa_type=USHrContract.FUTA_TYPE_NORMAL,
|
||||||
|
):
|
||||||
|
if not struct_id:
|
||||||
|
struct_id = self.ref('l10n_us_hr_payroll.hr_payroll_salary_structure_us_employee')
|
||||||
|
|
||||||
|
return self.env['hr.contract'].create({
|
||||||
|
'date_start': '2016-01-01',
|
||||||
|
'date_end': '2030-12-31',
|
||||||
|
'name': 'Contract for Jared 2016',
|
||||||
|
'wage': salary,
|
||||||
|
'type_id': self.ref('hr_contract.hr_contract_type_emp'),
|
||||||
|
'employee_id': employee.id,
|
||||||
|
'struct_id': struct_id,
|
||||||
|
'resource_calendar_id': self.ref('resource.resource_calendar_std'),
|
||||||
|
'schedule_pay': schedule_pay,
|
||||||
|
'w4_allowances': w4_allowances,
|
||||||
|
'w4_filing_status': w4_filing_status,
|
||||||
|
'w4_is_nonresident_alien': w4_is_nonresident_alien,
|
||||||
|
'w4_additional_withholding': w4_additional_withholding,
|
||||||
|
'external_wages': external_wages,
|
||||||
|
'futa_type': futa_type,
|
||||||
|
'state': 'open', # if not "Running" then no automatic selection when Payslip is created
|
||||||
|
})
|
||||||
|
|
||||||
|
def _createPayslip(self, employee, date_from, date_to):
|
||||||
|
return self.env['hr.payslip'].create({
|
||||||
|
'employee_id': employee.id,
|
||||||
|
'date_from': date_from,
|
||||||
|
'date_to': date_to
|
||||||
|
})
|
||||||
|
|
||||||
|
def _getCategories(self, payslip):
|
||||||
|
detail_lines = payslip.details_by_salary_rule_category
|
||||||
|
categories = {}
|
||||||
|
for line in detail_lines:
|
||||||
|
self._log(' line code: ' + str(line.code) +
|
||||||
|
' category code: ' + line.category_id.code +
|
||||||
|
' total: ' + str(line.total) +
|
||||||
|
' rate: ' + str(line.rate) +
|
||||||
|
' amount: ' + str(line.amount))
|
||||||
|
if line.category_id.code not in categories:
|
||||||
|
categories[line.category_id.code] = line.total
|
||||||
|
else:
|
||||||
|
categories[line.category_id.code] += line.total
|
||||||
|
|
||||||
|
return categories
|
||||||
|
|
||||||
|
def assertPayrollEqual(self, first, second):
|
||||||
|
self.assertAlmostEqual(first, second, self.payroll_digits)
|
||||||
|
|
||||||
|
def test_semi_monthly(self):
|
||||||
|
salary = 80000.0
|
||||||
|
employee = self._createEmployee()
|
||||||
|
contract = self._createContract(employee, salary, schedule_pay='semi-monthly')
|
||||||
|
payslip = self._createPayslip(employee, '2016-01-01', '2016-01-14')
|
||||||
|
|
||||||
|
payslip.compute_sheet()
|
||||||
377
l10n_us_hr_payroll/tests/test_us_payslip_2016.py
Executable file
377
l10n_us_hr_payroll/tests/test_us_payslip_2016.py
Executable file
@@ -0,0 +1,377 @@
|
|||||||
|
from .test_us_payslip import TestUsPayslip, process_payslip
|
||||||
|
|
||||||
|
from odoo.addons.l10n_us_hr_payroll.l10n_us_hr_payroll import USHrContract
|
||||||
|
|
||||||
|
|
||||||
|
class TestUsPayslip2016(TestUsPayslip):
|
||||||
|
FUTA_RATE_NORMAL_2016 = 0.6
|
||||||
|
FUTA_RATE_BASIC_2016 = 6.0
|
||||||
|
FUTA_RATE_EXEMPT_2016 = 0.0
|
||||||
|
|
||||||
|
|
||||||
|
###
|
||||||
|
# 2016 Taxes and Rates
|
||||||
|
###
|
||||||
|
|
||||||
|
def test_2016_taxes(self):
|
||||||
|
# salary is high so that second payslip runs over max
|
||||||
|
# social security salary
|
||||||
|
salary = 80000.0
|
||||||
|
|
||||||
|
## tax rates
|
||||||
|
FICA_SS = -0.062
|
||||||
|
FICA_M = -0.0145
|
||||||
|
FUTA = -self.FUTA_RATE_NORMAL_2016 / 100.0
|
||||||
|
FICA_M_ADD = -0.009
|
||||||
|
|
||||||
|
## tax maximums
|
||||||
|
FICA_SS_MAX_WAGE = 118500.0
|
||||||
|
FICA_M_MAX_WAGE = self.float_info.max
|
||||||
|
FICA_M_ADD_START_WAGE = 200000.0
|
||||||
|
FUTA_MAX_WAGE = 7000.0
|
||||||
|
|
||||||
|
employee = self._createEmployee()
|
||||||
|
|
||||||
|
contract = self._createContract(employee, salary)
|
||||||
|
|
||||||
|
self._log('2016 tax first payslip:')
|
||||||
|
payslip = self._createPayslip(employee, '2016-01-01', '2016-01-31')
|
||||||
|
|
||||||
|
payslip.compute_sheet()
|
||||||
|
|
||||||
|
cats = self._getCategories(payslip)
|
||||||
|
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_SS_WAGES'], salary)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_SS'], cats['FICA_EMP_SS_WAGES'] * FICA_SS)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M_WAGES'], salary)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M'], cats['FICA_EMP_M_WAGES'] * FICA_M)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M_ADD_WAGES'], 0.0)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M_ADD'], 0.0)
|
||||||
|
self.assertPayrollEqual(cats['FICA_COMP_SS'], cats['FICA_EMP_SS'])
|
||||||
|
self.assertPayrollEqual(cats['FICA_COMP_M'], cats['FICA_EMP_M'])
|
||||||
|
self.assertPayrollEqual(cats['FUTA_WAGES'], FUTA_MAX_WAGE)
|
||||||
|
self.assertPayrollEqual(cats['FUTA'], cats['FUTA_WAGES'] * FUTA)
|
||||||
|
|
||||||
|
process_payslip(payslip)
|
||||||
|
|
||||||
|
# Make a new payslip, this one will have maximums for FICA Social Security Wages
|
||||||
|
|
||||||
|
remaining_ss_wages = FICA_SS_MAX_WAGE - salary if (FICA_SS_MAX_WAGE - 2 * salary < salary) else salary
|
||||||
|
remaining_m_wages = FICA_M_MAX_WAGE - salary if (FICA_M_MAX_WAGE - 2 * salary < salary) else salary
|
||||||
|
|
||||||
|
self._log('2016 tax second payslip:')
|
||||||
|
payslip = self._createPayslip(employee, '2016-02-01', '2016-02-29') # 2016 is a leap year
|
||||||
|
|
||||||
|
payslip.compute_sheet()
|
||||||
|
|
||||||
|
cats = self._getCategories(payslip)
|
||||||
|
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_SS_WAGES'], remaining_ss_wages)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_SS'], cats['FICA_EMP_SS_WAGES'] * FICA_SS)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M_WAGES'], remaining_m_wages)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M'], cats['FICA_EMP_M_WAGES'] * FICA_M)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M_ADD_WAGES'], 0.0)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M_ADD'], 0.0)
|
||||||
|
self.assertPayrollEqual(cats['FICA_COMP_SS'], cats['FICA_EMP_SS'])
|
||||||
|
self.assertPayrollEqual(cats['FICA_COMP_M'], cats['FICA_EMP_M'])
|
||||||
|
self.assertPayrollEqual(cats['FUTA_WAGES'], 0)
|
||||||
|
self.assertPayrollEqual(cats['FUTA'], 0)
|
||||||
|
|
||||||
|
process_payslip(payslip)
|
||||||
|
|
||||||
|
# Make a new payslip, this one will have reached Medicare Additional (employee only)
|
||||||
|
|
||||||
|
self._log('2016 tax third payslip:')
|
||||||
|
payslip = self._createPayslip(employee, '2016-03-01', '2016-03-31')
|
||||||
|
|
||||||
|
payslip.compute_sheet()
|
||||||
|
|
||||||
|
cats = self._getCategories(payslip)
|
||||||
|
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M_WAGES'], salary)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M_ADD_WAGES'], FICA_M_ADD_START_WAGE - (salary * 2)) # aka 40k
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M_ADD'], cats['FICA_EMP_M_ADD_WAGES'] * FICA_M_ADD)
|
||||||
|
|
||||||
|
process_payslip(payslip)
|
||||||
|
|
||||||
|
# Make a new payslip, this one will have all salary as Medicare Additional
|
||||||
|
|
||||||
|
self._log('2016 tax fourth payslip:')
|
||||||
|
payslip = self._createPayslip(employee, '2016-04-01', '2016-04-30')
|
||||||
|
|
||||||
|
payslip.compute_sheet()
|
||||||
|
|
||||||
|
cats = self._getCategories(payslip)
|
||||||
|
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M_WAGES'], salary)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M_ADD_WAGES'], salary)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M_ADD'], cats['FICA_EMP_M_ADD_WAGES'] * FICA_M_ADD)
|
||||||
|
|
||||||
|
process_payslip(payslip)
|
||||||
|
|
||||||
|
|
||||||
|
def test_2016_fed_income_withholding_single(self):
|
||||||
|
salary = 6000.00
|
||||||
|
schedule_pay = 'monthly'
|
||||||
|
w4_allowances = 3
|
||||||
|
w4_allowance_amt = 337.50 * w4_allowances
|
||||||
|
adjusted_salary = salary - w4_allowance_amt # should be 4987.50, but would work over a wide value for the rate
|
||||||
|
###
|
||||||
|
# Single MONTHLY form Publication 15
|
||||||
|
expected_withholding = self.float_round(-(431.95 + ((adjusted_salary - 3325) * 0.25)), self.payroll_digits)
|
||||||
|
|
||||||
|
employee = self._createEmployee()
|
||||||
|
contract = self._createContract(employee, salary, schedule_pay, w4_allowances, 'single')
|
||||||
|
|
||||||
|
self._log('2016 fed income single payslip: adjusted_salary: ' + str(adjusted_salary))
|
||||||
|
payslip = self._createPayslip(employee, '2016-01-01', '2016-01-31')
|
||||||
|
|
||||||
|
payslip.compute_sheet()
|
||||||
|
|
||||||
|
cats = self._getCategories(payslip)
|
||||||
|
|
||||||
|
self.assertPayrollEqual(cats['FED_INC_WITHHOLD'], expected_withholding)
|
||||||
|
|
||||||
|
|
||||||
|
def test_2016_fed_income_withholding_married_as_single(self):
|
||||||
|
salary = 500.00
|
||||||
|
schedule_pay = 'weekly'
|
||||||
|
w4_allowances = 1
|
||||||
|
w4_allowance_amt = 77.90 * w4_allowances
|
||||||
|
adjusted_salary = salary - w4_allowance_amt # should be 422.10, but would work over a wide value for the rate
|
||||||
|
###
|
||||||
|
# Single MONTHLY form Publication 15
|
||||||
|
expected_withholding = self.float_round(-(17.90 + ((adjusted_salary - 222) * 0.15)), self.payroll_digits)
|
||||||
|
|
||||||
|
employee = self._createEmployee()
|
||||||
|
contract = self._createContract(employee, salary, schedule_pay, w4_allowances, 'married_as_single')
|
||||||
|
|
||||||
|
self._log('2016 fed income married_as_single payslip: adjusted_salary: ' + str(adjusted_salary))
|
||||||
|
payslip = self._createPayslip(employee, '2016-01-01', '2016-01-31')
|
||||||
|
|
||||||
|
payslip.compute_sheet()
|
||||||
|
|
||||||
|
cats = self._getCategories(payslip)
|
||||||
|
|
||||||
|
self.assertPayrollEqual(cats['FED_INC_WITHHOLD'], expected_withholding)
|
||||||
|
|
||||||
|
|
||||||
|
def test_2016_fed_income_withholding_married(self):
|
||||||
|
salary = 14000.00
|
||||||
|
schedule_pay = 'bi-weekly'
|
||||||
|
w4_allowances = 2
|
||||||
|
w4_allowance_amt = 155.80 * w4_allowances
|
||||||
|
adjusted_salary = salary - w4_allowance_amt # should be 1368.84, but would work over a wide value for the rate
|
||||||
|
###
|
||||||
|
# Single MONTHLY form Publication 15
|
||||||
|
expected_withholding = self.float_round(-(1992.05 + ((adjusted_salary - 9231) * 0.33)), self.payroll_digits)
|
||||||
|
|
||||||
|
employee = self._createEmployee()
|
||||||
|
contract = self._createContract(employee, salary, schedule_pay, w4_allowances, 'married')
|
||||||
|
|
||||||
|
self._log('2016 fed income married payslip: adjusted_salary: ' + str(adjusted_salary))
|
||||||
|
payslip = self._createPayslip(employee, '2016-01-01', '2016-01-31')
|
||||||
|
|
||||||
|
payslip.compute_sheet()
|
||||||
|
|
||||||
|
cats = self._getCategories(payslip)
|
||||||
|
|
||||||
|
self.assertPayrollEqual(cats['FED_INC_WITHHOLD'], expected_withholding)
|
||||||
|
|
||||||
|
|
||||||
|
def test_2016_taxes_with_external(self):
|
||||||
|
## tax rates
|
||||||
|
FICA_SS = -0.062
|
||||||
|
FICA_M = -0.0145
|
||||||
|
FUTA = -self.FUTA_RATE_NORMAL_2016 / 100.0
|
||||||
|
FICA_M_ADD = -0.009
|
||||||
|
|
||||||
|
## tax maximums
|
||||||
|
FICA_SS_MAX_WAGE = 118500.0
|
||||||
|
FICA_M_MAX_WAGE = self.float_info.max
|
||||||
|
FICA_M_ADD_START_WAGE = 200000.0
|
||||||
|
FUTA_MAX_WAGE = 7000.0
|
||||||
|
|
||||||
|
# social security salary
|
||||||
|
salary = FICA_M_ADD_START_WAGE
|
||||||
|
external_wages = 6000.0
|
||||||
|
|
||||||
|
employee = self._createEmployee()
|
||||||
|
|
||||||
|
contract = self._createContract(employee, salary, external_wages=external_wages)
|
||||||
|
|
||||||
|
self._log('2016 tax first payslip:')
|
||||||
|
payslip = self._createPayslip(employee, '2016-01-01', '2016-01-31')
|
||||||
|
|
||||||
|
payslip.compute_sheet()
|
||||||
|
|
||||||
|
cats = self._getCategories(payslip)
|
||||||
|
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_SS_WAGES'], FICA_SS_MAX_WAGE - external_wages)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_SS'], cats['FICA_EMP_SS_WAGES'] * FICA_SS)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M_WAGES'], salary)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M'], cats['FICA_EMP_M_WAGES'] * FICA_M)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M_ADD_WAGES'], 0.0)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M_ADD'], cats['FICA_EMP_M_ADD_WAGES'] * FICA_M_ADD)
|
||||||
|
self.assertPayrollEqual(cats['FICA_COMP_SS'], cats['FICA_EMP_SS'])
|
||||||
|
self.assertPayrollEqual(cats['FICA_COMP_M'], cats['FICA_EMP_M'])
|
||||||
|
self.assertPayrollEqual(cats['FUTA_WAGES'], FUTA_MAX_WAGE - external_wages)
|
||||||
|
self.assertPayrollEqual(cats['FUTA'], cats['FUTA_WAGES'] * FUTA)
|
||||||
|
|
||||||
|
|
||||||
|
def test_2016_taxes_with_full_futa(self):
|
||||||
|
## tax rates
|
||||||
|
FICA_SS = -0.062
|
||||||
|
FICA_M = -0.0145
|
||||||
|
FUTA = -self.FUTA_RATE_BASIC_2016 / 100.0 # because of state exemption
|
||||||
|
FICA_M_ADD = -0.009
|
||||||
|
|
||||||
|
## tax maximums
|
||||||
|
FICA_SS_MAX_WAGE = 118500.0
|
||||||
|
FICA_M_MAX_WAGE = self.float_info.max
|
||||||
|
FICA_M_ADD_START_WAGE = 200000.0
|
||||||
|
FUTA_MAX_WAGE = 7000.0
|
||||||
|
|
||||||
|
# social security salary
|
||||||
|
salary = FICA_M_ADD_START_WAGE
|
||||||
|
|
||||||
|
employee = self._createEmployee()
|
||||||
|
|
||||||
|
contract = self._createContract(employee, salary, futa_type=USHrContract.FUTA_TYPE_BASIC)
|
||||||
|
|
||||||
|
self._log('2016 tax first payslip:')
|
||||||
|
payslip = self._createPayslip(employee, '2016-01-01', '2016-01-31')
|
||||||
|
|
||||||
|
payslip.compute_sheet()
|
||||||
|
|
||||||
|
cats = self._getCategories(payslip)
|
||||||
|
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_SS_WAGES'], FICA_SS_MAX_WAGE)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_SS'], cats['FICA_EMP_SS_WAGES'] * FICA_SS)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M_WAGES'], salary)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M'], cats['FICA_EMP_M_WAGES'] * FICA_M)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M_ADD_WAGES'], 0.0)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M_ADD'], cats['FICA_EMP_M_ADD_WAGES'] * FICA_M_ADD)
|
||||||
|
self.assertPayrollEqual(cats['FICA_COMP_SS'], cats['FICA_EMP_SS'])
|
||||||
|
self.assertPayrollEqual(cats['FICA_COMP_M'], cats['FICA_EMP_M'])
|
||||||
|
self.assertPayrollEqual(cats['FUTA_WAGES'], FUTA_MAX_WAGE)
|
||||||
|
self.assertPayrollEqual(cats['FUTA'], cats['FUTA_WAGES'] * FUTA)
|
||||||
|
|
||||||
|
|
||||||
|
def test_2016_taxes_with_futa_exempt(self):
|
||||||
|
## tax rates
|
||||||
|
FICA_SS = -0.062
|
||||||
|
FICA_M = -0.0145
|
||||||
|
FUTA = self.FUTA_RATE_EXEMPT_2016 # because of exemption
|
||||||
|
FICA_M_ADD = -0.009
|
||||||
|
|
||||||
|
## tax maximums
|
||||||
|
FICA_SS_MAX_WAGE = 118500.0
|
||||||
|
FICA_M_MAX_WAGE = self.float_info.max
|
||||||
|
FICA_M_ADD_START_WAGE = 200000.0
|
||||||
|
FUTA_MAX_WAGE = 7000.0
|
||||||
|
|
||||||
|
# social security salary
|
||||||
|
salary = FICA_M_ADD_START_WAGE
|
||||||
|
|
||||||
|
employee = self._createEmployee()
|
||||||
|
|
||||||
|
contract = self._createContract(employee, salary, futa_type=USHrContract.FUTA_TYPE_EXEMPT)
|
||||||
|
|
||||||
|
self._log('2016 tax first payslip:')
|
||||||
|
payslip = self._createPayslip(employee, '2016-01-01', '2016-01-31')
|
||||||
|
|
||||||
|
payslip.compute_sheet()
|
||||||
|
|
||||||
|
cats = self._getCategories(payslip)
|
||||||
|
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_SS_WAGES'], FICA_SS_MAX_WAGE)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_SS'], cats['FICA_EMP_SS_WAGES'] * FICA_SS)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M_WAGES'], salary)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M'], cats['FICA_EMP_M_WAGES'] * FICA_M)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M_ADD_WAGES'], 0.0)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M_ADD'], cats['FICA_EMP_M_ADD_WAGES'] * FICA_M_ADD)
|
||||||
|
self.assertPayrollEqual(cats['FICA_COMP_SS'], cats['FICA_EMP_SS'])
|
||||||
|
self.assertPayrollEqual(cats['FICA_COMP_M'], cats['FICA_EMP_M'])
|
||||||
|
|
||||||
|
FUTA_WAGES = 0.0
|
||||||
|
if 'FUTA_WAGES' in cats:
|
||||||
|
FUTA_WAGES = cats['FUTA_WAGES']
|
||||||
|
FUTA = 0.0
|
||||||
|
if 'FUTA' in cats:
|
||||||
|
FUTA = cats['FUTA']
|
||||||
|
self.assertPayrollEqual(FUTA_WAGES, 0.0)
|
||||||
|
self.assertPayrollEqual(FUTA, FUTA_WAGES * FUTA)
|
||||||
|
|
||||||
|
|
||||||
|
def test_2016_fed_income_withholding_nonresident_alien(self):
|
||||||
|
salary = 3500.00
|
||||||
|
schedule_pay = 'quarterly'
|
||||||
|
w4_allowances = 1
|
||||||
|
w4_allowance_amt = 1012.50 * w4_allowances
|
||||||
|
nra_adjustment = 562.50 # for quarterly
|
||||||
|
adjusted_salary = salary - w4_allowance_amt + nra_adjustment # 3050
|
||||||
|
|
||||||
|
###
|
||||||
|
# Single QUARTERLY form Publication 15
|
||||||
|
expected_withholding = self.float_round(-(231.80 + ((adjusted_salary - 2881) * 0.15)), self.payroll_digits)
|
||||||
|
|
||||||
|
employee = self._createEmployee()
|
||||||
|
contract = self._createContract(employee, salary, schedule_pay, w4_allowances, 'single',
|
||||||
|
w4_is_nonresident_alien=True)
|
||||||
|
|
||||||
|
self._log('2016 fed income single payslip nonresident alien: adjusted_salary: ' + str(adjusted_salary))
|
||||||
|
payslip = self._createPayslip(employee, '2016-01-01', '2016-01-31')
|
||||||
|
|
||||||
|
payslip.compute_sheet()
|
||||||
|
|
||||||
|
cats = self._getCategories(payslip)
|
||||||
|
|
||||||
|
self.assertPayrollEqual(cats['FED_INC_WITHHOLD'], expected_withholding)
|
||||||
|
|
||||||
|
|
||||||
|
def test_2016_fed_income_additional_withholding(self):
|
||||||
|
salary = 50000.00
|
||||||
|
schedule_pay = 'annually'
|
||||||
|
w4_additional_withholding = 5000.0
|
||||||
|
w4_allowances = 2
|
||||||
|
w4_allowance_amt = 4050.0 * w4_allowances
|
||||||
|
adjusted_salary = salary - w4_allowance_amt # 41900
|
||||||
|
|
||||||
|
###
|
||||||
|
# Single ANNUAL form Publication 15
|
||||||
|
expected_withholding = self.float_round(-((1855 + ((adjusted_salary - 27100) * 0.15)) + w4_additional_withholding),
|
||||||
|
self.payroll_digits)
|
||||||
|
|
||||||
|
employee = self._createEmployee()
|
||||||
|
contract = self._createContract(employee, salary, schedule_pay, w4_allowances, 'married',
|
||||||
|
w4_additional_withholding=w4_additional_withholding)
|
||||||
|
|
||||||
|
self._log('2016 fed income married payslip additional withholding: adjusted_salary: ' + str(adjusted_salary))
|
||||||
|
payslip = self._createPayslip(employee, '2016-01-01', '2016-01-31')
|
||||||
|
|
||||||
|
payslip.compute_sheet()
|
||||||
|
|
||||||
|
cats = self._getCategories(payslip)
|
||||||
|
|
||||||
|
self.assertPayrollEqual(cats['FED_INC_WITHHOLD'], expected_withholding)
|
||||||
|
|
||||||
|
|
||||||
|
def test_2016_taxes_with_w4_exempt(self):
|
||||||
|
salary = 6000.0
|
||||||
|
schedule_pay = 'bi-weekly'
|
||||||
|
w4_allowances = 0
|
||||||
|
employee = self._createEmployee()
|
||||||
|
contract = self._createContract(employee, salary, schedule_pay, w4_allowances, '')
|
||||||
|
|
||||||
|
self._log('2016 tax w4 exempt payslip:')
|
||||||
|
payslip = self._createPayslip(employee, '2016-01-01', '2016-01-31')
|
||||||
|
|
||||||
|
payslip.compute_sheet()
|
||||||
|
|
||||||
|
cats = self._getCategories(payslip)
|
||||||
|
|
||||||
|
FED_INC_WITHHOLD = 0.0
|
||||||
|
if 'FED_INC_WITHHOLD' in cats:
|
||||||
|
FED_INC_WITHHOLD = cats['FED_INC_WITHHOLD']
|
||||||
|
self.assertPayrollEqual(FED_INC_WITHHOLD, 0.0)
|
||||||
344
l10n_us_hr_payroll/tests/test_us_payslip_2017.py
Executable file
344
l10n_us_hr_payroll/tests/test_us_payslip_2017.py
Executable file
@@ -0,0 +1,344 @@
|
|||||||
|
from .test_us_payslip import TestUsPayslip, process_payslip
|
||||||
|
|
||||||
|
from odoo.addons.l10n_us_hr_payroll.l10n_us_hr_payroll import USHrContract
|
||||||
|
|
||||||
|
from sys import float_info
|
||||||
|
|
||||||
|
|
||||||
|
class TestUsPayslip2017(TestUsPayslip):
|
||||||
|
# FUTA Constants
|
||||||
|
FUTA_RATE_NORMAL = 0.6
|
||||||
|
FUTA_RATE_BASIC = 6.0
|
||||||
|
FUTA_RATE_EXEMPT = 0.0
|
||||||
|
|
||||||
|
# Wage caps
|
||||||
|
FICA_SS_MAX_WAGE = 127200.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
|
||||||
|
|
||||||
|
###
|
||||||
|
# 2017 Taxes and Rates
|
||||||
|
###
|
||||||
|
|
||||||
|
def test_2017_taxes(self):
|
||||||
|
# salary is high so that second payslip runs over max
|
||||||
|
# social security salary
|
||||||
|
salary = 80000.0
|
||||||
|
|
||||||
|
employee = self._createEmployee()
|
||||||
|
|
||||||
|
self._createContract(employee, salary)
|
||||||
|
|
||||||
|
self._log('2016 tax last slip')
|
||||||
|
payslip = self._createPayslip(employee, '2016-12-01', '2016-12-31')
|
||||||
|
payslip.compute_sheet()
|
||||||
|
process_payslip(payslip)
|
||||||
|
|
||||||
|
self._log('2017 tax first payslip:')
|
||||||
|
payslip = self._createPayslip(employee, '2017-01-01', '2017-01-31')
|
||||||
|
|
||||||
|
payslip.compute_sheet()
|
||||||
|
|
||||||
|
cats = self._getCategories(payslip)
|
||||||
|
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_SS_WAGES'], salary)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_SS'], cats['FICA_EMP_SS_WAGES'] * self.FICA_SS)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M_WAGES'], salary)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M'], cats['FICA_EMP_M_WAGES'] * self.FICA_M)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M_ADD_WAGES'], 0.0)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M_ADD'], 0.0)
|
||||||
|
self.assertPayrollEqual(cats['FICA_COMP_SS'], cats['FICA_EMP_SS'])
|
||||||
|
self.assertPayrollEqual(cats['FICA_COMP_M'], cats['FICA_EMP_M'])
|
||||||
|
self.assertPayrollEqual(cats['FUTA_WAGES'], self.FUTA_MAX_WAGE)
|
||||||
|
self.assertPayrollEqual(cats['FUTA'], cats['FUTA_WAGES'] * self.FUTA)
|
||||||
|
|
||||||
|
process_payslip(payslip)
|
||||||
|
|
||||||
|
# Make a new payslip, this one will have maximums for FICA Social Security Wages
|
||||||
|
|
||||||
|
remaining_ss_wages = self.FICA_SS_MAX_WAGE - salary if (self.FICA_SS_MAX_WAGE - 2 * salary < salary) else salary
|
||||||
|
remaining_m_wages = self.FICA_M_MAX_WAGE - salary if (self.FICA_M_MAX_WAGE - 2 * salary < salary) else salary
|
||||||
|
|
||||||
|
self._log('2017 tax second payslip:')
|
||||||
|
payslip = self._createPayslip(employee, '2017-02-01', '2017-02-28')
|
||||||
|
|
||||||
|
payslip.compute_sheet()
|
||||||
|
|
||||||
|
cats = self._getCategories(payslip)
|
||||||
|
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_SS_WAGES'], remaining_ss_wages)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_SS'], cats['FICA_EMP_SS_WAGES'] * self.FICA_SS)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M_WAGES'], remaining_m_wages)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M'], cats['FICA_EMP_M_WAGES'] * self.FICA_M)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M_ADD_WAGES'], 0.0)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M_ADD'], 0.0)
|
||||||
|
self.assertPayrollEqual(cats['FICA_COMP_SS'], cats['FICA_EMP_SS'])
|
||||||
|
self.assertPayrollEqual(cats['FICA_COMP_M'], cats['FICA_EMP_M'])
|
||||||
|
self.assertPayrollEqual(cats['FUTA_WAGES'], 0)
|
||||||
|
self.assertPayrollEqual(cats['FUTA'], 0)
|
||||||
|
|
||||||
|
process_payslip(payslip)
|
||||||
|
|
||||||
|
# Make a new payslip, this one will have reached Medicare Additional (employee only)
|
||||||
|
|
||||||
|
self._log('2017 tax third payslip:')
|
||||||
|
payslip = self._createPayslip(employee, '2017-03-01', '2017-03-31')
|
||||||
|
|
||||||
|
payslip.compute_sheet()
|
||||||
|
|
||||||
|
cats = self._getCategories(payslip)
|
||||||
|
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M_WAGES'], salary)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M_ADD_WAGES'], self.FICA_M_ADD_START_WAGE - (salary * 2)) # aka 40k
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M_ADD'], cats['FICA_EMP_M_ADD_WAGES'] * self.FICA_M_ADD)
|
||||||
|
|
||||||
|
process_payslip(payslip)
|
||||||
|
|
||||||
|
# Make a new payslip, this one will have all salary as Medicare Additional
|
||||||
|
|
||||||
|
self._log('2017 tax fourth payslip:')
|
||||||
|
payslip = self._createPayslip(employee, '2017-04-01', '2017-04-30')
|
||||||
|
|
||||||
|
payslip.compute_sheet()
|
||||||
|
|
||||||
|
cats = self._getCategories(payslip)
|
||||||
|
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M_WAGES'], salary)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M_ADD_WAGES'], salary)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M_ADD'], cats['FICA_EMP_M_ADD_WAGES'] * self.FICA_M_ADD)
|
||||||
|
|
||||||
|
process_payslip(payslip)
|
||||||
|
|
||||||
|
def test_2017_fed_income_withholding_single(self):
|
||||||
|
salary = 6000.00
|
||||||
|
schedule_pay = 'monthly'
|
||||||
|
w4_allowances = 3
|
||||||
|
w4_allowance_amt = 337.50 * w4_allowances
|
||||||
|
adjusted_salary = salary - w4_allowance_amt # should be 4987.50, but would work over a wide value for the rate
|
||||||
|
###
|
||||||
|
# Single MONTHLY form Publication 15
|
||||||
|
expected_withholding = self.float_round(-(435.45 + ((adjusted_salary - 3354) * 0.25)), self.payroll_digits)
|
||||||
|
|
||||||
|
employee = self._createEmployee()
|
||||||
|
self._createContract(employee, salary, schedule_pay, w4_allowances, 'single')
|
||||||
|
|
||||||
|
self._log('2017 fed income single payslip: adjusted_salary: ' + str(adjusted_salary))
|
||||||
|
payslip = self._createPayslip(employee, '2017-01-01', '2017-01-31')
|
||||||
|
|
||||||
|
payslip.compute_sheet()
|
||||||
|
|
||||||
|
cats = self._getCategories(payslip)
|
||||||
|
|
||||||
|
self.assertPayrollEqual(cats['FED_INC_WITHHOLD'], expected_withholding)
|
||||||
|
|
||||||
|
def test_2017_fed_income_withholding_married_as_single(self):
|
||||||
|
salary = 500.00
|
||||||
|
schedule_pay = 'weekly'
|
||||||
|
w4_allowances = 1
|
||||||
|
w4_allowance_amt = 77.90 * w4_allowances
|
||||||
|
adjusted_salary = salary - w4_allowance_amt # should be 422.10, but would work over a wide value for the rate
|
||||||
|
###
|
||||||
|
# Single MONTHLY form Publication 15
|
||||||
|
expected_withholding = self.float_round(-(18.00 + ((adjusted_salary - 224) * 0.15)), self.payroll_digits)
|
||||||
|
|
||||||
|
employee = self._createEmployee()
|
||||||
|
self._createContract(employee, salary, schedule_pay, w4_allowances, 'married_as_single')
|
||||||
|
|
||||||
|
self._log('2017 fed income married_as_single payslip: adjusted_salary: ' + str(adjusted_salary))
|
||||||
|
payslip = self._createPayslip(employee, '2017-01-01', '2017-01-31')
|
||||||
|
|
||||||
|
payslip.compute_sheet()
|
||||||
|
|
||||||
|
cats = self._getCategories(payslip)
|
||||||
|
|
||||||
|
self.assertPayrollEqual(cats['FED_INC_WITHHOLD'], expected_withholding)
|
||||||
|
|
||||||
|
def test_2017_fed_income_withholding_married(self):
|
||||||
|
salary = 14000.00
|
||||||
|
schedule_pay = 'bi-weekly'
|
||||||
|
w4_allowances = 2
|
||||||
|
w4_allowance_amt = 155.80 * w4_allowances
|
||||||
|
adjusted_salary = salary - w4_allowance_amt # should be 1368.84, but would work over a wide value for the rate
|
||||||
|
###
|
||||||
|
# Single MONTHLY form Publication 15
|
||||||
|
expected_withholding = self.float_round(-(2008.61 + ((adjusted_salary - 9308) * 0.33)), self.payroll_digits)
|
||||||
|
|
||||||
|
employee = self._createEmployee()
|
||||||
|
self._createContract(employee, salary, schedule_pay, w4_allowances, 'married')
|
||||||
|
|
||||||
|
self._log('2017 fed income married payslip: adjusted_salary: ' + str(adjusted_salary))
|
||||||
|
payslip = self._createPayslip(employee, '2017-01-01', '2017-01-31')
|
||||||
|
|
||||||
|
payslip.compute_sheet()
|
||||||
|
|
||||||
|
cats = self._getCategories(payslip)
|
||||||
|
|
||||||
|
self.assertPayrollEqual(cats['FED_INC_WITHHOLD'], expected_withholding)
|
||||||
|
|
||||||
|
def test_2017_taxes_with_external(self):
|
||||||
|
|
||||||
|
# social security salary
|
||||||
|
salary = self.FICA_M_ADD_START_WAGE
|
||||||
|
external_wages = 6000.0
|
||||||
|
|
||||||
|
employee = self._createEmployee()
|
||||||
|
|
||||||
|
self._createContract(employee, salary, external_wages=external_wages)
|
||||||
|
|
||||||
|
self._log('2017 tax first payslip:')
|
||||||
|
payslip = self._createPayslip(employee, '2017-01-01', '2017-01-31')
|
||||||
|
|
||||||
|
payslip.compute_sheet()
|
||||||
|
|
||||||
|
cats = self._getCategories(payslip)
|
||||||
|
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_SS_WAGES'], self.FICA_SS_MAX_WAGE - external_wages)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_SS'], cats['FICA_EMP_SS_WAGES'] * self.FICA_SS)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M_WAGES'], salary)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M'], cats['FICA_EMP_M_WAGES'] * self.FICA_M)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M_ADD_WAGES'], 0.0)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M_ADD'], cats['FICA_EMP_M_ADD_WAGES'] * self.FICA_M_ADD)
|
||||||
|
self.assertPayrollEqual(cats['FICA_COMP_SS'], cats['FICA_EMP_SS'])
|
||||||
|
self.assertPayrollEqual(cats['FICA_COMP_M'], cats['FICA_EMP_M'])
|
||||||
|
self.assertPayrollEqual(cats['FUTA_WAGES'], self.FUTA_MAX_WAGE - external_wages)
|
||||||
|
self.assertPayrollEqual(cats['FUTA'], cats['FUTA_WAGES'] * self.FUTA)
|
||||||
|
|
||||||
|
def test_2017_taxes_with_full_futa(self):
|
||||||
|
futa_rate = self.FUTA_RATE_BASIC / -100.0
|
||||||
|
# social security salary
|
||||||
|
salary = self.FICA_M_ADD_START_WAGE
|
||||||
|
|
||||||
|
employee = self._createEmployee()
|
||||||
|
|
||||||
|
self._createContract(employee, salary, futa_type=USHrContract.FUTA_TYPE_BASIC)
|
||||||
|
|
||||||
|
self._log('2017 tax first payslip:')
|
||||||
|
payslip = self._createPayslip(employee, '2017-01-01', '2017-01-31')
|
||||||
|
|
||||||
|
payslip.compute_sheet()
|
||||||
|
|
||||||
|
cats = self._getCategories(payslip)
|
||||||
|
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_SS_WAGES'], self.FICA_SS_MAX_WAGE)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_SS'], cats['FICA_EMP_SS_WAGES'] * self.FICA_SS)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M_WAGES'], salary)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M'], cats['FICA_EMP_M_WAGES'] * self.FICA_M)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M_ADD_WAGES'], 0.0)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M_ADD'], cats['FICA_EMP_M_ADD_WAGES'] * self.FICA_M_ADD)
|
||||||
|
self.assertPayrollEqual(cats['FICA_COMP_SS'], cats['FICA_EMP_SS'])
|
||||||
|
self.assertPayrollEqual(cats['FICA_COMP_M'], cats['FICA_EMP_M'])
|
||||||
|
self.assertPayrollEqual(cats['FUTA_WAGES'], self.FUTA_MAX_WAGE)
|
||||||
|
self.assertPayrollEqual(cats['FUTA'], cats['FUTA_WAGES'] * futa_rate)
|
||||||
|
|
||||||
|
def test_2017_taxes_with_futa_exempt(self):
|
||||||
|
futa_rate = self.FUTA_RATE_EXEMPT / -100.0 # because of exemption
|
||||||
|
|
||||||
|
# social security salary
|
||||||
|
salary = self.FICA_M_ADD_START_WAGE
|
||||||
|
|
||||||
|
employee = self._createEmployee()
|
||||||
|
|
||||||
|
self._createContract(employee, salary, futa_type=USHrContract.FUTA_TYPE_EXEMPT)
|
||||||
|
|
||||||
|
self._log('2017 tax first payslip:')
|
||||||
|
payslip = self._createPayslip(employee, '2017-01-01', '2017-01-31')
|
||||||
|
|
||||||
|
payslip.compute_sheet()
|
||||||
|
|
||||||
|
cats = self._getCategories(payslip)
|
||||||
|
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_SS_WAGES'], self.FICA_SS_MAX_WAGE)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_SS'], cats['FICA_EMP_SS_WAGES'] * self.FICA_SS)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M_WAGES'], salary)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M'], cats['FICA_EMP_M_WAGES'] * self.FICA_M)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M_ADD_WAGES'], 0.0)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M_ADD'], cats['FICA_EMP_M_ADD_WAGES'] * self.FICA_M_ADD)
|
||||||
|
self.assertPayrollEqual(cats['FICA_COMP_SS'], cats['FICA_EMP_SS'])
|
||||||
|
self.assertPayrollEqual(cats['FICA_COMP_M'], cats['FICA_EMP_M'])
|
||||||
|
|
||||||
|
futa_wages = 0.0
|
||||||
|
if 'FUTA_WAGES' in cats:
|
||||||
|
futa_wages = cats['FUTA_WAGES']
|
||||||
|
futa = 0.0
|
||||||
|
if 'FUTA' in cats:
|
||||||
|
futa = cats['FUTA']
|
||||||
|
self.assertPayrollEqual(futa_wages, 0.0)
|
||||||
|
self.assertPayrollEqual(futa, futa_wages * futa_rate)
|
||||||
|
|
||||||
|
def test_2017_fed_income_withholding_nonresident_alien(self):
|
||||||
|
salary = 3500.00
|
||||||
|
schedule_pay = 'quarterly'
|
||||||
|
w4_allowances = 1
|
||||||
|
w4_allowance_amt = 1012.50 * w4_allowances
|
||||||
|
nra_adjustment = 575.00 # for quarterly
|
||||||
|
adjusted_salary = salary - w4_allowance_amt + nra_adjustment # 3050
|
||||||
|
|
||||||
|
###
|
||||||
|
# Single QUARTERLY form Publication 15
|
||||||
|
expected_withholding = self.float_round(-(233.10 + ((adjusted_salary - 2906) * 0.15)), self.payroll_digits)
|
||||||
|
|
||||||
|
employee = self._createEmployee()
|
||||||
|
self._createContract(employee, salary, schedule_pay, w4_allowances, 'single',
|
||||||
|
w4_is_nonresident_alien=True)
|
||||||
|
|
||||||
|
self._log('2017 fed income single payslip nonresident alien: adjusted_salary: ' + str(adjusted_salary))
|
||||||
|
payslip = self._createPayslip(employee, '2017-01-01', '2017-01-31')
|
||||||
|
|
||||||
|
payslip.compute_sheet()
|
||||||
|
|
||||||
|
cats = self._getCategories(payslip)
|
||||||
|
|
||||||
|
self.assertPayrollEqual(cats['FED_INC_WITHHOLD'], expected_withholding)
|
||||||
|
|
||||||
|
def test_2017_fed_income_additional_withholding(self):
|
||||||
|
salary = 50000.00
|
||||||
|
schedule_pay = 'annually'
|
||||||
|
w4_additional_withholding = 5000.0
|
||||||
|
w4_allowances = 2
|
||||||
|
w4_allowance_amt = 4050.0 * w4_allowances
|
||||||
|
adjusted_salary = salary - w4_allowance_amt # 41900
|
||||||
|
|
||||||
|
###
|
||||||
|
# Single ANNUAL form Publication 15
|
||||||
|
expected_withholding = \
|
||||||
|
self.float_round(-((1865.00 + ((adjusted_salary - 27300) * 0.15)) + w4_additional_withholding),
|
||||||
|
self.payroll_digits)
|
||||||
|
|
||||||
|
employee = self._createEmployee()
|
||||||
|
self._createContract(employee, salary, schedule_pay, w4_allowances, 'married',
|
||||||
|
w4_additional_withholding=w4_additional_withholding)
|
||||||
|
|
||||||
|
self._log('2017 fed income married payslip additional withholding: adjusted_salary: ' + str(adjusted_salary))
|
||||||
|
payslip = self._createPayslip(employee, '2017-01-01', '2017-01-31')
|
||||||
|
|
||||||
|
payslip.compute_sheet()
|
||||||
|
|
||||||
|
cats = self._getCategories(payslip)
|
||||||
|
|
||||||
|
self.assertPayrollEqual(cats['FED_INC_WITHHOLD'], expected_withholding)
|
||||||
|
|
||||||
|
def test_2017_taxes_with_w4_exempt(self):
|
||||||
|
salary = 6000.0
|
||||||
|
schedule_pay = 'bi-weekly'
|
||||||
|
w4_allowances = 0
|
||||||
|
employee = self._createEmployee()
|
||||||
|
self._createContract(employee, salary, schedule_pay, w4_allowances, '')
|
||||||
|
|
||||||
|
self._log('2017 tax w4 exempt payslip:')
|
||||||
|
payslip = self._createPayslip(employee, '2017-01-01', '2017-01-31')
|
||||||
|
|
||||||
|
payslip.compute_sheet()
|
||||||
|
|
||||||
|
cats = self._getCategories(payslip)
|
||||||
|
|
||||||
|
fed_inc_withhold = 0.0
|
||||||
|
if 'FED_INC_WITHHOLD' in cats:
|
||||||
|
fed_inc_withhold = cats['FED_INC_WITHHOLD']
|
||||||
|
self.assertPayrollEqual(fed_inc_withhold, 0.0)
|
||||||
344
l10n_us_hr_payroll/tests/test_us_payslip_2018.py
Executable file
344
l10n_us_hr_payroll/tests/test_us_payslip_2018.py
Executable file
@@ -0,0 +1,344 @@
|
|||||||
|
from .test_us_payslip import TestUsPayslip, process_payslip
|
||||||
|
|
||||||
|
from odoo.addons.l10n_us_hr_payroll.l10n_us_hr_payroll import USHrContract
|
||||||
|
|
||||||
|
from sys import float_info
|
||||||
|
|
||||||
|
|
||||||
|
class TestUsPayslip2018(TestUsPayslip):
|
||||||
|
# FUTA Constants
|
||||||
|
FUTA_RATE_NORMAL = 0.6
|
||||||
|
FUTA_RATE_BASIC = 6.0
|
||||||
|
FUTA_RATE_EXEMPT = 0.0
|
||||||
|
|
||||||
|
# Wage caps
|
||||||
|
FICA_SS_MAX_WAGE = 128400.0
|
||||||
|
FICA_M_MAX_WAGE = float_info.max
|
||||||
|
FICA_M_ADD_START_WAGE = 200000.0
|
||||||
|
FUTA_MAX_WAGE = 7000.0
|
||||||
|
|
||||||
|
# Rates
|
||||||
|
FICA_SS = 6.2 / -100.0
|
||||||
|
FICA_M = 1.45 / -100.0
|
||||||
|
FUTA = FUTA_RATE_NORMAL / -100.0
|
||||||
|
FICA_M_ADD = 0.9 / -100.0
|
||||||
|
|
||||||
|
###
|
||||||
|
# 2018 Taxes and Rates
|
||||||
|
###
|
||||||
|
|
||||||
|
def test_2018_taxes(self):
|
||||||
|
# salary is high so that second payslip runs over max
|
||||||
|
# social security salary
|
||||||
|
salary = 80000.0
|
||||||
|
|
||||||
|
employee = self._createEmployee()
|
||||||
|
|
||||||
|
self._createContract(employee, salary)
|
||||||
|
|
||||||
|
self._log('2017 tax last slip')
|
||||||
|
payslip = self._createPayslip(employee, '2017-12-01', '2017-12-31')
|
||||||
|
payslip.compute_sheet()
|
||||||
|
process_payslip(payslip)
|
||||||
|
|
||||||
|
self._log('2018 tax first payslip:')
|
||||||
|
payslip = self._createPayslip(employee, '2018-01-01', '2018-01-31')
|
||||||
|
|
||||||
|
payslip.compute_sheet()
|
||||||
|
|
||||||
|
cats = self._getCategories(payslip)
|
||||||
|
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_SS_WAGES'], salary)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_SS'], cats['FICA_EMP_SS_WAGES'] * self.FICA_SS)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M_WAGES'], salary)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M'], cats['FICA_EMP_M_WAGES'] * self.FICA_M)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M_ADD_WAGES'], 0.0)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M_ADD'], 0.0)
|
||||||
|
self.assertPayrollEqual(cats['FICA_COMP_SS'], cats['FICA_EMP_SS'])
|
||||||
|
self.assertPayrollEqual(cats['FICA_COMP_M'], cats['FICA_EMP_M'])
|
||||||
|
self.assertPayrollEqual(cats['FUTA_WAGES'], self.FUTA_MAX_WAGE)
|
||||||
|
self.assertPayrollEqual(cats['FUTA'], cats['FUTA_WAGES'] * self.FUTA)
|
||||||
|
|
||||||
|
process_payslip(payslip)
|
||||||
|
|
||||||
|
# Make a new payslip, this one will have maximums for FICA Social Security Wages
|
||||||
|
|
||||||
|
remaining_ss_wages = self.FICA_SS_MAX_WAGE - salary if (self.FICA_SS_MAX_WAGE - 2 * salary < salary) else salary
|
||||||
|
remaining_m_wages = self.FICA_M_MAX_WAGE - salary if (self.FICA_M_MAX_WAGE - 2 * salary < salary) else salary
|
||||||
|
|
||||||
|
self._log('2018 tax second payslip:')
|
||||||
|
payslip = self._createPayslip(employee, '2018-02-01', '2018-02-28')
|
||||||
|
|
||||||
|
payslip.compute_sheet()
|
||||||
|
|
||||||
|
cats = self._getCategories(payslip)
|
||||||
|
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_SS_WAGES'], remaining_ss_wages)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_SS'], cats['FICA_EMP_SS_WAGES'] * self.FICA_SS)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M_WAGES'], remaining_m_wages)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M'], cats['FICA_EMP_M_WAGES'] * self.FICA_M)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M_ADD_WAGES'], 0.0)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M_ADD'], 0.0)
|
||||||
|
self.assertPayrollEqual(cats['FICA_COMP_SS'], cats['FICA_EMP_SS'])
|
||||||
|
self.assertPayrollEqual(cats['FICA_COMP_M'], cats['FICA_EMP_M'])
|
||||||
|
self.assertPayrollEqual(cats['FUTA_WAGES'], 0)
|
||||||
|
self.assertPayrollEqual(cats['FUTA'], 0)
|
||||||
|
|
||||||
|
process_payslip(payslip)
|
||||||
|
|
||||||
|
# Make a new payslip, this one will have reached Medicare Additional (employee only)
|
||||||
|
|
||||||
|
self._log('2018 tax third payslip:')
|
||||||
|
payslip = self._createPayslip(employee, '2018-03-01', '2018-03-31')
|
||||||
|
|
||||||
|
payslip.compute_sheet()
|
||||||
|
|
||||||
|
cats = self._getCategories(payslip)
|
||||||
|
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M_WAGES'], salary)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M_ADD_WAGES'], self.FICA_M_ADD_START_WAGE - (salary * 2)) # aka 40k
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M_ADD'], cats['FICA_EMP_M_ADD_WAGES'] * self.FICA_M_ADD)
|
||||||
|
|
||||||
|
process_payslip(payslip)
|
||||||
|
|
||||||
|
# Make a new payslip, this one will have all salary as Medicare Additional
|
||||||
|
|
||||||
|
self._log('2018 tax fourth payslip:')
|
||||||
|
payslip = self._createPayslip(employee, '2018-04-01', '2018-04-30')
|
||||||
|
|
||||||
|
payslip.compute_sheet()
|
||||||
|
|
||||||
|
cats = self._getCategories(payslip)
|
||||||
|
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M_WAGES'], salary)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M_ADD_WAGES'], salary)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M_ADD'], cats['FICA_EMP_M_ADD_WAGES'] * self.FICA_M_ADD)
|
||||||
|
|
||||||
|
process_payslip(payslip)
|
||||||
|
|
||||||
|
def test_2018_fed_income_withholding_single(self):
|
||||||
|
salary = 6000.00
|
||||||
|
schedule_pay = 'monthly'
|
||||||
|
w4_allowances = 3
|
||||||
|
w4_allowance_amt = 345.80 * w4_allowances
|
||||||
|
adjusted_salary = salary - w4_allowance_amt # should be 4962.60, but would work over a wide value for the rate
|
||||||
|
###
|
||||||
|
# Single MONTHLY form Publication 15
|
||||||
|
expected_withholding = self.float_round(-(371.12 + ((adjusted_salary - 3533) * 0.22)), self.payroll_digits)
|
||||||
|
|
||||||
|
employee = self._createEmployee()
|
||||||
|
self._createContract(employee, salary, schedule_pay, w4_allowances, 'single')
|
||||||
|
|
||||||
|
self._log('2018 fed income single payslip: adjusted_salary: ' + str(adjusted_salary))
|
||||||
|
payslip = self._createPayslip(employee, '2018-01-01', '2018-01-31')
|
||||||
|
|
||||||
|
payslip.compute_sheet()
|
||||||
|
|
||||||
|
cats = self._getCategories(payslip)
|
||||||
|
|
||||||
|
self.assertPayrollEqual(cats['FED_INC_WITHHOLD'], expected_withholding)
|
||||||
|
|
||||||
|
def test_2018_fed_income_withholding_married_as_single(self):
|
||||||
|
salary = 500.00
|
||||||
|
schedule_pay = 'weekly'
|
||||||
|
w4_allowances = 1
|
||||||
|
w4_allowance_amt = 79.80 * w4_allowances
|
||||||
|
adjusted_salary = salary - w4_allowance_amt # should be 420.50, but would work over a wide value for the rate
|
||||||
|
###
|
||||||
|
# Single MONTHLY form Publication 15
|
||||||
|
expected_withholding = self.float_round(-(18.30 + ((adjusted_salary - 254) * 0.12)), self.payroll_digits)
|
||||||
|
|
||||||
|
employee = self._createEmployee()
|
||||||
|
self._createContract(employee, salary, schedule_pay, w4_allowances, 'married_as_single')
|
||||||
|
|
||||||
|
self._log('2018 fed income married_as_single payslip: adjusted_salary: ' + str(adjusted_salary))
|
||||||
|
payslip = self._createPayslip(employee, '2018-01-01', '2018-01-31')
|
||||||
|
|
||||||
|
payslip.compute_sheet()
|
||||||
|
|
||||||
|
cats = self._getCategories(payslip)
|
||||||
|
|
||||||
|
self.assertPayrollEqual(cats['FED_INC_WITHHOLD'], expected_withholding)
|
||||||
|
|
||||||
|
def test_2018_fed_income_withholding_married(self):
|
||||||
|
salary = 14000.00
|
||||||
|
schedule_pay = 'bi-weekly'
|
||||||
|
w4_allowances = 2
|
||||||
|
w4_allowance_amt = 159.60 * w4_allowances
|
||||||
|
adjusted_salary = salary - w4_allowance_amt # should be 13680.80, but would work over a wide value for the rate
|
||||||
|
###
|
||||||
|
# Single MONTHLY form Publication 15
|
||||||
|
expected_withholding = self.float_round(-(2468.56 + ((adjusted_salary - 12560) * 0.32)), self.payroll_digits)
|
||||||
|
|
||||||
|
employee = self._createEmployee()
|
||||||
|
self._createContract(employee, salary, schedule_pay, w4_allowances, 'married')
|
||||||
|
|
||||||
|
self._log('2018 fed income married payslip: adjusted_salary: ' + str(adjusted_salary))
|
||||||
|
payslip = self._createPayslip(employee, '2018-01-01', '2018-01-31')
|
||||||
|
|
||||||
|
payslip.compute_sheet()
|
||||||
|
|
||||||
|
cats = self._getCategories(payslip)
|
||||||
|
|
||||||
|
self.assertPayrollEqual(cats['FED_INC_WITHHOLD'], expected_withholding)
|
||||||
|
|
||||||
|
def test_2018_taxes_with_external(self):
|
||||||
|
|
||||||
|
# social security salary
|
||||||
|
salary = self.FICA_M_ADD_START_WAGE
|
||||||
|
external_wages = 6000.0
|
||||||
|
|
||||||
|
employee = self._createEmployee()
|
||||||
|
|
||||||
|
self._createContract(employee, salary, external_wages=external_wages)
|
||||||
|
|
||||||
|
self._log('2018 tax first payslip:')
|
||||||
|
payslip = self._createPayslip(employee, '2018-01-01', '2018-01-31')
|
||||||
|
|
||||||
|
payslip.compute_sheet()
|
||||||
|
|
||||||
|
cats = self._getCategories(payslip)
|
||||||
|
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_SS_WAGES'], self.FICA_SS_MAX_WAGE - external_wages)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_SS'], cats['FICA_EMP_SS_WAGES'] * self.FICA_SS)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M_WAGES'], salary)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M'], cats['FICA_EMP_M_WAGES'] * self.FICA_M)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M_ADD_WAGES'], 0.0)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M_ADD'], cats['FICA_EMP_M_ADD_WAGES'] * self.FICA_M_ADD)
|
||||||
|
self.assertPayrollEqual(cats['FICA_COMP_SS'], cats['FICA_EMP_SS'])
|
||||||
|
self.assertPayrollEqual(cats['FICA_COMP_M'], cats['FICA_EMP_M'])
|
||||||
|
self.assertPayrollEqual(cats['FUTA_WAGES'], self.FUTA_MAX_WAGE - external_wages)
|
||||||
|
self.assertPayrollEqual(cats['FUTA'], cats['FUTA_WAGES'] * self.FUTA)
|
||||||
|
|
||||||
|
def test_2018_taxes_with_full_futa(self):
|
||||||
|
futa_rate = self.FUTA_RATE_BASIC / -100.0
|
||||||
|
# social security salary
|
||||||
|
salary = self.FICA_M_ADD_START_WAGE
|
||||||
|
|
||||||
|
employee = self._createEmployee()
|
||||||
|
|
||||||
|
self._createContract(employee, salary, futa_type=USHrContract.FUTA_TYPE_BASIC)
|
||||||
|
|
||||||
|
self._log('2018 tax first payslip:')
|
||||||
|
payslip = self._createPayslip(employee, '2018-01-01', '2018-01-31')
|
||||||
|
|
||||||
|
payslip.compute_sheet()
|
||||||
|
|
||||||
|
cats = self._getCategories(payslip)
|
||||||
|
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_SS_WAGES'], self.FICA_SS_MAX_WAGE)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_SS'], cats['FICA_EMP_SS_WAGES'] * self.FICA_SS)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M_WAGES'], salary)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M'], cats['FICA_EMP_M_WAGES'] * self.FICA_M)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M_ADD_WAGES'], 0.0)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M_ADD'], cats['FICA_EMP_M_ADD_WAGES'] * self.FICA_M_ADD)
|
||||||
|
self.assertPayrollEqual(cats['FICA_COMP_SS'], cats['FICA_EMP_SS'])
|
||||||
|
self.assertPayrollEqual(cats['FICA_COMP_M'], cats['FICA_EMP_M'])
|
||||||
|
self.assertPayrollEqual(cats['FUTA_WAGES'], self.FUTA_MAX_WAGE)
|
||||||
|
self.assertPayrollEqual(cats['FUTA'], cats['FUTA_WAGES'] * futa_rate)
|
||||||
|
|
||||||
|
def test_2018_taxes_with_futa_exempt(self):
|
||||||
|
futa_rate = self.FUTA_RATE_EXEMPT / -100.0 # because of exemption
|
||||||
|
|
||||||
|
# social security salary
|
||||||
|
salary = self.FICA_M_ADD_START_WAGE
|
||||||
|
|
||||||
|
employee = self._createEmployee()
|
||||||
|
|
||||||
|
self._createContract(employee, salary, futa_type=USHrContract.FUTA_TYPE_EXEMPT)
|
||||||
|
|
||||||
|
self._log('2018 tax first payslip:')
|
||||||
|
payslip = self._createPayslip(employee, '2018-01-01', '2018-01-31')
|
||||||
|
|
||||||
|
payslip.compute_sheet()
|
||||||
|
|
||||||
|
cats = self._getCategories(payslip)
|
||||||
|
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_SS_WAGES'], self.FICA_SS_MAX_WAGE)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_SS'], cats['FICA_EMP_SS_WAGES'] * self.FICA_SS)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M_WAGES'], salary)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M'], cats['FICA_EMP_M_WAGES'] * self.FICA_M)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M_ADD_WAGES'], 0.0)
|
||||||
|
self.assertPayrollEqual(cats['FICA_EMP_M_ADD'], cats['FICA_EMP_M_ADD_WAGES'] * self.FICA_M_ADD)
|
||||||
|
self.assertPayrollEqual(cats['FICA_COMP_SS'], cats['FICA_EMP_SS'])
|
||||||
|
self.assertPayrollEqual(cats['FICA_COMP_M'], cats['FICA_EMP_M'])
|
||||||
|
|
||||||
|
futa_wages = 0.0
|
||||||
|
if 'FUTA_WAGES' in cats:
|
||||||
|
futa_wages = cats['FUTA_WAGES']
|
||||||
|
futa = 0.0
|
||||||
|
if 'FUTA' in cats:
|
||||||
|
futa = cats['FUTA']
|
||||||
|
self.assertPayrollEqual(futa_wages, 0.0)
|
||||||
|
self.assertPayrollEqual(futa, futa_wages * futa_rate)
|
||||||
|
|
||||||
|
def test_2018_fed_income_withholding_nonresident_alien(self):
|
||||||
|
salary = 3500.00
|
||||||
|
schedule_pay = 'quarterly'
|
||||||
|
w4_allowances = 1
|
||||||
|
w4_allowance_amt = 1037.50 * w4_allowances
|
||||||
|
nra_adjustment = 1962.50 # for quarterly
|
||||||
|
adjusted_salary = salary - w4_allowance_amt + nra_adjustment # 4425
|
||||||
|
|
||||||
|
###
|
||||||
|
# Single QUARTERLY form Publication 15
|
||||||
|
expected_withholding = self.float_round(-(238.10 + ((adjusted_salary - 3306) * 0.12)), self.payroll_digits)
|
||||||
|
|
||||||
|
employee = self._createEmployee()
|
||||||
|
self._createContract(employee, salary, schedule_pay, w4_allowances, 'single',
|
||||||
|
w4_is_nonresident_alien=True)
|
||||||
|
|
||||||
|
self._log('2018 fed income single payslip nonresident alien: adjusted_salary: ' + str(adjusted_salary))
|
||||||
|
payslip = self._createPayslip(employee, '2018-01-01', '2018-01-31')
|
||||||
|
|
||||||
|
payslip.compute_sheet()
|
||||||
|
|
||||||
|
cats = self._getCategories(payslip)
|
||||||
|
|
||||||
|
self.assertPayrollEqual(cats['FED_INC_WITHHOLD'], expected_withholding)
|
||||||
|
|
||||||
|
def test_2018_fed_income_additional_withholding(self):
|
||||||
|
salary = 50000.00
|
||||||
|
schedule_pay = 'annually'
|
||||||
|
w4_additional_withholding = 5000.0
|
||||||
|
w4_allowances = 2
|
||||||
|
w4_allowance_amt = 4150.00 * w4_allowances
|
||||||
|
adjusted_salary = salary - w4_allowance_amt # 41700
|
||||||
|
|
||||||
|
###
|
||||||
|
# Single ANNUAL form Publication 15
|
||||||
|
expected_withholding = \
|
||||||
|
self.float_round(-((1905.00 + ((adjusted_salary - 30600) * 0.12)) + w4_additional_withholding),
|
||||||
|
self.payroll_digits)
|
||||||
|
|
||||||
|
employee = self._createEmployee()
|
||||||
|
self._createContract(employee, salary, schedule_pay, w4_allowances, 'married',
|
||||||
|
w4_additional_withholding=w4_additional_withholding)
|
||||||
|
|
||||||
|
self._log('2018 fed income married payslip additional withholding: adjusted_salary: ' + str(adjusted_salary))
|
||||||
|
payslip = self._createPayslip(employee, '2018-01-01', '2018-01-31')
|
||||||
|
|
||||||
|
payslip.compute_sheet()
|
||||||
|
|
||||||
|
cats = self._getCategories(payslip)
|
||||||
|
|
||||||
|
self.assertPayrollEqual(cats['FED_INC_WITHHOLD'], expected_withholding)
|
||||||
|
|
||||||
|
def test_2018_taxes_with_w4_exempt(self):
|
||||||
|
salary = 6000.0
|
||||||
|
schedule_pay = 'bi-weekly'
|
||||||
|
w4_allowances = 0
|
||||||
|
employee = self._createEmployee()
|
||||||
|
self._createContract(employee, salary, schedule_pay, w4_allowances, '')
|
||||||
|
|
||||||
|
self._log('2018 tax w4 exempt payslip:')
|
||||||
|
payslip = self._createPayslip(employee, '2018-01-01', '2018-01-31')
|
||||||
|
|
||||||
|
payslip.compute_sheet()
|
||||||
|
|
||||||
|
cats = self._getCategories(payslip)
|
||||||
|
|
||||||
|
fed_inc_withhold = 0.0
|
||||||
|
if 'FED_INC_WITHHOLD' in cats:
|
||||||
|
fed_inc_withhold = cats['FED_INC_WITHHOLD']
|
||||||
|
self.assertPayrollEqual(fed_inc_withhold, 0.0)
|
||||||
2
l10n_us_hr_payroll_account/__init__.py
Executable file
2
l10n_us_hr_payroll_account/__init__.py
Executable file
@@ -0,0 +1,2 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
22
l10n_us_hr_payroll_account/__manifest__.py
Executable file
22
l10n_us_hr_payroll_account/__manifest__.py
Executable file
@@ -0,0 +1,22 @@
|
|||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
{
|
||||||
|
'name': 'USA - Payroll with Accounting',
|
||||||
|
'author': 'Hibou Corp. <hello@hibou.io>',
|
||||||
|
'license': 'AGPL-3',
|
||||||
|
'category': 'Localization',
|
||||||
|
'depends': ['l10n_us_hr_payroll', 'hr_payroll_account'],
|
||||||
|
'version': '11.0.2017.0.0',
|
||||||
|
'description': """
|
||||||
|
USA Payroll Accounting hooks.
|
||||||
|
=============================
|
||||||
|
|
||||||
|
* Links Rules to Accounts based on US Accounting Localization
|
||||||
|
""",
|
||||||
|
|
||||||
|
'auto_install': False,
|
||||||
|
'website': 'https://hibou.io/',
|
||||||
|
'data':[
|
||||||
|
'l10n_us_hr_payroll_account_data.xml',
|
||||||
|
],
|
||||||
|
'installable': True
|
||||||
|
}
|
||||||
87
l10n_us_hr_payroll_account/l10n_us_hr_payroll_account_data.xml
Executable file
87
l10n_us_hr_payroll_account/l10n_us_hr_payroll_account_data.xml
Executable file
@@ -0,0 +1,87 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<data>
|
||||||
|
<!-- Basic -->
|
||||||
|
<record id="hr_payroll.hr_rule_basic" model="hr.salary.rule">
|
||||||
|
<!-- Salary Expense -->
|
||||||
|
<field name="account_debit" search="[('code', 'like', '212100%')]"/>
|
||||||
|
</record>
|
||||||
|
<record id="hr_payroll.hr_rule_net" model="hr.salary.rule">
|
||||||
|
<!-- Salary Payable / Accounts Payable -->
|
||||||
|
<field name="account_credit" search="[('code', 'like', '111100%')]"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Employee Deductions FICA & Federal Withholding -->
|
||||||
|
<record id="l10n_us_hr_payroll.hr_payroll_rules_fica_emp_ss_2016" model="hr.salary.rule">
|
||||||
|
<!-- Accounts Payable -->
|
||||||
|
<field name="account_debit" search="[('code', 'like', '111100%')]"/>
|
||||||
|
</record>
|
||||||
|
<record id="l10n_us_hr_payroll.hr_payroll_rules_fica_emp_m_2016" model="hr.salary.rule">
|
||||||
|
<!-- Accounts Payable -->
|
||||||
|
<field name="account_debit" search="[('code', 'like', '111100%')]"/>
|
||||||
|
</record>
|
||||||
|
<record id="l10n_us_hr_payroll.hr_payroll_rules_fica_emp_m_add_2016" model="hr.salary.rule">
|
||||||
|
<!-- Accounts Payable -->
|
||||||
|
<field name="account_debit" search="[('code', 'like', '111100%')]"/>
|
||||||
|
</record>
|
||||||
|
<record id="l10n_us_hr_payroll.hr_payroll_rules_fed_inc_withhold_2016_single" model="hr.salary.rule">
|
||||||
|
<!-- Accounts Payable -->
|
||||||
|
<field name="account_debit" search="[('code', 'like', '111100%')]"/>
|
||||||
|
</record>
|
||||||
|
<record id="l10n_us_hr_payroll.hr_payroll_rules_fed_inc_withhold_2016_married" model="hr.salary.rule">
|
||||||
|
<!-- Accounts Payable -->
|
||||||
|
<field name="account_debit" search="[('code', 'like', '111100%')]"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Company Contributions FICA & FUTA -->
|
||||||
|
<record id="l10n_us_hr_payroll.hr_payroll_rules_fica_comp_ss" model="hr.salary.rule">
|
||||||
|
<!-- Accounts Payable -->
|
||||||
|
<field name="account_debit" search="[('code', 'like', '111100%')]"/>
|
||||||
|
<!-- Salary Expense -->
|
||||||
|
<field name="account_credit" search="[('code', 'like', '220000%')]"/>
|
||||||
|
</record>
|
||||||
|
<record id="l10n_us_hr_payroll.hr_payroll_rules_fica_comp_m" model="hr.salary.rule">
|
||||||
|
<!-- Accounts Payable -->
|
||||||
|
<field name="account_debit" search="[('code', 'like', '111100%')]"/>
|
||||||
|
<!-- Salary Expense -->
|
||||||
|
<field name="account_credit" search="[('code', 'like', '220000%')]"/>
|
||||||
|
</record>
|
||||||
|
<record id="l10n_us_hr_payroll.hr_payroll_rules_futa_2016" model="hr.salary.rule">
|
||||||
|
<!-- Accounts Payable -->
|
||||||
|
<field name="account_debit" search="[('code', 'like', '111100%')]"/>
|
||||||
|
<!-- Salary Expense -->
|
||||||
|
<field name="account_credit" search="[('code', 'like', '220000%')]"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- 2017 -->
|
||||||
|
<!-- Employee Deductions FICA & Federal Withholding -->
|
||||||
|
<record id="l10n_us_hr_payroll.hr_payroll_rules_fica_emp_ss_2017" model="hr.salary.rule">
|
||||||
|
<!-- Accounts Payable -->
|
||||||
|
<field name="account_debit" search="[('code', 'like', '111100%')]"/>
|
||||||
|
</record>
|
||||||
|
<record id="l10n_us_hr_payroll.hr_payroll_rules_fica_emp_m_2017" model="hr.salary.rule">
|
||||||
|
<!-- Accounts Payable -->
|
||||||
|
<field name="account_debit" search="[('code', 'like', '111100%')]"/>
|
||||||
|
</record>
|
||||||
|
<record id="l10n_us_hr_payroll.hr_payroll_rules_fica_emp_m_add_2017" model="hr.salary.rule">
|
||||||
|
<!-- Accounts Payable -->
|
||||||
|
<field name="account_debit" search="[('code', 'like', '111100%')]"/>
|
||||||
|
</record>
|
||||||
|
<record id="l10n_us_hr_payroll.hr_payroll_rules_fed_inc_withhold_2017_single" model="hr.salary.rule">
|
||||||
|
<!-- Accounts Payable -->
|
||||||
|
<field name="account_debit" search="[('code', 'like', '111100%')]"/>
|
||||||
|
</record>
|
||||||
|
<record id="l10n_us_hr_payroll.hr_payroll_rules_fed_inc_withhold_2017_married" model="hr.salary.rule">
|
||||||
|
<!-- Accounts Payable -->
|
||||||
|
<field name="account_debit" search="[('code', 'like', '111100%')]"/>
|
||||||
|
</record>
|
||||||
|
<!-- Company Contributions FICA & FUTA -->
|
||||||
|
<record id="l10n_us_hr_payroll.hr_payroll_rules_futa_2017" model="hr.salary.rule">
|
||||||
|
<!-- Accounts Payable -->
|
||||||
|
<field name="account_debit" search="[('code', 'like', '111100%')]"/>
|
||||||
|
<!-- Salary Expense -->
|
||||||
|
<field name="account_credit" search="[('code', 'like', '220000%')]"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</data>
|
||||||
|
</odoo>
|
||||||
3
l10n_us_mo_hr_payroll/__init__.py
Executable file
3
l10n_us_mo_hr_payroll/__init__.py
Executable file
@@ -0,0 +1,3 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from . import hr_payroll
|
||||||
28
l10n_us_mo_hr_payroll/__manifest__.py
Executable file
28
l10n_us_mo_hr_payroll/__manifest__.py
Executable file
@@ -0,0 +1,28 @@
|
|||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
{
|
||||||
|
'name': 'USA - Missouri - Payroll',
|
||||||
|
'author': 'Hibou Corp. <hello@hibou.io>',
|
||||||
|
'license': 'AGPL-3',
|
||||||
|
'category': 'Localization',
|
||||||
|
'depends': ['l10n_us_hr_payroll'],
|
||||||
|
'version': '11.0.2018.0.0',
|
||||||
|
'description': """
|
||||||
|
USA::Missouri Payroll Rules.
|
||||||
|
============================
|
||||||
|
|
||||||
|
* Contribution register and partner for Missouri Department of Revenue - Unemployment
|
||||||
|
* Contribution register and partner for Missouri Department of Revenue - Income Tax Withholding
|
||||||
|
* Contract level Missouri Exemptions and MO W-4 fields
|
||||||
|
* Company level Missouri Unemployment Rate
|
||||||
|
""",
|
||||||
|
|
||||||
|
'auto_install': False,
|
||||||
|
'website': 'https://hibou.io/',
|
||||||
|
'data': [
|
||||||
|
'hr_payroll_view.xml',
|
||||||
|
'data/base.xml',
|
||||||
|
'data/rules_2018.xml',
|
||||||
|
'data/final.xml',
|
||||||
|
],
|
||||||
|
'installable': True
|
||||||
|
}
|
||||||
46
l10n_us_mo_hr_payroll/data/base.xml
Executable file
46
l10n_us_mo_hr_payroll/data/base.xml
Executable file
@@ -0,0 +1,46 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<data>
|
||||||
|
<!-- CONTRIBUTION REGISTERS -->
|
||||||
|
<record id="res_partner_modor_unemp" model="res.partner">
|
||||||
|
<field name="name">Missouri Department of Revenue - Unemployment Tax</field>
|
||||||
|
<field name="supplier">1</field>
|
||||||
|
<field eval="0" name="customer"/>
|
||||||
|
</record>
|
||||||
|
<record id="res_partner_modor_withhold" model="res.partner">
|
||||||
|
<field name="name">Missouri Department of Revenue - Income Tax Withholding</field>
|
||||||
|
<field name="supplier">1</field>
|
||||||
|
<field eval="0" name="customer"/>
|
||||||
|
</record>
|
||||||
|
<record id="contrib_register_modor_unemp" model="hr.contribution.register">
|
||||||
|
<field name="name">Missouri Unemployment</field>
|
||||||
|
<field name="note">Missouri Department of Revemie - Unemployment</field>
|
||||||
|
<field name="partner_id" ref="res_partner_modor_unemp"/>
|
||||||
|
</record>
|
||||||
|
<record id="contrib_register_modor_withhold" model="hr.contribution.register">
|
||||||
|
<field name="name">Missouri Income Tax Withholding</field>
|
||||||
|
<field name="note">Missouri Department of Revenue - Income Tax Withholding</field>
|
||||||
|
<field name="partner_id" ref="res_partner_modor_withhold"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- HR SALARY RULE CATEGORIES-->
|
||||||
|
<record id="hr_payroll_mo_unemp_wages" model="hr.salary.rule.category">
|
||||||
|
<field name="name">Missouri Unemployment - Wages</field>
|
||||||
|
<field name="code">MO_UNEMP_WAGES</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="hr_payroll_mo_unemp" model="hr.salary.rule.category">
|
||||||
|
<field name="name">Missouri Unemployment</field>
|
||||||
|
<field name="code">MO_UNEMP</field>
|
||||||
|
<field name="parent_id" ref="hr_payroll.COMP"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="hr_payroll_mo_income_withhold" model="hr.salary.rule.category">
|
||||||
|
<field name="name">Missouri Income Withholding</field>
|
||||||
|
<field name="code">MO_INC_WITHHOLD</field>
|
||||||
|
<field name="parent_id" ref="hr_payroll.DED"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</data>
|
||||||
|
</odoo>
|
||||||
19
l10n_us_mo_hr_payroll/data/final.xml
Executable file
19
l10n_us_mo_hr_payroll/data/final.xml
Executable file
@@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<data>
|
||||||
|
|
||||||
|
<!-- HR PAYROLL STRUCTURE -->
|
||||||
|
<record id="hr_payroll_salary_structure_us_mo_employee" model="hr.payroll.structure">
|
||||||
|
<field name="code">US_MO_EMP</field>
|
||||||
|
<field name="name">USA Missouri Employee</field>
|
||||||
|
<field eval="[(6, 0, [
|
||||||
|
ref('hr_payroll_rules_mo_unemp_wages_2018'),
|
||||||
|
ref('hr_payroll_rules_mo_unemp_2018'),
|
||||||
|
ref('hr_payroll_rules_mo_inc_withhold_2018'),
|
||||||
|
])]" name="rule_ids"/>
|
||||||
|
<field name="company_id" ref="base.main_company"/>
|
||||||
|
<field name="parent_id" ref="l10n_us_hr_payroll.hr_payroll_salary_structure_us_employee"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</data>
|
||||||
|
</odoo>
|
||||||
159
l10n_us_mo_hr_payroll/data/rules_2018.xml
Executable file
159
l10n_us_mo_hr_payroll/data/rules_2018.xml
Executable file
@@ -0,0 +1,159 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<data>
|
||||||
|
|
||||||
|
<!-- HR SALARY RULES-->
|
||||||
|
<record id="hr_payroll_rules_mo_unemp_wages_2018" model="hr.salary.rule">
|
||||||
|
<field name="sequence" eval="423"/>
|
||||||
|
<field name="category_id" ref="hr_payroll_mo_unemp_wages"/>
|
||||||
|
<field name="name">Missouri Unemployment - Wages (2018)</field>
|
||||||
|
<field name="code">MO_UNEMP_WAGES_2018</field>
|
||||||
|
<field name="condition_select">python</field>
|
||||||
|
<field name="condition_python">result = (payslip.date_to[:4] == '2018')</field>
|
||||||
|
<field name="amount_select">code</field>
|
||||||
|
<field name="amount_python_compute">
|
||||||
|
###
|
||||||
|
ytd = payslip.sum('MO_UNEMP_WAGES_2018', '2018-01-01', '2019-01-01')
|
||||||
|
ytd += contract.external_wages
|
||||||
|
remaining = 12500.0 - ytd
|
||||||
|
if remaining <= 0.0:
|
||||||
|
result = 0
|
||||||
|
elif remaining < categories.GROSS:
|
||||||
|
result = remaining
|
||||||
|
else:
|
||||||
|
result = categories.GROSS
|
||||||
|
</field>
|
||||||
|
<field name="appears_on_payslip" eval="False"/>
|
||||||
|
</record>
|
||||||
|
<record id="hr_payroll_rules_mo_unemp_2018" model="hr.salary.rule">
|
||||||
|
<field name="sequence" eval="443"/>
|
||||||
|
<field name="category_id" ref="hr_payroll_mo_unemp"/>
|
||||||
|
<field name="name">Missouri Unemployment (2018)</field>
|
||||||
|
<field name="code">MO_UNEMP_2018</field>
|
||||||
|
<field name="condition_select">python</field>
|
||||||
|
<field name="condition_python">result = (payslip.date_to[:4] == '2018')</field>
|
||||||
|
<field name="amount_select">code</field>
|
||||||
|
<field name="amount_python_compute">
|
||||||
|
result_rate = -contract.mo_unemp_rate(2018)
|
||||||
|
result = categories.MO_UNEMP_WAGES
|
||||||
|
|
||||||
|
# result_rate of 0 implies 100% due to bug
|
||||||
|
if result_rate == 0.0:
|
||||||
|
result = 0.0
|
||||||
|
</field>
|
||||||
|
<field name="register_id" ref="contrib_register_modor_unemp"/>
|
||||||
|
<field name="appears_on_payslip" eval="False"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="hr_payroll_rules_mo_inc_withhold_2018" model="hr.salary.rule">
|
||||||
|
<field name="sequence" eval="145"/>
|
||||||
|
<field name="category_id" ref="hr_payroll_mo_income_withhold"/>
|
||||||
|
<field name="name">Missouri Income Withholding (2018)</field>
|
||||||
|
<field name="code">MO_INC_WITHHOLD_2018</field>
|
||||||
|
<field name="condition_select">python</field>
|
||||||
|
<field name="condition_python">result = (payslip.date_to[:4] == '2018')</field>
|
||||||
|
<field name="amount_select">code</field>
|
||||||
|
<field name="amount_python_compute">
|
||||||
|
TAX = [
|
||||||
|
(1028.0, 1.5),
|
||||||
|
(1028.0, 2.0),
|
||||||
|
(1028.0, 2.5),
|
||||||
|
(1028.0, 3.0),
|
||||||
|
(1028.0, 3.5),
|
||||||
|
(1028.0, 4.0),
|
||||||
|
(1028.0, 4.5),
|
||||||
|
(1028.0, 5.0),
|
||||||
|
(1028.0, 5.5),
|
||||||
|
(999999999.0, 5.9),
|
||||||
|
]
|
||||||
|
wages = categories.GROSS
|
||||||
|
exemptions = contract.mo_mow4_exemptions
|
||||||
|
filing_status = contract.mo_mow4_filing_status
|
||||||
|
additional = contract.mo_mow4_additional_withholding
|
||||||
|
spouse_employed = contract.mo_mow4_spouse_employed
|
||||||
|
schedule_pay = contract.schedule_pay
|
||||||
|
result = 0.00
|
||||||
|
|
||||||
|
if filing_status:
|
||||||
|
PP = 0
|
||||||
|
if 'weekly' == schedule_pay:
|
||||||
|
PP = 52
|
||||||
|
elif 'bi-weekly' == schedule_pay:
|
||||||
|
PP = 26
|
||||||
|
elif 'semi-monthly' == schedule_pay:
|
||||||
|
PP = 24
|
||||||
|
elif 'monthly' == schedule_pay:
|
||||||
|
PP = 12
|
||||||
|
elif 'quarterly' == schedule_pay:
|
||||||
|
PP = 4
|
||||||
|
elif 'semi-annually' == schedule_pay:
|
||||||
|
PP = 2
|
||||||
|
elif 'annually' == schedule_pay:
|
||||||
|
PP = 1
|
||||||
|
else:
|
||||||
|
raise Exception('Invalid schedule_pay="' + schedule_pay + '" for MO Income Withholding calculation')
|
||||||
|
|
||||||
|
gross_salary = PP * wages
|
||||||
|
deduction = -1
|
||||||
|
if filing_status == 'single' or (filing_status == 'married' and spouse_employed):
|
||||||
|
deduction = 6500.0
|
||||||
|
elif filing_status == 'married' and not spouse_employed:
|
||||||
|
deduction = 13000.0
|
||||||
|
else:
|
||||||
|
deduction = 9550.0
|
||||||
|
|
||||||
|
if deduction < 0:
|
||||||
|
raise UserError('Invalid deduction calculation')
|
||||||
|
|
||||||
|
allowances = 0
|
||||||
|
if filing_status == 'single' or (filing_status == 'married' and spouse_employed):
|
||||||
|
if exemptions == 1:
|
||||||
|
allowances = 2100.0
|
||||||
|
elif exemptions > 1:
|
||||||
|
allowances = 2100.0 + ((exemptions - 1) * 1200.0)
|
||||||
|
elif filing_status == 'married' and not spouse_employed:
|
||||||
|
if exemptions > 0 and exemptions < 3:
|
||||||
|
allowances = 2100.0 * exemptions
|
||||||
|
elif exemptions >= 3:
|
||||||
|
allowances = 2100.0 + 2100.0 + ((exemptions - 2) * 1200.0)
|
||||||
|
else:
|
||||||
|
if exemptions == 1:
|
||||||
|
allowances = 3500.0
|
||||||
|
elif exemptions > 1:
|
||||||
|
allowances = 3500.0 + ((exemptions - 1) * 1200.0)
|
||||||
|
|
||||||
|
federal = categories.FED_INC_WITHHOLD * -1 * PP
|
||||||
|
if filing_status == 'married' and not spouse_employed:
|
||||||
|
federal = min(10000.0, federal)
|
||||||
|
else:
|
||||||
|
federal = min(5000.0, federal)
|
||||||
|
|
||||||
|
mo_taxable_income = gross_salary - deduction - allowances - federal
|
||||||
|
|
||||||
|
remaining_taxable_income = mo_taxable_income
|
||||||
|
tax = 0.0
|
||||||
|
for amt, rate in TAX:
|
||||||
|
rate = rate / 100.0
|
||||||
|
remaining_taxable_income = remaining_taxable_income - amt
|
||||||
|
if remaining_taxable_income > 0.0 or remaining_taxable_income == 0.0:
|
||||||
|
tax += rate * amt
|
||||||
|
else:
|
||||||
|
tax += rate * (remaining_taxable_income + amt)
|
||||||
|
break
|
||||||
|
tax = -tax
|
||||||
|
tax = tax / PP
|
||||||
|
|
||||||
|
# Small GROSS results in an underflow
|
||||||
|
if tax > 0.0:
|
||||||
|
tax = 0.0
|
||||||
|
|
||||||
|
if additional:
|
||||||
|
tax -= additional
|
||||||
|
|
||||||
|
result = round(tax)
|
||||||
|
</field>
|
||||||
|
<field name="register_id" ref="contrib_register_modor_withhold"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</data>
|
||||||
|
</odoo>
|
||||||
34
l10n_us_mo_hr_payroll/hr_payroll.py
Executable file
34
l10n_us_mo_hr_payroll/hr_payroll.py
Executable file
@@ -0,0 +1,34 @@
|
|||||||
|
from odoo import models, fields, api
|
||||||
|
|
||||||
|
|
||||||
|
class USMOHrContract(models.Model):
|
||||||
|
_inherit = 'hr.contract'
|
||||||
|
|
||||||
|
mo_mow4_filing_status = fields.Selection([
|
||||||
|
('', 'Exempt'),
|
||||||
|
('single', 'Single'),
|
||||||
|
('married', 'Married'),
|
||||||
|
('head_of_household', 'Head of Household'),
|
||||||
|
], string='Federal W4 Filing Status', default='single')
|
||||||
|
mo_mow4_spouse_employed = fields.Boolean(string='Missouri W-4 Spouse Employed', default=False)
|
||||||
|
mo_mow4_exemptions = fields.Integer(string='Missouri W-4 Exemptions', default=0)
|
||||||
|
mo_mow4_additional_withholding = fields.Float(string="Missouri W-4 Additional Withholding", default=0.0)
|
||||||
|
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def mo_unemp_rate(self, year):
|
||||||
|
self.ensure_one()
|
||||||
|
if self.futa_type == self.FUTA_TYPE_BASIC:
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
if hasattr(self.employee_id.company_id, 'mo_unemp_rate_' + str(year)):
|
||||||
|
return self.employee_id.company_id['mo_unemp_rate_' + str(year)]
|
||||||
|
|
||||||
|
raise NotImplemented('Year (' + str(year) + ') Not implemented for US Missouri.')
|
||||||
|
|
||||||
|
|
||||||
|
class MOCompany(models.Model):
|
||||||
|
_inherit = 'res.company'
|
||||||
|
|
||||||
|
# Defaults from :: https://labor.mo.gov/DES/Employers/tax_rates#beginning
|
||||||
|
mo_unemp_rate_2018 = fields.Float(string="Missouri Unemployment Rate 2018", default=2.511)
|
||||||
34
l10n_us_mo_hr_payroll/hr_payroll_view.xml
Executable file
34
l10n_us_mo_hr_payroll/hr_payroll_view.xml
Executable file
@@ -0,0 +1,34 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<data>
|
||||||
|
<record id="us_mo_view_company_form" model="ir.ui.view">
|
||||||
|
<field name="name">res.company.form</field>
|
||||||
|
<field name="model">res.company</field>
|
||||||
|
<field name="priority">64</field>
|
||||||
|
<field name="inherit_id" ref="base.view_company_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<data>
|
||||||
|
<xpath expr="//field[@name='currency_id']" position="after">
|
||||||
|
<field name="mo_unemp_rate_2018"/>
|
||||||
|
</xpath>
|
||||||
|
</data>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
<record id="hr_contract_form_l10n_us_mo_inherit" model="ir.ui.view">
|
||||||
|
<field name="name">hr.contract.form.inherit</field>
|
||||||
|
<field name="model">hr.contract</field>
|
||||||
|
<field name="priority">64</field>
|
||||||
|
<field name="inherit_id" ref="hr_contract.hr_contract_view_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<data>
|
||||||
|
<xpath expr="//field[@name='w4_filing_status']" position="before">
|
||||||
|
<field name="mo_mow4_filing_status"/>
|
||||||
|
<field name="mo_mow4_exemptions"/>
|
||||||
|
<field name="mo_mow4_spouse_employed"/>
|
||||||
|
<field name="mo_mow4_additional_withholding"/>
|
||||||
|
</xpath>
|
||||||
|
</data>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
</data>
|
||||||
|
</odoo>
|
||||||
3
l10n_us_mo_hr_payroll/tests/__init__.py
Executable file
3
l10n_us_mo_hr_payroll/tests/__init__.py
Executable file
@@ -0,0 +1,3 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from . import test_us_mo_payslip_2018
|
||||||
224
l10n_us_mo_hr_payroll/tests/test_us_mo_payslip_2018.py
Executable file
224
l10n_us_mo_hr_payroll/tests/test_us_mo_payslip_2018.py
Executable file
@@ -0,0 +1,224 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from odoo.addons.l10n_us_hr_payroll.tests.test_us_payslip import TestUsPayslip, process_payslip
|
||||||
|
from odoo.addons.l10n_us_hr_payroll.l10n_us_hr_payroll import USHrContract
|
||||||
|
|
||||||
|
|
||||||
|
class TestUsMoPayslip(TestUsPayslip):
|
||||||
|
# Calculations from http://dor.mo.gov/forms/4282_2018.pdf
|
||||||
|
SALARY = 5000.0
|
||||||
|
MO_ALLOWANCES = 3 # Different calculated amounts for different filing statuses
|
||||||
|
|
||||||
|
TAX = [
|
||||||
|
(1028.0, 1.5),
|
||||||
|
(1028.0, 2.0),
|
||||||
|
(1028.0, 2.5),
|
||||||
|
(1028.0, 3.0),
|
||||||
|
(1028.0, 3.5),
|
||||||
|
(1028.0, 4.0),
|
||||||
|
(1028.0, 4.5),
|
||||||
|
(1028.0, 5.0),
|
||||||
|
(1028.0, 5.5),
|
||||||
|
(999999999.0, 5.9),
|
||||||
|
]
|
||||||
|
|
||||||
|
def test_2018_taxes_single(self):
|
||||||
|
# Payroll Period Monthly
|
||||||
|
salary = self.SALARY
|
||||||
|
pp = 12.0
|
||||||
|
gross_salary = salary * pp
|
||||||
|
spouse_employed = False
|
||||||
|
|
||||||
|
# Single
|
||||||
|
standard_deduction = 6500.0
|
||||||
|
mo_allowance_calculated = 2100.0 + 1200.0 + 1200.0
|
||||||
|
|
||||||
|
employee = self._createEmployee()
|
||||||
|
employee.company_id.mo_unemp_rate_2018 = 2.511
|
||||||
|
|
||||||
|
contract = self._createContract(employee, salary, struct_id=self.ref('l10n_us_mo_hr_payroll.hr_payroll_salary_structure_us_mo_employee'))
|
||||||
|
contract.mo_mow4_spouse_employed = spouse_employed
|
||||||
|
contract.mo_mow4_filing_status = 'single'
|
||||||
|
contract.mo_mow4_exemptions = 3
|
||||||
|
contract.mo_mow4_additional_withholding = 0.0
|
||||||
|
|
||||||
|
# tax rates
|
||||||
|
mo_unemp = contract.mo_unemp_rate(2018) / -100.0
|
||||||
|
|
||||||
|
self._log('2018 Missouri tax first payslip:')
|
||||||
|
payslip = self._createPayslip(employee, '2018-01-01', '2018-01-31')
|
||||||
|
|
||||||
|
payslip.compute_sheet()
|
||||||
|
|
||||||
|
cats = self._getCategories(payslip)
|
||||||
|
|
||||||
|
self.assertPayrollEqual(cats['MO_UNEMP_WAGES'], salary)
|
||||||
|
self.assertPayrollEqual(cats['MO_UNEMP'], cats['MO_UNEMP_WAGES'] * mo_unemp)
|
||||||
|
|
||||||
|
US_WITHHOLDING = cats['FED_INC_WITHHOLD']
|
||||||
|
# -693.86
|
||||||
|
self._log(US_WITHHOLDING)
|
||||||
|
us_withholding = -US_WITHHOLDING * pp
|
||||||
|
|
||||||
|
# 5000 for single and married with spouse working, 10000 for married spouse not working
|
||||||
|
us_withholding = min(5000, us_withholding)
|
||||||
|
|
||||||
|
|
||||||
|
mo_taxable_income = gross_salary - standard_deduction - mo_allowance_calculated - us_withholding
|
||||||
|
# 48306.14
|
||||||
|
self._log(mo_taxable_income)
|
||||||
|
|
||||||
|
remaining_taxable_income = mo_taxable_income
|
||||||
|
tax = 0.0
|
||||||
|
for amt, rate in self.TAX:
|
||||||
|
rate = rate / 100.0
|
||||||
|
self._log(str(amt) + ' : ' + str(rate) + ' : ' + str(remaining_taxable_income))
|
||||||
|
remaining_taxable_income = remaining_taxable_income - amt
|
||||||
|
if remaining_taxable_income > 0.0 or remaining_taxable_income == 0.0:
|
||||||
|
tax += rate * amt
|
||||||
|
else:
|
||||||
|
tax += rate * (remaining_taxable_income + amt)
|
||||||
|
break
|
||||||
|
tax = -tax
|
||||||
|
self._log('Computed annual tax: ' + str(tax))
|
||||||
|
|
||||||
|
tax = tax / pp
|
||||||
|
tax = round(tax)
|
||||||
|
self._log('Computed period tax: ' + str(tax))
|
||||||
|
self.assertPayrollEqual(cats['MO_INC_WITHHOLD'], tax)
|
||||||
|
|
||||||
|
def test_2018_spouse_not_employed(self):
|
||||||
|
# Payroll Period Semi-monthly
|
||||||
|
salary = self.SALARY
|
||||||
|
pp = 24.0
|
||||||
|
gross_salary = salary * pp
|
||||||
|
spouse_employed = False
|
||||||
|
|
||||||
|
# Single
|
||||||
|
standard_deduction = 13000.0
|
||||||
|
mo_allowance_calculated = 2100.0 + 2100.0 + 1200.0
|
||||||
|
|
||||||
|
employee = self._createEmployee()
|
||||||
|
|
||||||
|
contract = self._createContract(employee, salary,
|
||||||
|
struct_id=self.ref('l10n_us_mo_hr_payroll.hr_payroll_salary_structure_us_mo_employee'),
|
||||||
|
schedule_pay='semi-monthly')
|
||||||
|
contract.mo_mow4_spouse_employed = spouse_employed
|
||||||
|
contract.mo_mow4_filing_status = 'married'
|
||||||
|
contract.mo_mow4_exemptions = 3
|
||||||
|
contract.mo_mow4_additional_withholding = 0.0
|
||||||
|
|
||||||
|
self._log('2018 Missouri tax first payslip:')
|
||||||
|
payslip = self._createPayslip(employee, '2018-01-01', '2018-01-31')
|
||||||
|
|
||||||
|
payslip.compute_sheet()
|
||||||
|
|
||||||
|
cats = self._getCategories(payslip)
|
||||||
|
|
||||||
|
US_WITHHOLDING = cats['FED_INC_WITHHOLD']
|
||||||
|
# -693.86
|
||||||
|
self._log(US_WITHHOLDING)
|
||||||
|
us_withholding = -US_WITHHOLDING * pp
|
||||||
|
|
||||||
|
# 5000 for single and married with spouse working, 10000 for married spouse not working
|
||||||
|
us_withholding = min(10000, us_withholding)
|
||||||
|
|
||||||
|
|
||||||
|
mo_taxable_income = gross_salary - standard_deduction - mo_allowance_calculated - us_withholding
|
||||||
|
# 48306.14
|
||||||
|
self._log(mo_taxable_income)
|
||||||
|
|
||||||
|
remaining_taxable_income = mo_taxable_income
|
||||||
|
tax = 0.0
|
||||||
|
for amt, rate in self.TAX:
|
||||||
|
rate = rate / 100.0
|
||||||
|
self._log(str(amt) + ' : ' + str(rate) + ' : ' + str(remaining_taxable_income))
|
||||||
|
remaining_taxable_income = remaining_taxable_income - amt
|
||||||
|
if remaining_taxable_income > 0.0 or remaining_taxable_income == 0.0:
|
||||||
|
tax += rate * amt
|
||||||
|
else:
|
||||||
|
tax += rate * (remaining_taxable_income + amt)
|
||||||
|
break
|
||||||
|
tax = -tax
|
||||||
|
self._log('Computed annual tax: ' + str(tax))
|
||||||
|
|
||||||
|
tax = tax / pp
|
||||||
|
tax = round(tax)
|
||||||
|
self._log('Computed period tax: ' + str(tax))
|
||||||
|
self.assertPayrollEqual(cats['MO_INC_WITHHOLD'], tax)
|
||||||
|
|
||||||
|
def test_2018_head_of_household(self):
|
||||||
|
# Payroll Period Weekly
|
||||||
|
salary = self.SALARY
|
||||||
|
|
||||||
|
# Payroll Period Weekly
|
||||||
|
salary = self.SALARY
|
||||||
|
pp = 52.0
|
||||||
|
gross_salary = salary * pp
|
||||||
|
spouse_employed = False
|
||||||
|
|
||||||
|
# Single HoH
|
||||||
|
standard_deduction = 9550.0
|
||||||
|
mo_allowance_calculated = 3500.0 + 1200.0 + 1200.0
|
||||||
|
|
||||||
|
employee = self._createEmployee()
|
||||||
|
|
||||||
|
contract = self._createContract(employee, salary,
|
||||||
|
struct_id=self.ref('l10n_us_mo_hr_payroll.hr_payroll_salary_structure_us_mo_employee'),
|
||||||
|
schedule_pay='weekly')
|
||||||
|
contract.mo_mow4_spouse_employed = spouse_employed
|
||||||
|
contract.mo_mow4_filing_status = 'head_of_household'
|
||||||
|
contract.mo_mow4_exemptions = 3
|
||||||
|
contract.mo_mow4_additional_withholding = 0.0
|
||||||
|
|
||||||
|
self._log('2018 Missouri tax first payslip:')
|
||||||
|
payslip = self._createPayslip(employee, '2018-01-01', '2018-01-31')
|
||||||
|
|
||||||
|
payslip.compute_sheet()
|
||||||
|
|
||||||
|
cats = self._getCategories(payslip)
|
||||||
|
|
||||||
|
US_WITHHOLDING = cats['FED_INC_WITHHOLD']
|
||||||
|
self._log(US_WITHHOLDING)
|
||||||
|
us_withholding = -US_WITHHOLDING * pp
|
||||||
|
|
||||||
|
# 5000 for single and married with spouse working, 10000 for married spouse not working
|
||||||
|
us_withholding = min(5000, us_withholding)
|
||||||
|
|
||||||
|
|
||||||
|
mo_taxable_income = gross_salary - standard_deduction - mo_allowance_calculated - us_withholding
|
||||||
|
self._log(mo_taxable_income)
|
||||||
|
|
||||||
|
remaining_taxable_income = mo_taxable_income
|
||||||
|
tax = 0.0
|
||||||
|
for amt, rate in self.TAX:
|
||||||
|
rate = rate / 100.0
|
||||||
|
self._log(str(amt) + ' : ' + str(rate) + ' : ' + str(remaining_taxable_income))
|
||||||
|
remaining_taxable_income = remaining_taxable_income - amt
|
||||||
|
if remaining_taxable_income > 0.0 or remaining_taxable_income == 0.0:
|
||||||
|
tax += rate * amt
|
||||||
|
else:
|
||||||
|
tax += rate * (remaining_taxable_income + amt)
|
||||||
|
break
|
||||||
|
tax = -tax
|
||||||
|
self._log('Computed annual tax: ' + str(tax))
|
||||||
|
|
||||||
|
tax = tax / pp
|
||||||
|
tax = round(tax)
|
||||||
|
self._log('Computed period tax: ' + str(tax))
|
||||||
|
self.assertPayrollEqual(cats['MO_INC_WITHHOLD'], tax)
|
||||||
|
|
||||||
|
def test_2018_underflow(self):
|
||||||
|
# Payroll Period Weekly
|
||||||
|
salary = 200.0
|
||||||
|
|
||||||
|
employee = self._createEmployee()
|
||||||
|
|
||||||
|
contract = self._createContract(employee, salary,
|
||||||
|
struct_id=self.ref('l10n_us_mo_hr_payroll.hr_payroll_salary_structure_us_mo_employee'))
|
||||||
|
|
||||||
|
payslip = self._createPayslip(employee, '2018-01-01', '2018-01-31')
|
||||||
|
payslip.compute_sheet()
|
||||||
|
cats = self._getCategories(payslip)
|
||||||
|
|
||||||
|
self.assertPayrollEqual(cats['MO_INC_WITHHOLD'], 0.0)
|
||||||
3
l10n_us_oh_hr_payroll/__init__.py
Executable file
3
l10n_us_oh_hr_payroll/__init__.py
Executable file
@@ -0,0 +1,3 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from . import hr_payroll
|
||||||
31
l10n_us_oh_hr_payroll/__manifest__.py
Executable file
31
l10n_us_oh_hr_payroll/__manifest__.py
Executable file
@@ -0,0 +1,31 @@
|
|||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
{
|
||||||
|
'name': 'USA - Ohio - Payroll',
|
||||||
|
'author': 'Hibou Corp. <hello@hibou.io>',
|
||||||
|
'license': 'AGPL-3',
|
||||||
|
'category': 'Localization',
|
||||||
|
'depends': ['l10n_us_hr_payroll'],
|
||||||
|
'version': '11.0.2017.0.0',
|
||||||
|
'description': """
|
||||||
|
USA::Ohio Payroll Rules.
|
||||||
|
========================
|
||||||
|
|
||||||
|
* Ohio Department of Revenue partner
|
||||||
|
* Contribution register for Ohio DoR Unemployment
|
||||||
|
* Contribution register for Ohio DoR Income Tax Withholding
|
||||||
|
* Contract level Ohio Withholding Allowance
|
||||||
|
* Company level Ohio Unemployment Rate
|
||||||
|
""",
|
||||||
|
|
||||||
|
'auto_install': False,
|
||||||
|
'website': 'https://hibou.io/',
|
||||||
|
'data':[
|
||||||
|
'hr_payroll_view.xml',
|
||||||
|
'data/base.xml',
|
||||||
|
'data/rules_2016.xml',
|
||||||
|
'data/rules_2017.xml',
|
||||||
|
'data/rules_2018.xml',
|
||||||
|
'data/final.xml',
|
||||||
|
],
|
||||||
|
'installable': True
|
||||||
|
}
|
||||||
46
l10n_us_oh_hr_payroll/data/base.xml
Executable file
46
l10n_us_oh_hr_payroll/data/base.xml
Executable file
@@ -0,0 +1,46 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<data>
|
||||||
|
<!-- CONTRIBUTION REGISTERS -->
|
||||||
|
<record id="res_partner_ohdor_unemp" model="res.partner">
|
||||||
|
<field name="name">Ohio OBG - Unemployment</field>
|
||||||
|
<field name="supplier">1</field>
|
||||||
|
<field eval="0" name="customer"/>
|
||||||
|
</record>
|
||||||
|
<record id="res_partner_ohdor_withhold" model="res.partner">
|
||||||
|
<field name="name">Ohio OBG - Income Withholding</field>
|
||||||
|
<field name="supplier">1</field>
|
||||||
|
<field eval="0" name="customer"/>
|
||||||
|
</record>
|
||||||
|
<record id="contrib_register_ohdor_unemp" model="hr.contribution.register">
|
||||||
|
<field name="name">Ohio Unemployment</field>
|
||||||
|
<field name="note">Ohio Treasurer of State - Unemployment</field>
|
||||||
|
<field name="partner_id" ref="res_partner_ohdor_unemp"/>
|
||||||
|
</record>
|
||||||
|
<record id="contrib_register_ohdor_withhold" model="hr.contribution.register">
|
||||||
|
<field name="name">Ohio Income Tax Withholding</field>
|
||||||
|
<field name="note">Ohio Treasurer of State - Income Tax Withholding</field>
|
||||||
|
<field name="partner_id" ref="res_partner_ohdor_withhold"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- HR SALARY RULE CATEGORIES-->
|
||||||
|
<record id="hr_payroll_oh_unemp_wages" model="hr.salary.rule.category">
|
||||||
|
<field name="name">Ohio Unemployment - Wages</field>
|
||||||
|
<field name="code">OH_UNEMP_WAGES</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="hr_payroll_oh_unemp" model="hr.salary.rule.category">
|
||||||
|
<field name="name">Ohio Unemployment</field>
|
||||||
|
<field name="code">OH_UNEMP</field>
|
||||||
|
<field name="parent_id" ref="hr_payroll.COMP"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="hr_payroll_oh_income_withhold" model="hr.salary.rule.category">
|
||||||
|
<field name="name">Ohio Income Withholding</field>
|
||||||
|
<field name="code">OH_INC_WITHHOLD</field>
|
||||||
|
<field name="parent_id" ref="hr_payroll.DED"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</data>
|
||||||
|
</odoo>
|
||||||
27
l10n_us_oh_hr_payroll/data/final.xml
Executable file
27
l10n_us_oh_hr_payroll/data/final.xml
Executable file
@@ -0,0 +1,27 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<data>
|
||||||
|
|
||||||
|
<!-- HR PAYROLL STRUCTURE -->
|
||||||
|
<record id="hr_payroll_salary_structure_us_oh_employee" model="hr.payroll.structure">
|
||||||
|
<field name="code">US_OH_EMP</field>
|
||||||
|
<field name="name">USA Ohio Employee</field>
|
||||||
|
<field eval="[(6, 0, [
|
||||||
|
ref('hr_payroll_rules_oh_unemp_wages_2016'),
|
||||||
|
ref('hr_payroll_rules_oh_unemp_2016'),
|
||||||
|
ref('hr_payroll_rules_oh_inc_withhold_2016'),
|
||||||
|
|
||||||
|
ref('hr_payroll_rules_oh_unemp_wages_2017'),
|
||||||
|
ref('hr_payroll_rules_oh_unemp_2017'),
|
||||||
|
ref('hr_payroll_rules_oh_inc_withhold_2017'),
|
||||||
|
|
||||||
|
ref('hr_payroll_rules_oh_unemp_wages_2018'),
|
||||||
|
ref('hr_payroll_rules_oh_unemp_2018'),
|
||||||
|
ref('hr_payroll_rules_oh_inc_withhold_2018'),
|
||||||
|
])]" name="rule_ids"/>
|
||||||
|
<field name="company_id" ref="base.main_company"/>
|
||||||
|
<field name="parent_id" ref="l10n_us_hr_payroll.hr_payroll_salary_structure_us_employee"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</data>
|
||||||
|
</odoo>
|
||||||
104
l10n_us_oh_hr_payroll/data/rules_2016.xml
Executable file
104
l10n_us_oh_hr_payroll/data/rules_2016.xml
Executable file
@@ -0,0 +1,104 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<data>
|
||||||
|
|
||||||
|
<!-- HR SALARY RULES-->
|
||||||
|
<record id="hr_payroll_rules_oh_unemp_wages_2016" model="hr.salary.rule">
|
||||||
|
<field name="sequence" eval="423"/>
|
||||||
|
<field name="category_id" ref="hr_payroll_oh_unemp_wages"/>
|
||||||
|
<field name="name">Ohio Unemployment - Wages (2016)</field>
|
||||||
|
<field name="code">OH_UNEMP_WAGES_2016</field>
|
||||||
|
<field name="condition_select">python</field>
|
||||||
|
<field name="condition_python">result = (payslip.date_to[:4] == '2016')</field>
|
||||||
|
<field name="amount_select">code</field>
|
||||||
|
<field name="amount_python_compute">
|
||||||
|
###
|
||||||
|
ytd = payslip.sum('OH_UNEMP_WAGES_2016', '2016-01-01', '2017-01-01')
|
||||||
|
ytd += contract.external_wages
|
||||||
|
remaining = 9000.0 - ytd
|
||||||
|
if remaining <= 0.0:
|
||||||
|
result = 0
|
||||||
|
elif remaining < categories.GROSS:
|
||||||
|
result = remaining
|
||||||
|
else:
|
||||||
|
result = categories.GROSS
|
||||||
|
</field>
|
||||||
|
<field name="appears_on_payslip" eval="False"/>
|
||||||
|
</record>
|
||||||
|
<record id="hr_payroll_rules_oh_unemp_2016" model="hr.salary.rule">
|
||||||
|
<field name="sequence" eval="443"/>
|
||||||
|
<field name="category_id" ref="hr_payroll_oh_unemp"/>
|
||||||
|
<field name="name">Ohio Unemployment (2016)</field>
|
||||||
|
<field name="code">OH_UNEMP_2016</field>
|
||||||
|
<field name="condition_select">python</field>
|
||||||
|
<field name="condition_python">result = (payslip.date_to[:4] == '2016')</field>
|
||||||
|
<field name="amount_select">code</field>
|
||||||
|
<field name="amount_python_compute">
|
||||||
|
result_rate = -contract.oh_unemp_rate(2016)
|
||||||
|
result = categories.OH_UNEMP_WAGES
|
||||||
|
|
||||||
|
# result_rate of 0 implies 100% due to bug
|
||||||
|
if result_rate == 0.0:
|
||||||
|
result = 0.0
|
||||||
|
</field>
|
||||||
|
<field name="register_id" ref="contrib_register_ohdor_unemp"/>
|
||||||
|
<field name="appears_on_payslip" eval="False"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="hr_payroll_rules_oh_inc_withhold_2016" model="hr.salary.rule">
|
||||||
|
<field name="sequence" eval="145"/>
|
||||||
|
<field name="category_id" ref="hr_payroll_oh_income_withhold"/>
|
||||||
|
<field name="name">Ohio Income Withholding</field>
|
||||||
|
<field name="code">OH_INC_WITHHOLD_2016</field>
|
||||||
|
<field name="condition_select">python</field>
|
||||||
|
<field name="condition_python">result = (payslip.date_to[:4] == '2016')</field>
|
||||||
|
<field name="amount_select">code</field>
|
||||||
|
<field name="amount_python_compute">
|
||||||
|
wages = categories.GROSS
|
||||||
|
allowances = contract.oh_income_allowances
|
||||||
|
schedule_pay = contract.schedule_pay
|
||||||
|
val = 0.00
|
||||||
|
|
||||||
|
PP = 0
|
||||||
|
if 'weekly' == schedule_pay:
|
||||||
|
PP = 52
|
||||||
|
elif 'bi-weekly' == schedule_pay:
|
||||||
|
PP = 26
|
||||||
|
elif 'semi-monthly' == schedule_pay: # impossible
|
||||||
|
PP = 24
|
||||||
|
elif 'monthly' == schedule_pay:
|
||||||
|
PP = 12
|
||||||
|
elif 'quarterly' == schedule_pay:
|
||||||
|
PP = 4
|
||||||
|
elif 'semi-annually' == schedule_pay:
|
||||||
|
PP = 2
|
||||||
|
elif 'annually' == schedule_pay:
|
||||||
|
PP = 1
|
||||||
|
else:
|
||||||
|
raise Exception('Invalid schedule_pay="' + schedule_pay + '" for OH Income Withholding calculation')
|
||||||
|
|
||||||
|
# Algorithm from http://www.tax.ohio.gov/Portals/0/employer_withholding/August2015Rates/WTH_OptionalComputerFormula_073015.pdf
|
||||||
|
TW = (wages * PP) - (650 * allowances)
|
||||||
|
if TW <= 5000.0:
|
||||||
|
WD = ((TW * 0.005) / PP) * 1.112
|
||||||
|
elif TW <= 10000.0:
|
||||||
|
WD = ((((TW - 5000.0) * 0.01) + 25.0) / PP) * 1.112
|
||||||
|
elif TW <= 15000.0:
|
||||||
|
WD = ((((TW - 10000.0) * 0.02) + 75.0) / PP) * 1.112
|
||||||
|
elif TW <= 20000.0:
|
||||||
|
WD = ((((TW - 15000.0) * 0.025) + 175.0) / PP) * 1.112
|
||||||
|
elif TW <= 40000.0:
|
||||||
|
WD = ((((TW - 20000.0) * 0.03) + 300.0) / PP) * 1.112
|
||||||
|
elif TW <= 80000.0:
|
||||||
|
WD = ((((TW - 40000.0) * 0.035) + 900.0) / PP) * 1.112
|
||||||
|
elif TW <= 100000.0:
|
||||||
|
WD = ((((TW - 80000.0) * 0.04) + 2300.0) / PP) * 1.112
|
||||||
|
else:
|
||||||
|
WD = ((((TW - 100000.0) * 0.05) + 3100.0) / PP) * 1.112
|
||||||
|
result = -WD
|
||||||
|
</field>
|
||||||
|
<field name="register_id" ref="contrib_register_ohdor_withhold"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</data>
|
||||||
|
</odoo>
|
||||||
104
l10n_us_oh_hr_payroll/data/rules_2017.xml
Executable file
104
l10n_us_oh_hr_payroll/data/rules_2017.xml
Executable file
@@ -0,0 +1,104 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<data>
|
||||||
|
|
||||||
|
<!-- HR SALARY RULES-->
|
||||||
|
<record id="hr_payroll_rules_oh_unemp_wages_2017" model="hr.salary.rule">
|
||||||
|
<field name="sequence" eval="423"/>
|
||||||
|
<field name="category_id" ref="hr_payroll_oh_unemp_wages"/>
|
||||||
|
<field name="name">Ohio Unemployment - Wages (2017)</field>
|
||||||
|
<field name="code">OH_UNEMP_WAGES_2017</field>
|
||||||
|
<field name="condition_select">python</field>
|
||||||
|
<field name="condition_python">result = (payslip.date_to[:4] == '2017')</field>
|
||||||
|
<field name="amount_select">code</field>
|
||||||
|
<field name="amount_python_compute">
|
||||||
|
###
|
||||||
|
ytd = payslip.sum('OH_UNEMP_WAGES_2017', '2017-01-01', '2018-01-01')
|
||||||
|
ytd += contract.external_wages
|
||||||
|
remaining = 9000.0 - ytd
|
||||||
|
if remaining <= 0.0:
|
||||||
|
result = 0
|
||||||
|
elif remaining < categories.GROSS:
|
||||||
|
result = remaining
|
||||||
|
else:
|
||||||
|
result = categories.GROSS
|
||||||
|
</field>
|
||||||
|
<field name="appears_on_payslip" eval="False"/>
|
||||||
|
</record>
|
||||||
|
<record id="hr_payroll_rules_oh_unemp_2017" model="hr.salary.rule">
|
||||||
|
<field name="sequence" eval="443"/>
|
||||||
|
<field name="category_id" ref="hr_payroll_oh_unemp"/>
|
||||||
|
<field name="name">Ohio Unemployment (2017)</field>
|
||||||
|
<field name="code">OH_UNEMP_2017</field>
|
||||||
|
<field name="condition_select">python</field>
|
||||||
|
<field name="condition_python">result = (payslip.date_to[:4] == '2017')</field>
|
||||||
|
<field name="amount_select">code</field>
|
||||||
|
<field name="amount_python_compute">
|
||||||
|
result_rate = -contract.oh_unemp_rate(2017)
|
||||||
|
result = categories.OH_UNEMP_WAGES
|
||||||
|
|
||||||
|
# result_rate of 0 implies 100% due to bug
|
||||||
|
if result_rate == 0.0:
|
||||||
|
result = 0.0
|
||||||
|
</field>
|
||||||
|
<field name="register_id" ref="contrib_register_ohdor_unemp"/>
|
||||||
|
<field name="appears_on_payslip" eval="False"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="hr_payroll_rules_oh_inc_withhold_2017" model="hr.salary.rule">
|
||||||
|
<field name="sequence" eval="145"/>
|
||||||
|
<field name="category_id" ref="hr_payroll_oh_income_withhold"/>
|
||||||
|
<field name="name">Ohio Income Withholding</field>
|
||||||
|
<field name="code">OH_INC_WITHHOLD_2017</field>
|
||||||
|
<field name="condition_select">python</field>
|
||||||
|
<field name="condition_python">result = (payslip.date_to[:4] == '2017')</field>
|
||||||
|
<field name="amount_select">code</field>
|
||||||
|
<field name="amount_python_compute">
|
||||||
|
wages = categories.GROSS
|
||||||
|
allowances = contract.oh_income_allowances
|
||||||
|
schedule_pay = contract.schedule_pay
|
||||||
|
val = 0.00
|
||||||
|
|
||||||
|
PP = 0
|
||||||
|
if 'weekly' == schedule_pay:
|
||||||
|
PP = 52
|
||||||
|
elif 'bi-weekly' == schedule_pay:
|
||||||
|
PP = 26
|
||||||
|
elif 'semi-monthly' == schedule_pay: # impossible
|
||||||
|
PP = 24
|
||||||
|
elif 'monthly' == schedule_pay:
|
||||||
|
PP = 12
|
||||||
|
elif 'quarterly' == schedule_pay:
|
||||||
|
PP = 4
|
||||||
|
elif 'semi-annually' == schedule_pay:
|
||||||
|
PP = 2
|
||||||
|
elif 'annually' == schedule_pay:
|
||||||
|
PP = 1
|
||||||
|
else:
|
||||||
|
raise Exception('Invalid schedule_pay="' + schedule_pay + '" for OH Income Withholding calculation')
|
||||||
|
|
||||||
|
# Algorithm from http://www.tax.ohio.gov/Portals/0/employer_withholding/August2015Rates/WTH_OptionalComputerFormula_073015.pdf
|
||||||
|
TW = (wages * PP) - (650 * allowances)
|
||||||
|
if TW <= 5000.0:
|
||||||
|
WD = ((TW * 0.005) / PP) * 1.112
|
||||||
|
elif TW <= 10000.0:
|
||||||
|
WD = ((((TW - 5000.0) * 0.01) + 25.0) / PP) * 1.112
|
||||||
|
elif TW <= 15000.0:
|
||||||
|
WD = ((((TW - 10000.0) * 0.02) + 75.0) / PP) * 1.112
|
||||||
|
elif TW <= 20000.0:
|
||||||
|
WD = ((((TW - 15000.0) * 0.025) + 175.0) / PP) * 1.112
|
||||||
|
elif TW <= 40000.0:
|
||||||
|
WD = ((((TW - 20000.0) * 0.03) + 300.0) / PP) * 1.112
|
||||||
|
elif TW <= 80000.0:
|
||||||
|
WD = ((((TW - 40000.0) * 0.035) + 900.0) / PP) * 1.112
|
||||||
|
elif TW <= 100000.0:
|
||||||
|
WD = ((((TW - 80000.0) * 0.04) + 2300.0) / PP) * 1.112
|
||||||
|
else:
|
||||||
|
WD = ((((TW - 100000.0) * 0.05) + 3100.0) / PP) * 1.112
|
||||||
|
result = -WD
|
||||||
|
</field>
|
||||||
|
<field name="register_id" ref="contrib_register_ohdor_withhold"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</data>
|
||||||
|
</odoo>
|
||||||
104
l10n_us_oh_hr_payroll/data/rules_2018.xml
Executable file
104
l10n_us_oh_hr_payroll/data/rules_2018.xml
Executable file
@@ -0,0 +1,104 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<data>
|
||||||
|
|
||||||
|
<!-- HR SALARY RULES-->
|
||||||
|
<record id="hr_payroll_rules_oh_unemp_wages_2018" model="hr.salary.rule">
|
||||||
|
<field name="sequence" eval="423"/>
|
||||||
|
<field name="category_id" ref="hr_payroll_oh_unemp_wages"/>
|
||||||
|
<field name="name">Ohio Unemployment - Wages (2018)</field>
|
||||||
|
<field name="code">OH_UNEMP_WAGES_2018</field>
|
||||||
|
<field name="condition_select">python</field>
|
||||||
|
<field name="condition_python">result = (payslip.date_to[:4] == '2018')</field>
|
||||||
|
<field name="amount_select">code</field>
|
||||||
|
<field name="amount_python_compute">
|
||||||
|
###
|
||||||
|
ytd = payslip.sum('OH_UNEMP_WAGES_2018', '2018-01-01', '2019-01-01')
|
||||||
|
ytd += contract.external_wages
|
||||||
|
remaining = 9500.0 - ytd
|
||||||
|
if remaining <= 0.0:
|
||||||
|
result = 0
|
||||||
|
elif remaining < categories.GROSS:
|
||||||
|
result = remaining
|
||||||
|
else:
|
||||||
|
result = categories.GROSS
|
||||||
|
</field>
|
||||||
|
<field name="appears_on_payslip" eval="False"/>
|
||||||
|
</record>
|
||||||
|
<record id="hr_payroll_rules_oh_unemp_2018" model="hr.salary.rule">
|
||||||
|
<field name="sequence" eval="443"/>
|
||||||
|
<field name="category_id" ref="hr_payroll_oh_unemp"/>
|
||||||
|
<field name="name">Ohio Unemployment (2018)</field>
|
||||||
|
<field name="code">OH_UNEMP_2018</field>
|
||||||
|
<field name="condition_select">python</field>
|
||||||
|
<field name="condition_python">result = (payslip.date_to[:4] == '2018')</field>
|
||||||
|
<field name="amount_select">code</field>
|
||||||
|
<field name="amount_python_compute">
|
||||||
|
result_rate = -contract.oh_unemp_rate(2018)
|
||||||
|
result = categories.OH_UNEMP_WAGES
|
||||||
|
|
||||||
|
# result_rate of 0 implies 100% due to bug
|
||||||
|
if result_rate == 0.0:
|
||||||
|
result = 0.0
|
||||||
|
</field>
|
||||||
|
<field name="register_id" ref="contrib_register_ohdor_unemp"/>
|
||||||
|
<field name="appears_on_payslip" eval="False"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="hr_payroll_rules_oh_inc_withhold_2018" model="hr.salary.rule">
|
||||||
|
<field name="sequence" eval="145"/>
|
||||||
|
<field name="category_id" ref="hr_payroll_oh_income_withhold"/>
|
||||||
|
<field name="name">Ohio Income Withholding</field>
|
||||||
|
<field name="code">OH_INC_WITHHOLD_2018</field>
|
||||||
|
<field name="condition_select">python</field>
|
||||||
|
<field name="condition_python">result = (payslip.date_to[:4] == '2018')</field>
|
||||||
|
<field name="amount_select">code</field>
|
||||||
|
<field name="amount_python_compute">
|
||||||
|
wages = categories.GROSS
|
||||||
|
allowances = contract.oh_income_allowances
|
||||||
|
schedule_pay = contract.schedule_pay
|
||||||
|
val = 0.00
|
||||||
|
|
||||||
|
PP = 0
|
||||||
|
if 'weekly' == schedule_pay:
|
||||||
|
PP = 52
|
||||||
|
elif 'bi-weekly' == schedule_pay:
|
||||||
|
PP = 26
|
||||||
|
elif 'semi-monthly' == schedule_pay: # impossible
|
||||||
|
PP = 24
|
||||||
|
elif 'monthly' == schedule_pay:
|
||||||
|
PP = 12
|
||||||
|
elif 'quarterly' == schedule_pay:
|
||||||
|
PP = 4
|
||||||
|
elif 'semi-annually' == schedule_pay:
|
||||||
|
PP = 2
|
||||||
|
elif 'annually' == schedule_pay:
|
||||||
|
PP = 1
|
||||||
|
else:
|
||||||
|
raise Exception('Invalid schedule_pay="' + schedule_pay + '" for OH Income Withholding calculation')
|
||||||
|
|
||||||
|
# Algorithm from http://www.tax.ohio.gov/Portals/0/employer_withholding/August2015Rates/WTH_OptionalComputerFormula_073015.pdf
|
||||||
|
TW = (wages * PP) - (650 * allowances)
|
||||||
|
if TW <= 5000.0:
|
||||||
|
WD = ((TW * 0.005) / PP) * 1.112
|
||||||
|
elif TW <= 10000.0:
|
||||||
|
WD = ((((TW - 5000.0) * 0.01) + 25.0) / PP) * 1.112
|
||||||
|
elif TW <= 15000.0:
|
||||||
|
WD = ((((TW - 10000.0) * 0.02) + 75.0) / PP) * 1.112
|
||||||
|
elif TW <= 20000.0:
|
||||||
|
WD = ((((TW - 15000.0) * 0.025) + 175.0) / PP) * 1.112
|
||||||
|
elif TW <= 40000.0:
|
||||||
|
WD = ((((TW - 20000.0) * 0.03) + 300.0) / PP) * 1.112
|
||||||
|
elif TW <= 80000.0:
|
||||||
|
WD = ((((TW - 40000.0) * 0.035) + 900.0) / PP) * 1.112
|
||||||
|
elif TW <= 100000.0:
|
||||||
|
WD = ((((TW - 80000.0) * 0.04) + 2300.0) / PP) * 1.112
|
||||||
|
else:
|
||||||
|
WD = ((((TW - 100000.0) * 0.05) + 3100.0) / PP) * 1.112
|
||||||
|
result = -WD
|
||||||
|
</field>
|
||||||
|
<field name="register_id" ref="contrib_register_ohdor_withhold"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</data>
|
||||||
|
</odoo>
|
||||||
27
l10n_us_oh_hr_payroll/hr_payroll.py
Executable file
27
l10n_us_oh_hr_payroll/hr_payroll.py
Executable file
@@ -0,0 +1,27 @@
|
|||||||
|
from odoo import models, fields, api
|
||||||
|
|
||||||
|
|
||||||
|
class USOHHrContract(models.Model):
|
||||||
|
_inherit = 'hr.contract'
|
||||||
|
|
||||||
|
oh_income_allowances = fields.Integer(string='Ohio Income Allowances', default=0)
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def oh_unemp_rate(self, year):
|
||||||
|
self.ensure_one()
|
||||||
|
if self.futa_type == self.FUTA_TYPE_BASIC:
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
if hasattr(self.employee_id.company_id, 'oh_unemp_rate_' + str(year)):
|
||||||
|
return self.employee_id.company_id['oh_unemp_rate_' + str(year)]
|
||||||
|
|
||||||
|
raise NotImplemented('Year (' + str(year) + ') Not implemented for US Ohio.')
|
||||||
|
|
||||||
|
|
||||||
|
class OHCompany(models.Model):
|
||||||
|
_inherit = 'res.company'
|
||||||
|
|
||||||
|
# Defaults from :: https://jfs.ohio.gov/ouc/uctax/rates.stm
|
||||||
|
oh_unemp_rate_2016 = fields.Float(string="Ohio Unemployment Rate 2016", default=2.7)
|
||||||
|
oh_unemp_rate_2017 = fields.Float(string="Ohio Unemployment Rate 2017", default=2.7)
|
||||||
|
oh_unemp_rate_2018 = fields.Float(string="Ohio Unemployment Rate 2018", default=2.7)
|
||||||
33
l10n_us_oh_hr_payroll/hr_payroll_view.xml
Executable file
33
l10n_us_oh_hr_payroll/hr_payroll_view.xml
Executable file
@@ -0,0 +1,33 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<data>
|
||||||
|
<record id="us_fl_view_company_form" model="ir.ui.view">
|
||||||
|
<field name="name">res.company.form</field>
|
||||||
|
<field name="model">res.company</field>
|
||||||
|
<field name="priority">64</field>
|
||||||
|
<field name="inherit_id" ref="base.view_company_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<data>
|
||||||
|
<xpath expr="//field[@name='currency_id']" position="after">
|
||||||
|
<field name="oh_unemp_rate_2016"/>
|
||||||
|
<field name="oh_unemp_rate_2017"/>
|
||||||
|
<field name="oh_unemp_rate_2018"/>
|
||||||
|
</xpath>
|
||||||
|
</data>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
<record id="hr_contract_form_l10n_us_oh_inherit" model="ir.ui.view">
|
||||||
|
<field name="name">hr.contract.form.inherit</field>
|
||||||
|
<field name="model">hr.contract</field>
|
||||||
|
<field name="priority">64</field>
|
||||||
|
<field name="inherit_id" ref="hr_contract.hr_contract_view_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<data>
|
||||||
|
<xpath expr="//field[@name='w4_filing_status']" position="before">
|
||||||
|
<field name="oh_income_allowances"/>
|
||||||
|
</xpath>
|
||||||
|
</data>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
</data>
|
||||||
|
</odoo>
|
||||||
5
l10n_us_oh_hr_payroll/tests/__init__.py
Executable file
5
l10n_us_oh_hr_payroll/tests/__init__.py
Executable file
@@ -0,0 +1,5 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from . import test_us_oh_payslip_2016
|
||||||
|
from . import test_us_oh_payslip_2017
|
||||||
|
from . import test_us_oh_payslip_2018
|
||||||
111
l10n_us_oh_hr_payroll/tests/test_us_oh_payslip_2016.py
Executable file
111
l10n_us_oh_hr_payroll/tests/test_us_oh_payslip_2016.py
Executable file
@@ -0,0 +1,111 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from odoo.addons.l10n_us_hr_payroll.tests.test_us_payslip import TestUsPayslip, process_payslip
|
||||||
|
from odoo.addons.l10n_us_hr_payroll.l10n_us_hr_payroll import USHrContract
|
||||||
|
|
||||||
|
|
||||||
|
class TestUsOhPayslip(TestUsPayslip):
|
||||||
|
###
|
||||||
|
# 2016 Taxes and Rates
|
||||||
|
###
|
||||||
|
|
||||||
|
def test_2016_taxes(self):
|
||||||
|
salary = 5000.0
|
||||||
|
|
||||||
|
## tax maximums
|
||||||
|
OH_UNEMP_MAX_WAGE = 9000.0
|
||||||
|
|
||||||
|
## For formula here
|
||||||
|
# http://www.tax.ohio.gov/Portals/0/employer_withholding/August2015Rates/WTH_OptionalComputerFormula_073015.pdf
|
||||||
|
TW = salary * 12 # = 60000
|
||||||
|
WD = ((TW - 40000) * 0.035 + 900) / 12 * 1.112
|
||||||
|
|
||||||
|
|
||||||
|
employee = self._createEmployee()
|
||||||
|
employee.company_id.oh_unemp_rate_2016 = 2.7
|
||||||
|
|
||||||
|
contract = self._createContract(employee, salary, struct_id=self.ref('l10n_us_oh_hr_payroll.hr_payroll_salary_structure_us_oh_employee'))
|
||||||
|
|
||||||
|
## tax rates
|
||||||
|
OH_UNEMP = contract.oh_unemp_rate(2016) / -100.0
|
||||||
|
|
||||||
|
self._log('2016 Ohio tax first payslip:')
|
||||||
|
payslip = self._createPayslip(employee, '2016-01-01', '2016-01-31')
|
||||||
|
|
||||||
|
payslip.compute_sheet()
|
||||||
|
|
||||||
|
cats = self._getCategories(payslip)
|
||||||
|
|
||||||
|
self.assertPayrollEqual(cats['OH_UNEMP_WAGES'], salary)
|
||||||
|
self.assertPayrollEqual(cats['OH_UNEMP'], cats['OH_UNEMP_WAGES'] * OH_UNEMP)
|
||||||
|
self.assertPayrollEqual(cats['OH_INC_WITHHOLD'], -WD)
|
||||||
|
|
||||||
|
process_payslip(payslip)
|
||||||
|
|
||||||
|
# Make a new payslip, this one will have maximums
|
||||||
|
|
||||||
|
remaining_oh_unemp_wages = OH_UNEMP_MAX_WAGE - salary if (OH_UNEMP_MAX_WAGE - 2*salary < salary) else salary
|
||||||
|
|
||||||
|
self._log('2016 Ohio tax second payslip:')
|
||||||
|
payslip = self._createPayslip(employee, '2016-02-01', '2016-02-29') # 2016 is a leap year
|
||||||
|
|
||||||
|
payslip.compute_sheet()
|
||||||
|
|
||||||
|
cats = self._getCategories(payslip)
|
||||||
|
|
||||||
|
self.assertPayrollEqual(cats['OH_UNEMP_WAGES'], remaining_oh_unemp_wages)
|
||||||
|
self.assertPayrollEqual(cats['OH_UNEMP'], remaining_oh_unemp_wages * OH_UNEMP)
|
||||||
|
|
||||||
|
|
||||||
|
def test_2016_taxes_with_external(self):
|
||||||
|
salary = 5000.0
|
||||||
|
external_wages = 6000.0
|
||||||
|
|
||||||
|
employee = self._createEmployee()
|
||||||
|
employee.company_id.oh_unemp_rate_2016 = 2.8
|
||||||
|
|
||||||
|
contract = self._createContract(employee, salary, external_wages=external_wages,
|
||||||
|
struct_id=self.ref('l10n_us_oh_hr_payroll.hr_payroll_salary_structure_us_oh_employee'))
|
||||||
|
## tax maximums
|
||||||
|
OH_UNEMP_MAX_WAGE = 9000.0
|
||||||
|
|
||||||
|
## tax rates
|
||||||
|
OH_UNEMP = contract.oh_unemp_rate(2016) / -100.0
|
||||||
|
|
||||||
|
self._log('2016 Ohio_external tax first payslip:')
|
||||||
|
payslip = self._createPayslip(employee, '2016-01-01', '2016-01-31')
|
||||||
|
|
||||||
|
payslip.compute_sheet()
|
||||||
|
|
||||||
|
cats = self._getCategories(payslip)
|
||||||
|
|
||||||
|
self.assertPayrollEqual(cats['OH_UNEMP_WAGES'], OH_UNEMP_MAX_WAGE - external_wages)
|
||||||
|
self.assertPayrollEqual(cats['OH_UNEMP'], cats['OH_UNEMP_WAGES'] * OH_UNEMP)
|
||||||
|
|
||||||
|
|
||||||
|
def test_2016_taxes_with_state_exempt(self):
|
||||||
|
salary = 5000.0
|
||||||
|
external_wages = 6000.0
|
||||||
|
|
||||||
|
employee = self._createEmployee()
|
||||||
|
employee.company_id.oh_unemp_rate_2016 = 2.9
|
||||||
|
|
||||||
|
contract = self._createContract(employee, salary, external_wages=external_wages, struct_id=self.ref(
|
||||||
|
'l10n_us_oh_hr_payroll.hr_payroll_salary_structure_us_oh_employee'), futa_type=USHrContract.FUTA_TYPE_BASIC)
|
||||||
|
## tax maximums
|
||||||
|
OH_UNEMP_MAX_WAGE = 9000.0
|
||||||
|
|
||||||
|
## tax rates
|
||||||
|
OH_UNEMP = contract.oh_unemp_rate(2016) / -100.0
|
||||||
|
|
||||||
|
self.assertPayrollEqual(OH_UNEMP, 0.0)
|
||||||
|
|
||||||
|
self._log('2016 Ohio exempt tax first payslip:')
|
||||||
|
payslip = self._createPayslip(employee, '2016-01-01', '2016-01-31')
|
||||||
|
|
||||||
|
payslip.compute_sheet()
|
||||||
|
|
||||||
|
cats = self._getCategories(payslip)
|
||||||
|
|
||||||
|
self.assertPayrollEqual(cats['OH_UNEMP_WAGES'], OH_UNEMP_MAX_WAGE - external_wages)
|
||||||
|
self.assertPayrollEqual(cats['OH_UNEMP'], cats['OH_UNEMP_WAGES'] * OH_UNEMP)
|
||||||
108
l10n_us_oh_hr_payroll/tests/test_us_oh_payslip_2017.py
Executable file
108
l10n_us_oh_hr_payroll/tests/test_us_oh_payslip_2017.py
Executable file
@@ -0,0 +1,108 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from odoo.addons.l10n_us_hr_payroll.tests.test_us_payslip import TestUsPayslip, process_payslip
|
||||||
|
from odoo.addons.l10n_us_hr_payroll.l10n_us_hr_payroll import USHrContract
|
||||||
|
|
||||||
|
|
||||||
|
class TestUsOhPayslip(TestUsPayslip):
|
||||||
|
###
|
||||||
|
# Taxes and Rates
|
||||||
|
###
|
||||||
|
OH_UNEMP_MAX_WAGE = 9000.0
|
||||||
|
|
||||||
|
def test_2017_taxes(self):
|
||||||
|
salary = 5000.0
|
||||||
|
|
||||||
|
# For formula here
|
||||||
|
# http://www.tax.ohio.gov/Portals/0/employer_withholding/August2015Rates/WTH_OptionalComputerFormula_073015.pdf
|
||||||
|
tw = salary * 12 # = 60000
|
||||||
|
wd = ((tw - 40000) * 0.035 + 900) / 12 * 1.112
|
||||||
|
|
||||||
|
employee = self._createEmployee()
|
||||||
|
employee.company_id.oh_unemp_rate_2017 = 2.7
|
||||||
|
|
||||||
|
contract = self._createContract(employee, salary, struct_id=self.ref('l10n_us_oh_hr_payroll.hr_payroll_salary_structure_us_oh_employee'))
|
||||||
|
|
||||||
|
# tax rates
|
||||||
|
oh_unemp = contract.oh_unemp_rate(2017) / -100.0
|
||||||
|
|
||||||
|
self._log('2016 Ohio tax last payslip:')
|
||||||
|
payslip = self._createPayslip(employee, '2016-12-01', '2016-12-31')
|
||||||
|
payslip.compute_sheet()
|
||||||
|
process_payslip(payslip)
|
||||||
|
|
||||||
|
self._log('2017 Ohio tax first payslip:')
|
||||||
|
payslip = self._createPayslip(employee, '2017-01-01', '2017-01-31')
|
||||||
|
|
||||||
|
payslip.compute_sheet()
|
||||||
|
|
||||||
|
cats = self._getCategories(payslip)
|
||||||
|
|
||||||
|
self.assertPayrollEqual(cats['OH_UNEMP_WAGES'], salary)
|
||||||
|
self.assertPayrollEqual(cats['OH_UNEMP'], cats['OH_UNEMP_WAGES'] * oh_unemp)
|
||||||
|
self.assertPayrollEqual(cats['OH_INC_WITHHOLD'], -wd)
|
||||||
|
|
||||||
|
process_payslip(payslip)
|
||||||
|
|
||||||
|
# Make a new payslip, this one will have maximums
|
||||||
|
|
||||||
|
remaining_oh_unemp_wages = self.OH_UNEMP_MAX_WAGE - salary if (self.OH_UNEMP_MAX_WAGE - 2*salary < salary) \
|
||||||
|
else salary
|
||||||
|
|
||||||
|
self._log('2017 Ohio tax second payslip:')
|
||||||
|
payslip = self._createPayslip(employee, '2017-02-01', '2017-02-28')
|
||||||
|
|
||||||
|
payslip.compute_sheet()
|
||||||
|
|
||||||
|
cats = self._getCategories(payslip)
|
||||||
|
|
||||||
|
self.assertPayrollEqual(cats['OH_UNEMP_WAGES'], remaining_oh_unemp_wages)
|
||||||
|
self.assertPayrollEqual(cats['OH_UNEMP'], remaining_oh_unemp_wages * oh_unemp)
|
||||||
|
|
||||||
|
def test_2017_taxes_with_external(self):
|
||||||
|
salary = 5000.0
|
||||||
|
external_wages = 6000.0
|
||||||
|
|
||||||
|
employee = self._createEmployee()
|
||||||
|
employee.company_id.oh_unemp_rate_2017 = 2.8
|
||||||
|
|
||||||
|
contract = self._createContract(employee, salary, external_wages=external_wages,
|
||||||
|
struct_id=self.ref('l10n_us_oh_hr_payroll.hr_payroll_salary_structure_us_oh_employee'))
|
||||||
|
|
||||||
|
# tax rates
|
||||||
|
oh_unemp = contract.oh_unemp_rate(2017) / -100.0
|
||||||
|
|
||||||
|
self._log('2017 Ohio_external tax first payslip:')
|
||||||
|
payslip = self._createPayslip(employee, '2017-01-01', '2017-01-31')
|
||||||
|
|
||||||
|
payslip.compute_sheet()
|
||||||
|
|
||||||
|
cats = self._getCategories(payslip)
|
||||||
|
|
||||||
|
self.assertPayrollEqual(cats['OH_UNEMP_WAGES'], self.OH_UNEMP_MAX_WAGE - external_wages)
|
||||||
|
self.assertPayrollEqual(cats['OH_UNEMP'], cats['OH_UNEMP_WAGES'] * oh_unemp)
|
||||||
|
|
||||||
|
def test_2017_taxes_with_state_exempt(self):
|
||||||
|
salary = 5000.0
|
||||||
|
external_wages = 6000.0
|
||||||
|
|
||||||
|
employee = self._createEmployee()
|
||||||
|
employee.company_id.oh_unemp_rate_2017 = 2.9
|
||||||
|
|
||||||
|
contract = self._createContract(employee, salary, external_wages=external_wages, struct_id=self.ref(
|
||||||
|
'l10n_us_oh_hr_payroll.hr_payroll_salary_structure_us_oh_employee'), futa_type=USHrContract.FUTA_TYPE_BASIC)
|
||||||
|
|
||||||
|
# tax rates
|
||||||
|
oh_unemp = contract.oh_unemp_rate(2017) / -100.0
|
||||||
|
|
||||||
|
self.assertPayrollEqual(oh_unemp, 0.0)
|
||||||
|
|
||||||
|
self._log('2017 Ohio exempt tax first payslip:')
|
||||||
|
payslip = self._createPayslip(employee, '2017-01-01', '2017-01-31')
|
||||||
|
|
||||||
|
payslip.compute_sheet()
|
||||||
|
|
||||||
|
cats = self._getCategories(payslip)
|
||||||
|
|
||||||
|
self.assertPayrollEqual(cats['OH_UNEMP_WAGES'], self.OH_UNEMP_MAX_WAGE - external_wages)
|
||||||
|
self.assertPayrollEqual(cats['OH_UNEMP'], cats['OH_UNEMP_WAGES'] * oh_unemp)
|
||||||
108
l10n_us_oh_hr_payroll/tests/test_us_oh_payslip_2018.py
Executable file
108
l10n_us_oh_hr_payroll/tests/test_us_oh_payslip_2018.py
Executable file
@@ -0,0 +1,108 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from odoo.addons.l10n_us_hr_payroll.tests.test_us_payslip import TestUsPayslip, process_payslip
|
||||||
|
from odoo.addons.l10n_us_hr_payroll.l10n_us_hr_payroll import USHrContract
|
||||||
|
|
||||||
|
|
||||||
|
class TestUsOhPayslip(TestUsPayslip):
|
||||||
|
###
|
||||||
|
# Taxes and Rates
|
||||||
|
###
|
||||||
|
OH_UNEMP_MAX_WAGE = 9500.0
|
||||||
|
|
||||||
|
def test_2018_taxes(self):
|
||||||
|
salary = 5000.0
|
||||||
|
|
||||||
|
# For formula here
|
||||||
|
# http://www.tax.ohio.gov/Portals/0/employer_withholding/August2015Rates/WTH_OptionalComputerFormula_073015.pdf
|
||||||
|
tw = salary * 12 # = 60000
|
||||||
|
wd = ((tw - 40000) * 0.035 + 900) / 12 * 1.112
|
||||||
|
|
||||||
|
employee = self._createEmployee()
|
||||||
|
employee.company_id.oh_unemp_rate_2018 = 2.7
|
||||||
|
|
||||||
|
contract = self._createContract(employee, salary, struct_id=self.ref('l10n_us_oh_hr_payroll.hr_payroll_salary_structure_us_oh_employee'))
|
||||||
|
|
||||||
|
# tax rates
|
||||||
|
oh_unemp = contract.oh_unemp_rate(2018) / -100.0
|
||||||
|
|
||||||
|
self._log('2016 Ohio tax last payslip:')
|
||||||
|
payslip = self._createPayslip(employee, '2016-12-01', '2016-12-31')
|
||||||
|
payslip.compute_sheet()
|
||||||
|
process_payslip(payslip)
|
||||||
|
|
||||||
|
self._log('2018 Ohio tax first payslip:')
|
||||||
|
payslip = self._createPayslip(employee, '2018-01-01', '2018-01-31')
|
||||||
|
|
||||||
|
payslip.compute_sheet()
|
||||||
|
|
||||||
|
cats = self._getCategories(payslip)
|
||||||
|
|
||||||
|
self.assertPayrollEqual(cats['OH_UNEMP_WAGES'], salary)
|
||||||
|
self.assertPayrollEqual(cats['OH_UNEMP'], cats['OH_UNEMP_WAGES'] * oh_unemp)
|
||||||
|
self.assertPayrollEqual(cats['OH_INC_WITHHOLD'], -wd)
|
||||||
|
|
||||||
|
process_payslip(payslip)
|
||||||
|
|
||||||
|
# Make a new payslip, this one will have maximums
|
||||||
|
|
||||||
|
remaining_oh_unemp_wages = self.OH_UNEMP_MAX_WAGE - salary if (self.OH_UNEMP_MAX_WAGE - 2*salary < salary) \
|
||||||
|
else salary
|
||||||
|
|
||||||
|
self._log('2018 Ohio tax second payslip:')
|
||||||
|
payslip = self._createPayslip(employee, '2018-02-01', '2018-02-28')
|
||||||
|
|
||||||
|
payslip.compute_sheet()
|
||||||
|
|
||||||
|
cats = self._getCategories(payslip)
|
||||||
|
|
||||||
|
self.assertPayrollEqual(cats['OH_UNEMP_WAGES'], remaining_oh_unemp_wages)
|
||||||
|
self.assertPayrollEqual(cats['OH_UNEMP'], remaining_oh_unemp_wages * oh_unemp)
|
||||||
|
|
||||||
|
def test_2018_taxes_with_external(self):
|
||||||
|
salary = 5000.0
|
||||||
|
external_wages = 6000.0
|
||||||
|
|
||||||
|
employee = self._createEmployee()
|
||||||
|
employee.company_id.oh_unemp_rate_2018 = 2.8
|
||||||
|
|
||||||
|
contract = self._createContract(employee, salary, external_wages=external_wages,
|
||||||
|
struct_id=self.ref('l10n_us_oh_hr_payroll.hr_payroll_salary_structure_us_oh_employee'))
|
||||||
|
|
||||||
|
# tax rates
|
||||||
|
oh_unemp = contract.oh_unemp_rate(2018) / -100.0
|
||||||
|
|
||||||
|
self._log('2018 Ohio_external tax first payslip:')
|
||||||
|
payslip = self._createPayslip(employee, '2018-01-01', '2018-01-31')
|
||||||
|
|
||||||
|
payslip.compute_sheet()
|
||||||
|
|
||||||
|
cats = self._getCategories(payslip)
|
||||||
|
|
||||||
|
self.assertPayrollEqual(cats['OH_UNEMP_WAGES'], self.OH_UNEMP_MAX_WAGE - external_wages)
|
||||||
|
self.assertPayrollEqual(cats['OH_UNEMP'], cats['OH_UNEMP_WAGES'] * oh_unemp)
|
||||||
|
|
||||||
|
def test_2018_taxes_with_state_exempt(self):
|
||||||
|
salary = 5000.0
|
||||||
|
external_wages = 6000.0
|
||||||
|
|
||||||
|
employee = self._createEmployee()
|
||||||
|
employee.company_id.oh_unemp_rate_2018 = 2.9
|
||||||
|
|
||||||
|
contract = self._createContract(employee, salary, external_wages=external_wages, struct_id=self.ref(
|
||||||
|
'l10n_us_oh_hr_payroll.hr_payroll_salary_structure_us_oh_employee'), futa_type=USHrContract.FUTA_TYPE_BASIC)
|
||||||
|
|
||||||
|
# tax rates
|
||||||
|
oh_unemp = contract.oh_unemp_rate(2018) / -100.0
|
||||||
|
|
||||||
|
self.assertPayrollEqual(oh_unemp, 0.0)
|
||||||
|
|
||||||
|
self._log('2018 Ohio exempt tax first payslip:')
|
||||||
|
payslip = self._createPayslip(employee, '2018-01-01', '2018-01-31')
|
||||||
|
|
||||||
|
payslip.compute_sheet()
|
||||||
|
|
||||||
|
cats = self._getCategories(payslip)
|
||||||
|
|
||||||
|
self.assertPayrollEqual(cats['OH_UNEMP_WAGES'], self.OH_UNEMP_MAX_WAGE - external_wages)
|
||||||
|
self.assertPayrollEqual(cats['OH_UNEMP'], cats['OH_UNEMP_WAGES'] * oh_unemp)
|
||||||
3
l10n_us_va_hr_payroll/__init__.py
Executable file
3
l10n_us_va_hr_payroll/__init__.py
Executable file
@@ -0,0 +1,3 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from . import hr_payroll
|
||||||
28
l10n_us_va_hr_payroll/__manifest__.py
Executable file
28
l10n_us_va_hr_payroll/__manifest__.py
Executable file
@@ -0,0 +1,28 @@
|
|||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
{
|
||||||
|
'name': 'USA - Virginia - Payroll',
|
||||||
|
'author': 'Hibou Corp. <hello@hibou.io>',
|
||||||
|
'license': 'AGPL-3',
|
||||||
|
'category': 'Localization',
|
||||||
|
'depends': ['l10n_us_hr_payroll'],
|
||||||
|
'version': '11.0.2018.0.0',
|
||||||
|
'description': """
|
||||||
|
USA::Virginia Payroll Rules.
|
||||||
|
============================
|
||||||
|
|
||||||
|
* Contribution register and partner for Virginia Department of Taxaction - Unemployment
|
||||||
|
* Contribution register and partner for Virginia Department of Taxaction - Income Tax Withholding
|
||||||
|
* Contract level Virginia Exemptions
|
||||||
|
* Company level Virginia Unemployment Rate
|
||||||
|
""",
|
||||||
|
|
||||||
|
'auto_install': False,
|
||||||
|
'website': 'https://hibou.io/',
|
||||||
|
'data': [
|
||||||
|
'hr_payroll_view.xml',
|
||||||
|
'data/base.xml',
|
||||||
|
'data/rules_2018.xml',
|
||||||
|
'data/final.xml',
|
||||||
|
],
|
||||||
|
'installable': True
|
||||||
|
}
|
||||||
46
l10n_us_va_hr_payroll/data/base.xml
Executable file
46
l10n_us_va_hr_payroll/data/base.xml
Executable file
@@ -0,0 +1,46 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<data>
|
||||||
|
<!-- CONTRIBUTION REGISTERS -->
|
||||||
|
<record id="res_partner_vador_unemp" model="res.partner">
|
||||||
|
<field name="name">Virginia Department of Taxation - Unemployment Tax</field>
|
||||||
|
<field name="supplier">1</field>
|
||||||
|
<field eval="0" name="customer"/>
|
||||||
|
</record>
|
||||||
|
<record id="res_partner_vador_withhold" model="res.partner">
|
||||||
|
<field name="name">Virginia Department of Taxation - Income Tax Withholding</field>
|
||||||
|
<field name="supplier">1</field>
|
||||||
|
<field eval="0" name="customer"/>
|
||||||
|
</record>
|
||||||
|
<record id="contrib_register_vador_unemp" model="hr.contribution.register">
|
||||||
|
<field name="name">Virginia Unemployment</field>
|
||||||
|
<field name="note">Virginia Department of Taxation - Unemployment</field>
|
||||||
|
<field name="partner_id" ref="res_partner_vador_unemp"/>
|
||||||
|
</record>
|
||||||
|
<record id="contrib_register_vador_withhold" model="hr.contribution.register">
|
||||||
|
<field name="name">Virginia Income Tax Withholding</field>
|
||||||
|
<field name="note">Virginia Department of Taxation - Income Tax Withholding</field>
|
||||||
|
<field name="partner_id" ref="res_partner_vador_withhold"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- HR SALARY RULE CATEGORIES-->
|
||||||
|
<record id="hr_payroll_va_unemp_wages" model="hr.salary.rule.category">
|
||||||
|
<field name="name">Virginia Unemployment - Wages</field>
|
||||||
|
<field name="code">VA_UNEMP_WAGES</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="hr_payroll_va_unemp" model="hr.salary.rule.category">
|
||||||
|
<field name="name">Virginia Unemployment</field>
|
||||||
|
<field name="code">VA_UNEMP</field>
|
||||||
|
<field name="parent_id" ref="hr_payroll.COMP"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="hr_payroll_va_income_withhold" model="hr.salary.rule.category">
|
||||||
|
<field name="name">Virginia Income Withholding</field>
|
||||||
|
<field name="code">VA_INC_WITHHOLD</field>
|
||||||
|
<field name="parent_id" ref="hr_payroll.DED"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</data>
|
||||||
|
</odoo>
|
||||||
19
l10n_us_va_hr_payroll/data/final.xml
Executable file
19
l10n_us_va_hr_payroll/data/final.xml
Executable file
@@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<data>
|
||||||
|
|
||||||
|
<!-- HR PAYROLL STRUCTURE -->
|
||||||
|
<record id="hr_payroll_salary_structure_us_va_employee" model="hr.payroll.structure">
|
||||||
|
<field name="code">US_VA_EMP</field>
|
||||||
|
<field name="name">USA Virginia Employee</field>
|
||||||
|
<field eval="[(6, 0, [
|
||||||
|
ref('hr_payroll_rules_va_unemp_wages_2018'),
|
||||||
|
ref('hr_payroll_rules_va_unemp_2018'),
|
||||||
|
ref('hr_payroll_rules_va_inc_withhold_2018'),
|
||||||
|
])]" name="rule_ids"/>
|
||||||
|
<field name="company_id" ref="base.main_company"/>
|
||||||
|
<field name="parent_id" ref="l10n_us_hr_payroll.hr_payroll_salary_structure_us_employee"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</data>
|
||||||
|
</odoo>
|
||||||
99
l10n_us_va_hr_payroll/data/rules_2018.xml
Executable file
99
l10n_us_va_hr_payroll/data/rules_2018.xml
Executable file
@@ -0,0 +1,99 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<data>
|
||||||
|
|
||||||
|
<!-- HR SALARY RULES-->
|
||||||
|
<record id="hr_payroll_rules_va_unemp_wages_2018" model="hr.salary.rule">
|
||||||
|
<field name="sequence" eval="423"/>
|
||||||
|
<field name="category_id" ref="hr_payroll_va_unemp_wages"/>
|
||||||
|
<field name="name">Virginia Unemployment - Wages (2018)</field>
|
||||||
|
<field name="code">VA_UNEMP_WAGES_2018</field>
|
||||||
|
<field name="condition_select">python</field>
|
||||||
|
<field name="condition_python">result = (payslip.date_to[:4] == '2018')</field>
|
||||||
|
<field name="amount_select">code</field>
|
||||||
|
<field name="amount_python_compute">
|
||||||
|
###
|
||||||
|
ytd = payslip.sum('VA_UNEMP_WAGES_2018', '2018-01-01', '2019-01-01')
|
||||||
|
ytd += contract.external_wages
|
||||||
|
remaining = 8000.0 - ytd
|
||||||
|
if remaining <= 0.0:
|
||||||
|
result = 0
|
||||||
|
elif remaining < categories.GROSS:
|
||||||
|
result = remaining
|
||||||
|
else:
|
||||||
|
result = categories.GROSS
|
||||||
|
</field>
|
||||||
|
<field name="appears_on_payslip" eval="False"/>
|
||||||
|
</record>
|
||||||
|
<record id="hr_payroll_rules_va_unemp_2018" model="hr.salary.rule">
|
||||||
|
<field name="sequence" eval="443"/>
|
||||||
|
<field name="category_id" ref="hr_payroll_va_unemp"/>
|
||||||
|
<field name="name">Virginia Unemployment (2018)</field>
|
||||||
|
<field name="code">VA_UNEMP_2018</field>
|
||||||
|
<field name="condition_select">python</field>
|
||||||
|
<field name="condition_python">result = (payslip.date_to[:4] == '2018')</field>
|
||||||
|
<field name="amount_select">code</field>
|
||||||
|
<field name="amount_python_compute">
|
||||||
|
result_rate = -contract.va_unemp_rate(2018)
|
||||||
|
result = categories.VA_UNEMP_WAGES
|
||||||
|
|
||||||
|
# result_rate of 0 implies 100% due to bug
|
||||||
|
if result_rate == 0.0:
|
||||||
|
result = 0.0
|
||||||
|
</field>
|
||||||
|
<field name="register_id" ref="contrib_register_vador_unemp"/>
|
||||||
|
<field name="appears_on_payslip" eval="False"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="hr_payroll_rules_va_inc_withhold_2018" model="hr.salary.rule">
|
||||||
|
<field name="sequence" eval="145"/>
|
||||||
|
<field name="category_id" ref="hr_payroll_va_income_withhold"/>
|
||||||
|
<field name="name">Virginia Income Withholding</field>
|
||||||
|
<field name="code">VA_INC_WITHHOLD_2018</field>
|
||||||
|
<field name="condition_select">python</field>
|
||||||
|
<field name="condition_python">result = (payslip.date_to[:4] == '2018')</field>
|
||||||
|
<field name="amount_select">code</field>
|
||||||
|
<field name="amount_python_compute">
|
||||||
|
wages = categories.GROSS
|
||||||
|
exemptions = contract.va_va4_exemptions
|
||||||
|
other_exemptions = contract.va_va4p_exemptions
|
||||||
|
schedule_pay = contract.schedule_pay
|
||||||
|
val = 0.00
|
||||||
|
|
||||||
|
PP = 0
|
||||||
|
if 'weekly' == schedule_pay:
|
||||||
|
PP = 52
|
||||||
|
elif 'bi-weekly' == schedule_pay:
|
||||||
|
PP = 26
|
||||||
|
elif 'semi-monthly' == schedule_pay:
|
||||||
|
PP = 24
|
||||||
|
elif 'monthly' == schedule_pay:
|
||||||
|
PP = 12
|
||||||
|
elif 'quarterly' == schedule_pay:
|
||||||
|
PP = 4
|
||||||
|
elif 'semi-annually' == schedule_pay:
|
||||||
|
PP = 2
|
||||||
|
elif 'annually' == schedule_pay:
|
||||||
|
PP = 1
|
||||||
|
else:
|
||||||
|
raise Exception('Invalid schedule_pay="' + schedule_pay + '" for VA Income Withholding calculation')
|
||||||
|
|
||||||
|
# Algorithm from https://www.tax.virginia.gov/withholding-calculator
|
||||||
|
T = wages * PP - (3000 + (exemptions * 930) + (other_exemptions * 800))
|
||||||
|
|
||||||
|
if T <= 3000:
|
||||||
|
W = 0.02 * T
|
||||||
|
elif T <= 5000:
|
||||||
|
W = 60 + (0.03 * (T - 3000))
|
||||||
|
elif T <= 17000:
|
||||||
|
W = 120 + (0.05 * (T - 5000))
|
||||||
|
else:
|
||||||
|
W = 720 + (0.0575 * (T - 17000))
|
||||||
|
|
||||||
|
result = - (W / PP)
|
||||||
|
</field>
|
||||||
|
<field name="register_id" ref="contrib_register_vador_withhold"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</data>
|
||||||
|
</odoo>
|
||||||
26
l10n_us_va_hr_payroll/hr_payroll.py
Executable file
26
l10n_us_va_hr_payroll/hr_payroll.py
Executable file
@@ -0,0 +1,26 @@
|
|||||||
|
from odoo import models, fields, api
|
||||||
|
|
||||||
|
|
||||||
|
class USVAHrContract(models.Model):
|
||||||
|
_inherit = 'hr.contract'
|
||||||
|
|
||||||
|
va_va4_exemptions = fields.Integer(string='Virginia VA-4 Exemptions', default=0)
|
||||||
|
va_va4p_exemptions = fields.Integer(string='Virginia VA-4P Exemptions', default=0)
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def va_unemp_rate(self, year):
|
||||||
|
self.ensure_one()
|
||||||
|
if self.futa_type == self.FUTA_TYPE_BASIC:
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
if hasattr(self.employee_id.company_id, 'va_unemp_rate_' + str(year)):
|
||||||
|
return self.employee_id.company_id['va_unemp_rate_' + str(year)]
|
||||||
|
|
||||||
|
raise NotImplemented('Year (' + str(year) + ') Not implemented for US Virginia.')
|
||||||
|
|
||||||
|
|
||||||
|
class VACompany(models.Model):
|
||||||
|
_inherit = 'res.company'
|
||||||
|
|
||||||
|
# Defaults from :: https://www.payroll-taxes.com/state-tax/virginia
|
||||||
|
va_unemp_rate_2018 = fields.Float(string="Virginia Unemployment Rate 2018", default=2.53)
|
||||||
32
l10n_us_va_hr_payroll/hr_payroll_view.xml
Executable file
32
l10n_us_va_hr_payroll/hr_payroll_view.xml
Executable file
@@ -0,0 +1,32 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<data>
|
||||||
|
<record id="us_fl_view_company_form" model="ir.ui.view">
|
||||||
|
<field name="name">res.company.form</field>
|
||||||
|
<field name="model">res.company</field>
|
||||||
|
<field name="priority">64</field>
|
||||||
|
<field name="inherit_id" ref="base.view_company_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<data>
|
||||||
|
<xpath expr="//field[@name='currency_id']" position="after">
|
||||||
|
<field name="va_unemp_rate_2018"/>
|
||||||
|
</xpath>
|
||||||
|
</data>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
<record id="hr_contract_form_l10n_us_va_inherit" model="ir.ui.view">
|
||||||
|
<field name="name">hr.contract.form.inherit</field>
|
||||||
|
<field name="model">hr.contract</field>
|
||||||
|
<field name="priority">64</field>
|
||||||
|
<field name="inherit_id" ref="hr_contract.hr_contract_view_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<data>
|
||||||
|
<xpath expr="//field[@name='w4_filing_status']" position="before">
|
||||||
|
<field name="va_va4_exemptions"/>
|
||||||
|
<field name="va_va4p_exemptions"/>
|
||||||
|
</xpath>
|
||||||
|
</data>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
</data>
|
||||||
|
</odoo>
|
||||||
3
l10n_us_va_hr_payroll/tests/__init__.py
Executable file
3
l10n_us_va_hr_payroll/tests/__init__.py
Executable file
@@ -0,0 +1,3 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from . import test_us_va_payslip_2018
|
||||||
137
l10n_us_va_hr_payroll/tests/test_us_va_payslip_2018.py
Executable file
137
l10n_us_va_hr_payroll/tests/test_us_va_payslip_2018.py
Executable file
@@ -0,0 +1,137 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from odoo.addons.l10n_us_hr_payroll.tests.test_us_payslip import TestUsPayslip, process_payslip
|
||||||
|
from odoo.addons.l10n_us_hr_payroll.l10n_us_hr_payroll import USHrContract
|
||||||
|
|
||||||
|
|
||||||
|
class TestUsVaPayslip(TestUsPayslip):
|
||||||
|
###
|
||||||
|
# Taxes and Rates
|
||||||
|
###
|
||||||
|
VA_UNEMP_MAX_WAGE = 8000.0
|
||||||
|
|
||||||
|
def test_2018_taxes(self):
|
||||||
|
salary = 5000.0
|
||||||
|
|
||||||
|
# For formula from https://www.tax.virginia.gov/withholding-calculator
|
||||||
|
"""
|
||||||
|
Key
|
||||||
|
G = Gross Pay for Pay Period P = Pay periods per year
|
||||||
|
A = Annualized gross pay E1 = Personal and Dependent Exemptions
|
||||||
|
T = Annualized taxable income E2 = Age 65 and Over & Blind Exemptions
|
||||||
|
WH = Tax to be withheld for pay period W = Annualized tax to be withheld
|
||||||
|
G x P - [$3000+ (E1 x 930) + (E2 x 800)] = T
|
||||||
|
Calculate W as follows:
|
||||||
|
If T is: W is:
|
||||||
|
Not over $3,000 2% of T
|
||||||
|
Over But Not Over Then
|
||||||
|
$3,000 $5,000 $60 + (3% of excess over $3,000)
|
||||||
|
$5,000 $17,000 $120 + (5% of excess over $5,000)
|
||||||
|
$17,000 $720 + (5.75% of excess over $17,000)
|
||||||
|
W / P = WH
|
||||||
|
"""
|
||||||
|
e1 = 2
|
||||||
|
e2 = 0
|
||||||
|
t = salary * 12 - (3000 + (e1 * 930) + (e2 * 800))
|
||||||
|
|
||||||
|
if t <= 3000:
|
||||||
|
w = 0.02 * t
|
||||||
|
elif t <= 5000:
|
||||||
|
w = 60 + (0.03 * (t - 3000))
|
||||||
|
elif t <= 17000:
|
||||||
|
w = 120 + (0.05 * (t - 5000))
|
||||||
|
else:
|
||||||
|
w = 720 + (0.0575 * (t - 17000))
|
||||||
|
|
||||||
|
wh = w / 12
|
||||||
|
|
||||||
|
employee = self._createEmployee()
|
||||||
|
employee.company_id.va_unemp_rate_2018 = 2.53
|
||||||
|
|
||||||
|
contract = self._createContract(employee, salary, struct_id=self.ref('l10n_us_va_hr_payroll.hr_payroll_salary_structure_us_va_employee'))
|
||||||
|
contract.va_va4_exemptions = e1
|
||||||
|
contract.va_va4p_exemptions = e2
|
||||||
|
|
||||||
|
# tax rates
|
||||||
|
va_unemp = contract.va_unemp_rate(2018) / -100.0
|
||||||
|
|
||||||
|
self._log('2017 Virginia tax last payslip:')
|
||||||
|
payslip = self._createPayslip(employee, '2017-12-01', '2017-12-31')
|
||||||
|
payslip.compute_sheet()
|
||||||
|
process_payslip(payslip)
|
||||||
|
|
||||||
|
self._log('2018 Virginia tax first payslip:')
|
||||||
|
payslip = self._createPayslip(employee, '2018-01-01', '2018-01-31')
|
||||||
|
|
||||||
|
payslip.compute_sheet()
|
||||||
|
|
||||||
|
cats = self._getCategories(payslip)
|
||||||
|
|
||||||
|
self.assertPayrollEqual(cats['VA_UNEMP_WAGES'], salary)
|
||||||
|
self.assertPayrollEqual(cats['VA_UNEMP'], cats['VA_UNEMP_WAGES'] * va_unemp)
|
||||||
|
self.assertPayrollEqual(cats['VA_INC_WITHHOLD'], -wh)
|
||||||
|
|
||||||
|
process_payslip(payslip)
|
||||||
|
|
||||||
|
# Make a new payslip, this one will have maximums
|
||||||
|
|
||||||
|
remaining_va_unemp_wages = self.VA_UNEMP_MAX_WAGE - salary if (self.VA_UNEMP_MAX_WAGE - 2*salary < salary) \
|
||||||
|
else salary
|
||||||
|
|
||||||
|
self._log('2018 Virginia tax second payslip:')
|
||||||
|
payslip = self._createPayslip(employee, '2018-02-01', '2018-02-28')
|
||||||
|
|
||||||
|
payslip.compute_sheet()
|
||||||
|
|
||||||
|
cats = self._getCategories(payslip)
|
||||||
|
|
||||||
|
self.assertPayrollEqual(cats['VA_UNEMP_WAGES'], remaining_va_unemp_wages)
|
||||||
|
self.assertPayrollEqual(cats['VA_UNEMP'], remaining_va_unemp_wages * va_unemp)
|
||||||
|
|
||||||
|
def test_2018_taxes_with_external(self):
|
||||||
|
salary = 5000.0
|
||||||
|
external_wages = 6000.0
|
||||||
|
|
||||||
|
employee = self._createEmployee()
|
||||||
|
employee.company_id.va_unemp_rate_2018 = 2.8
|
||||||
|
|
||||||
|
contract = self._createContract(employee, salary, external_wages=external_wages,
|
||||||
|
struct_id=self.ref('l10n_us_va_hr_payroll.hr_payroll_salary_structure_us_va_employee'))
|
||||||
|
|
||||||
|
# tax rates
|
||||||
|
va_unemp = contract.va_unemp_rate(2018) / -100.0
|
||||||
|
|
||||||
|
self._log('2018 Virginia_external tax first payslip:')
|
||||||
|
payslip = self._createPayslip(employee, '2018-01-01', '2018-01-31')
|
||||||
|
|
||||||
|
payslip.compute_sheet()
|
||||||
|
|
||||||
|
cats = self._getCategories(payslip)
|
||||||
|
|
||||||
|
self.assertPayrollEqual(cats['VA_UNEMP_WAGES'], self.VA_UNEMP_MAX_WAGE - external_wages)
|
||||||
|
self.assertPayrollEqual(cats['VA_UNEMP'], cats['VA_UNEMP_WAGES'] * va_unemp)
|
||||||
|
|
||||||
|
def test_2018_taxes_with_state_exempt(self):
|
||||||
|
salary = 5000.0
|
||||||
|
external_wages = 6000.0
|
||||||
|
|
||||||
|
employee = self._createEmployee()
|
||||||
|
employee.company_id.va_unemp_rate_2018 = 2.9
|
||||||
|
|
||||||
|
contract = self._createContract(employee, salary, external_wages=external_wages, struct_id=self.ref(
|
||||||
|
'l10n_us_va_hr_payroll.hr_payroll_salary_structure_us_va_employee'), futa_type=USHrContract.FUTA_TYPE_BASIC)
|
||||||
|
|
||||||
|
# tax rates
|
||||||
|
va_unemp = contract.va_unemp_rate(2018) / -100.0
|
||||||
|
|
||||||
|
self.assertPayrollEqual(va_unemp, 0.0)
|
||||||
|
|
||||||
|
self._log('2018 Virginia exempt tax first payslip:')
|
||||||
|
payslip = self._createPayslip(employee, '2018-01-01', '2018-01-31')
|
||||||
|
|
||||||
|
payslip.compute_sheet()
|
||||||
|
|
||||||
|
cats = self._getCategories(payslip)
|
||||||
|
|
||||||
|
self.assertPayrollEqual(cats['VA_UNEMP_WAGES'], self.VA_UNEMP_MAX_WAGE - external_wages)
|
||||||
|
self.assertPayrollEqual(cats['VA_UNEMP'], cats['VA_UNEMP_WAGES'] * va_unemp)
|
||||||
Reference in New Issue
Block a user