[REM] hr_payroll_hibou: available in professional

H14528
This commit is contained in:
Mayank Patel
2024-09-11 05:47:37 +00:00
parent 8a029bed93
commit 94903f0132
20 changed files with 0 additions and 1317 deletions

View File

@@ -1 +0,0 @@
from . import models

View File

@@ -1,32 +0,0 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
{
'name': 'Hibou Payroll',
'author': 'Hibou Corp. <hello@hibou.io>',
'version': '15.0.2.1.0',
'category': 'Payroll Localization',
'depends': [
'hr_payroll',
'hr_contract_reports',
'hibou_professional',
],
'description': """
Hibou Payroll
=============
Base module for fixing specific qwerks or assumptions in the way Payroll Odoo Enterprise Edition behaves.
2.1.0 : fixes precision error in upstream by changing `normal_wage` field from Integer to Float
""",
'data': [
'security/ir.model.access.csv',
'data/cron_data.xml',
'views/payroll_views.xml',
'views/res_config_settings_views.xml',
'views/update_views.xml',
],
'demo': [
],
'auto_install': True,
'license': 'OPL-1',
}

View File

@@ -1,19 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<!-- Cron jobs -->
<record model="ir.cron" id="payroll_publisher_update_cron">
<field name="name">Payroll Update: download latest parameter values/rates.</field>
<field name="model_id" ref="hr_payroll_hibou.model_hr_payroll_publisher_update"/>
<field name="state">code</field>
<field name="code">model.cron_payroll_update()</field>
<field name="interval_number">1</field>
<field name="interval_type">weeks</field>
<field name="numbercall">-1</field>
<field name="nextcall" eval="(datetime.now() + timedelta(days=1)).strftime('%Y-%m-%d %H:%M:%S')"/>
<field name="doall" eval="False"/>
</record>
</data>
</odoo>

View File

@@ -1,212 +0,0 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * hr_payroll_hibou
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 15.0+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-10-12 01:26+0000\n"
"PO-Revision-Date: 2021-10-12 01:26+0000\n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: hr_payroll_hibou
#: model:ir.model.fields.selection,name:hr_payroll_hibou.selection__res_config_settings__payslip_sum_type__date
msgid "Accounting Date"
msgstr "Fecha de Contabilidad"
#. module: hr_payroll_hibou
#: model:ir.model.fields,field_description:hr_payroll_hibou.field_res_config_settings__module_hr_payroll_attendance
msgid "Attendance Entries & Overtime"
msgstr "Entradas de Asistencias y Horas Extras"
#. module: hr_payroll_hibou
#: model:ir.model.fields,help:hr_payroll_hibou.field_res_config_settings__payslip_sum_type
msgid ""
"Behavior for what payslips are considered during rule execution. Stock Odoo behavior would not consider a payslip starting on 2019-12-30 ending on 2020-01-07 when summing a 2020 payslip category.\n"
"\n"
"Accounting Date requires Payroll Accounting and will fall back to date_to as the 'closest behavior'."
msgstr ""
"El comportamiento que se considera en los recibos de nómina durante la ejecución de una regla. El comportamiento de Stock Odoo no considerará a un recibo de nomina a partir de 2019-12-30 y que termina en 2020-01-07 al sumar una categoría de nómina de 2020.\n"
"\n"
"La fecha de contabilidad requiere contabilidad de nomina y usara date_to de respaldo como el 'comportamiento más cercana'."
#. module: hr_payroll_hibou
#: model:ir.model.fields,field_description:hr_payroll_hibou.field_res_config_settings__module_hr_payroll_commission
msgid "Commission"
msgstr "Comisión"
#. module: hr_payroll_hibou
#: model:ir.model,name:hr_payroll_hibou.model_res_config_settings
msgid "Config Settings"
msgstr "Opciones de configuración"
#. module: hr_payroll_hibou
#: model_terms:ir.ui.view,arch_db:hr_payroll_hibou.res_config_settings_view_form_hibou
msgid "Configure Matching & Limits"
msgstr "Configurar Contribuciones & Limites"
#. module: hr_payroll_hibou
#: model_terms:ir.ui.view,arch_db:hr_payroll_hibou.res_config_settings_view_form_hibou
msgid ""
"Customize the behavior of what payslips are eligible when summing over date ranges in rules.\n"
" Generally, \"Date To\" or \"Accounting Date\" would be preferred in the United States and anywhere\n"
" else where the ending date on the payslip is used to calculate wage bases."
msgstr ""
"Personalizar el comportamiento de las nóminas elegibles al sumar rangos de fechas en las reglas.\n"
" Generalmente, \"Hasta La Fecha\" o \"Fecha Contable\" sería preferible en los Estados Unidos y en cualquier lugar\n"
" donde la fecha de finalización en la nómina se utiliza para calcular las bases salariales.."
#. module: hr_payroll_hibou
#: model:ir.model.fields.selection,name:hr_payroll_hibou.selection__res_config_settings__payslip_sum_type__date_from
msgid "Date From"
msgstr "Desde la Fecha"
#. module: hr_payroll_hibou
#: model:ir.model.fields.selection,name:hr_payroll_hibou.selection__res_config_settings__payslip_sum_type__date_to
msgid "Date To"
msgstr "Hasta la Fecha"
#. module: hr_payroll_hibou
#: model:ir.model.fields,field_description:hr_payroll_hibou.field_hr_payroll_structure_type__default_schedule_pay
msgid "Default Scheduled Pay"
msgstr "Pago Programado Predeterminado"
#. module: hr_payroll_hibou
#: model:ir.model.fields,help:hr_payroll_hibou.field_hr_payroll_structure__schedule_pay
#: model:ir.model.fields,help:hr_payroll_hibou.field_hr_payroll_structure_type__default_schedule_pay
msgid "Defines the frequency of the wage payment."
msgstr "Define la frecuencia del pago de salario."
#. module: hr_payroll_hibou
#: model:ir.model,name:hr_payroll_hibou.model_hr_contract
msgid "Employee Contract"
msgstr "Contrato del empleado"
#. module: hr_payroll_hibou
#: model_terms:ir.ui.view,arch_db:hr_payroll_hibou.res_config_settings_view_form_hibou
msgid ""
"Extend Attendance into Payroll with Work Types, Overtime!\n"
" <strong>Hibou Professional</strong>"
msgstr ""
"¡Extender asistencias en nóminas con tipos de trabajos, Horas Extra!\n"
" <strong>Hibou Profesional</strong>"
#. module: hr_payroll_hibou
#: model_terms:ir.ui.view,arch_db:hr_payroll_hibou.res_config_settings_view_form_hibou
msgid ""
"Extend Timesheets into Payroll with Work Types, Overtime!\n"
" <strong>Hibou Professional</strong>"
msgstr ""
"¡Extender hojas de asistencia en nominas con tipos de trabajos, Horas Extra!\n"
" <strong>Hibou Profesional</strong>"
#. module: hr_payroll_hibou
#: model_terms:ir.ui.view,arch_db:hr_payroll_hibou.res_config_settings_view_form_hibou
msgid "Hibou Payroll"
msgstr "Hibou Nómina"
#. module: hr_payroll_hibou
#: model:ir.model.fields.selection,name:hr_payroll_hibou.selection__hr_contract__wage_type__hourly
msgid "Hourly Wage"
msgstr "Salario por Hora"
#. module: hr_payroll_hibou
#: model_terms:ir.ui.view,arch_db:hr_payroll_hibou.res_config_settings_view_form_hibou
msgid ""
"Pay Commissions in Payroll!\n"
" <strong>Hibou Professional</strong>"
msgstr ""
"¡Paga comisiones en Nómina!\n"
" <strong>Hibou Profesional</strong>"
#. module: hr_payroll_hibou
#: model:ir.model,name:hr_payroll_hibou.model_hr_payslip
msgid "Pay Slip"
msgstr "Recibo de nómina"
#. module: hr_payroll_hibou
#: model:ir.model.fields,field_description:hr_payroll_hibou.field_res_config_settings__module_hr_payroll_payment
msgid "Payments & Advanced Accounting"
msgstr "Pagos & Contabilidad Avanzada"
#. module: hr_payroll_hibou
#: model:ir.model.fields,field_description:hr_payroll_hibou.field_res_config_settings__payslip_sum_type
#: model_terms:ir.ui.view,arch_db:hr_payroll_hibou.res_config_settings_view_form_hibou
msgid "Payslip Sum Behavior"
msgstr "Comportamiento de la suma de recibos de nómina"
#. module: hr_payroll_hibou
#: model:ir.model.fields.selection,name:hr_payroll_hibou.selection__hr_contract__wage_type__monthly
msgid "Period Fixed Wage"
msgstr "Periodo Salario Fijo"
#. module: hr_payroll_hibou
#: model_terms:ir.ui.view,arch_db:hr_payroll_hibou.res_config_settings_view_form_hibou
msgid ""
"Provide retirement plans with optional company matching.\n"
" <strong>Hibou Professional</strong>"
msgstr ""
"Proporcionar planes de jubilación con contribucion opcional de la compañía.\n"
" <strong>Hibou Profesional</strong>"
#. module: hr_payroll_hibou
#: model_terms:ir.ui.view,arch_db:hr_payroll_hibou.res_config_settings_view_form_hibou
msgid ""
"Register payments on payslips! Have control over journal entries created from\n"
" payroll to include partner details, set grouping options, and apply fiscal position\n"
" account mappings.\n"
" <strong>Hibou Professional</strong>"
msgstr ""
"Registrar pagos en recibos de nomina! Ten control sobre sus asientos contables creado desde\n"
" la nomina para incluir detalles del socio, establecer opciones de agrupamiento, y aplicar la asignación de cuentas\n"
" de posición fiscal.\n"
" <strong>Hibou Profesional</strong>"
#. module: hr_payroll_hibou
#: model:ir.model,name:hr_payroll_hibou.model_hr_payroll_structure
msgid "Salary Structure"
msgstr "Estructura salarial"
#. module: hr_payroll_hibou
#: model:ir.model,name:hr_payroll_hibou.model_hr_payroll_structure_type
msgid "Salary Structure Type"
msgstr "Tipo de estructura salarial"
#. module: hr_payroll_hibou
#: model:ir.model.fields,field_description:hr_payroll_hibou.field_hr_payroll_structure__schedule_pay
msgid "Scheduled Pay"
msgstr "Pago Programado"
#. module: hr_payroll_hibou
#: model:ir.model.fields.selection,name:hr_payroll_hibou.selection__hr_payroll_structure__schedule_pay__semi-monthly
#: model:ir.model.fields.selection,name:hr_payroll_hibou.selection__hr_payroll_structure_type__default_schedule_pay__semi-monthly
msgid "Semi-monthly"
msgstr "Semestral"
#. module: hr_payroll_hibou
#: model:ir.model.fields,field_description:hr_payroll_hibou.field_res_config_settings__module_hr_payroll_timesheet
msgid "Timesheet Entries & Overtime"
msgstr "Entradas de hojas de asistencias y horas extra"
#. module: hr_payroll_hibou
#: model:ir.model.fields,field_description:hr_payroll_hibou.field_res_config_settings__module_l10n_us_hr_payroll
msgid "USA Payroll"
msgstr "Nómina de EE.UU"
#. module: hr_payroll_hibou
#: model:ir.model.fields,field_description:hr_payroll_hibou.field_res_config_settings__module_l10n_us_hr_payroll_401k
#: model_terms:ir.ui.view,arch_db:hr_payroll_hibou.res_config_settings_view_form_hibou
msgid "USA Payroll 401k"
msgstr "Nómina de EE.UUU 401K"
#. module: hr_payroll_hibou
#: model:ir.model.fields,field_description:hr_payroll_hibou.field_hr_contract__wage_type
#: model:ir.model.fields,field_description:hr_payroll_hibou.field_hr_payslip__wage_type
msgid "Wage Type"
msgstr "Tipo de salario"

View File

@@ -1,6 +0,0 @@
from . import browsable_object
from . import hr_contract
from . import hr_payslip
from . import hr_salary_rule
from . import res_config_settings
from . import update

View File

@@ -1,156 +0,0 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
from odoo import fields
from odoo.addons.hr_payroll.models import browsable_object
class BrowsableObject(object):
def __init__(self, employee_id, dict, env):
self.employee_id = employee_id
self.dict = dict
self.env = env
# Customization to allow changing the behavior of the discrete browsable objects.
# you can think of this as 'compiling' the query based on the configuration.
sum_field = env['ir.config_parameter'].sudo().get_param('hr_payroll.payslip.sum_behavior', 'date_from')
if sum_field == 'date' and 'date' not in env['hr.payslip']:
# missing attribute, closest by definition
sum_field = 'date_to'
if not sum_field:
sum_field = 'date_from'
self._compile_browsable_query(sum_field)
def __getattr__(self, attr):
return attr in self.dict and self.dict.__getitem__(attr) or 0.0
def __getitem__(self, key):
return self.dict[key] or 0.0
def _compile_browsable_query(self, sum_field):
pass
class InputLine(BrowsableObject):
"""a class that will be used into the python code, mainly for usability purposes"""
def _compile_browsable_query(self, sum_field):
self.__browsable_query = """
SELECT sum(amount) as sum
FROM hr_payslip as hp, hr_payslip_input as pi
WHERE hp.employee_id = %s AND hp.state in ('done', 'paid')
AND hp.{sum_field} >= %s AND hp.date_to <= %s AND hp.id = pi.payslip_id AND pi.code = %s""".format(sum_field=sum_field)
def sum(self, code, from_date, to_date=None):
if to_date is None:
to_date = fields.Date.today()
self.env.cr.execute(self.__browsable_query, (self.employee_id, from_date, to_date, code))
return self.env.cr.fetchone()[0] or 0.0
class WorkedDays(BrowsableObject):
"""a class that will be used into the python code, mainly for usability purposes"""
def _compile_browsable_query(self, sum_field):
self.__browsable_query = """
SELECT sum(number_of_days) as number_of_days, sum(number_of_hours) as number_of_hours
FROM hr_payslip as hp, hr_payslip_worked_days as pi
WHERE hp.employee_id = %s AND hp.state in ('done', 'paid')
AND hp.{sum_field} >= %s AND hp.date_to <= %s AND hp.id = pi.payslip_id AND pi.code = %s""".format(sum_field=sum_field)
def _sum(self, code, from_date, to_date=None):
if to_date is None:
to_date = fields.Date.today()
self.env.cr.execute(self.__browsable_query, (self.employee_id, from_date, to_date, code))
return self.env.cr.fetchone()
def sum(self, code, from_date, to_date=None):
res = self._sum(code, from_date, to_date)
return res and res[0] or 0.0
def sum_hours(self, code, from_date, to_date=None):
res = self._sum(code, from_date, to_date)
return res and res[1] or 0.0
class Payslips(BrowsableObject):
"""a class that will be used into the python code, mainly for usability purposes"""
def _compile_browsable_query(self, sum_field):
# Note that the core odoo has this as `hp.credit_note = False` but what if it is NULL?
# reverse of the desired behavior.
self.__browsable_query_rule = """
SELECT sum(case when hp.credit_note is not True then (pl.total) else (-pl.total) end)
FROM hr_payslip as hp, hr_payslip_line as pl
WHERE hp.employee_id = %s AND hp.state in ('done', 'paid')
AND hp.{sum_field} >= %s AND hp.date_to <= %s AND hp.id = pl.slip_id AND pl.code = %s""".format(sum_field=sum_field)
# Original (non-recursive)
# self.__browsable_query_category = """
# SELECT sum(case when hp.credit_note is not True then (pl.total) else (-pl.total) end)
# FROM hr_payslip as hp, hr_payslip_line as pl, hr_salary_rule_category as rc
# WHERE hp.employee_id = %s AND hp.state = 'done'
# AND hp.{sum_field} >= %s AND hp.date_to <= %s AND hp.id = pl.slip_id
# AND rc.id = pl.category_id AND rc.code = %s""".format(sum_field=sum_field)
# Hibou Recursive version
self.__browsable_query_category = """
WITH RECURSIVE
category_by_code as (
SELECT id
FROM hr_salary_rule_category
WHERE code = %s
),
category_ids as (
SELECT COALESCE((SELECT id FROM category_by_code), -1) AS id
UNION ALL
SELECT rc.id
FROM hr_salary_rule_category AS rc
JOIN category_ids AS rcs ON rcs.id = rc.parent_id
)
SELECT sum(case when hp.credit_note is not True then (pl.total) else (-pl.total) end)
FROM hr_payslip as hp, hr_payslip_line as pl
WHERE hp.employee_id = %s AND hp.state in ('done', 'paid')
AND hp.{sum_field} >= %s AND hp.date_to <= %s AND hp.id = pl.slip_id
AND pl.category_id in (SELECT id from category_ids)""".format(sum_field=sum_field)
def sum(self, code, from_date, to_date=None):
if to_date is None:
to_date = fields.Date.today()
self.env.cr.execute(self.__browsable_query_rule, (self.employee_id, from_date, to_date, code))
res = self.env.cr.fetchone()
return res and res[0] or 0.0
def rule_parameter(self, code):
return self.env['hr.rule.parameter']._get_parameter_from_code(code, self.dict.date_to)
def sum_category(self, code, from_date, to_date=None):
if to_date is None:
to_date = fields.Date.today()
self.env['hr.payslip'].flush(['credit_note', 'employee_id', 'state', 'date_from', 'date_to'])
self.env['hr.payslip.line'].flush(['total', 'slip_id', 'category_id'])
self.env['hr.salary.rule.category'].flush(['code'])
# standard version
# self.env.cr.execute(self.__browsable_query_category, (self.employee_id, from_date, to_date, code))
# recursive category version
self.env.cr.execute(self.__browsable_query_category, (code, self.employee_id, from_date, to_date))
res = self.env.cr.fetchone()
return res and res[0] or 0.0
@property
def paid_amount(self):
return self.dict._get_paid_amount()
# Hibou helper
@property
def pay_periods_in_year(self):
return self.dict.get_pay_periods_in_year()
# Patch over Core
browsable_object.BrowsableObject.__init__ = BrowsableObject.__init__
browsable_object.BrowsableObject._compile_browsable_query = BrowsableObject._compile_browsable_query
browsable_object.InputLine._compile_browsable_query = InputLine._compile_browsable_query
browsable_object.InputLine.sum = InputLine.sum
browsable_object.WorkedDays._compile_browsable_query = WorkedDays._compile_browsable_query
browsable_object.WorkedDays.sum = WorkedDays.sum
browsable_object.Payslips._compile_browsable_query = Payslips._compile_browsable_query
browsable_object.Payslips.sum = Payslips.sum
browsable_object.Payslips.sum_category = Payslips.sum_category
browsable_object.Payslips.pay_periods_in_year = Payslips.pay_periods_in_year

View File

@@ -1,20 +0,0 @@
from odoo import fields, models
class HrContract(models.Model):
_inherit = 'hr.contract'
wage_type = fields.Selection([('monthly', 'Period Fixed Wage'), ('hourly', 'Hourly Wage')],
default='monthly', required=True, related=False)
def _get_contract_wage(self, work_type=None):
# Override if you pay differently for different work types
# In 14.0, this utilizes new computed field mechanism,
# but will still get the 'wage' field by default.
self.ensure_one()
return self[self._get_contract_wage_field(work_type=work_type)]
def _get_contract_wage_field(self, work_type=None):
if self.wage_type == 'hourly':
return 'hourly_wage'
return super()._get_contract_wage_field()

View File

@@ -1,53 +0,0 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
from odoo import api, fields, models
class HrPayslip(models.Model):
_inherit = 'hr.payslip'
# From IRS Publication 15-T or logically (annually, bi-monthly)
PAY_PERIODS_IN_YEAR = {
'annually': 1,
'semi-annually': 2,
'quarterly': 4,
'bi-monthly': 6,
'monthly': 12,
'semi-monthly': 24,
'bi-weekly': 26,
'weekly': 52,
'daily': 260,
}
# normal_wage is an integer field, but that lacks precision.
normal_wage = fields.Float(compute='_compute_normal_wage', store=True)
# We need to be able to support more complexity,
# namely, that different employees will be paid by different wage types as 'salary' vs 'hourly'
wage_type = fields.Selection(related='contract_id.wage_type')
@api.depends('contract_id')
def _compute_normal_wage(self):
with_contract = self.filtered('contract_id')
# fixes bug in original computation if the size of the recordset is >1
(self - with_contract).update({'normal_wage': 0.0})
for payslip in with_contract:
payslip.normal_wage = payslip._get_contract_wage()
def get_year(self):
"""
# Helper method to get the year (normalized between Odoo Versions)
:return: int year of payslip
"""
return self.date_to.year
def _get_contract_wage(self, work_type=None):
# Override if you pay differently for different work types
# In 14.0, this utilizes new computed field mechanism,
# but will still get the 'wage' field by default.
# This would be a good place to override though with a 'work type'
# based mechanism, like a minimum rate or 'rate card' implementation
return self.contract_id._get_contract_wage(work_type=work_type)
def get_pay_periods_in_year(self):
return self.PAY_PERIODS_IN_YEAR.get(self.contract_id.schedule_pay, 0)

View File

@@ -1,26 +0,0 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
from odoo import fields, models
class HrPayrollStructure(models.Model):
_inherit = 'hr.payroll.structure'
schedule_pay = fields.Selection(selection_add=[
('semi-monthly', 'Semi-monthly'),
], ondelete={'semi-monthly': 'set null'})
class HrPayrollStructureType(models.Model):
_inherit = 'hr.payroll.structure.type'
default_schedule_pay = fields.Selection(selection_add=[
('semi-monthly', 'Semi-monthly'),
])
class HrRuleParameter(models.Model):
_inherit = 'hr.rule.parameter'
update_locked = fields.Boolean(string='Update Lock',
help='Lock parameter to prevent updating rates from publisher.')

View File

@@ -1,32 +0,0 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
from odoo import fields, models
class ResConfigSettings(models.TransientModel):
_inherit = 'res.config.settings'
# TODO We need MORE here...
module_hr_payroll_payment = fields.Boolean(string='Payments & Advanced Accounting')
module_hr_payroll_attendance = fields.Boolean(string='Attendance Entries & Overtime')
module_hr_payroll_timesheet = fields.Boolean(string='Timesheet Entries & Overtime')
module_hr_payroll_commission = fields.Boolean(string='Commission')
module_l10n_us_hr_payroll = fields.Boolean(string='USA Payroll')
module_l10n_us_hr_payroll_401k = fields.Boolean(string='USA Payroll 401k')
payslip_sum_type = fields.Selection([
('date_from', 'Date From'),
('date_to', 'Date To'),
('date', 'Accounting Date'),
], 'Payslip Sum Behavior', help="Behavior for what payslips are considered "
"during rule execution. Stock Odoo behavior "
"would not consider a payslip starting on 2019-12-30 "
"ending on 2020-01-07 when summing a 2020 payslip category.\n\n"
"Accounting Date requires Payroll Accounting and will "
"fall back to date_to as the 'closest behavior'.",
config_parameter='hr_payroll.payslip.sum_behavior')
def set_values(self):
super(ResConfigSettings, self).set_values()
self.env['ir.config_parameter'].set_param('hr_payroll.payslip.sum_behavior',
self.payslip_sum_type or 'date_from')

View File

@@ -1,161 +0,0 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
import requests
import json
from odoo import api, fields, models, _
from odoo.exceptions import UserError
class HRPayrollPublisherUpdate(models.Model):
_name = 'hr.payroll.publisher.update'
_description = 'Payroll Update'
_order = 'id DESC'
def _default_request_modules(self):
request_modules = self.env.context.get('default_request_modules')
if not request_modules:
request_modules = '\n'.join(self.env['publisher_warranty.contract'].hibou_payroll_modules_to_update())
return request_modules
state = fields.Selection([
('draft', 'Draft'),
('result', 'Result'),
('done', 'Done'),
('error', 'Error'),
], default='draft')
request_modules = fields.Char(default=_default_request_modules,
states={'done': [('readonly', True)],
'error': [('readonly', True)]})
result = fields.Text(readonly=True)
parameter_codes_retrieved = fields.Text(readonly=True)
error = fields.Text(readonly=True)
@api.model
def cron_payroll_update(self):
update = self.create({})
if update.request_modules:
update.button_send()
else:
update.unlink()
def button_send(self):
self.ensure_one()
if not self.request_modules:
raise UserError('One or more modules needed.')
if self.result:
raise UserError('Already retrieved')
self._send()
if self.result and not self.state == 'error':
self._process_result()
def _send(self):
try:
self.env['publisher_warranty.contract'].hibou_payroll_update(self)
except UserError as e:
self.set_error_state(e.name)
def set_error_state(self, message=''):
self.write({
'state': 'error',
'error': message,
})
def set_result(self, result):
self.write({
'state': 'result',
'result': result,
'error': False,
})
def button_process_result(self):
self.ensure_one()
if not self.result:
raise UserError('No Result to process.')
self._process_result()
def _process_result(self):
try:
result_dict = json.loads(self.result)
parameter_values = result_dict.get('payroll_parameter_values')
if not parameter_values or not isinstance(parameter_values, list):
self.set_error_state('Result is missing expected parameter values.')
parameter_map = {}
parameter_model = self.env['hr.rule.parameter'].sudo()
for code, date_from, pv in parameter_values:
date_from = fields.Date.from_string(date_from)
if code not in parameter_map:
parameter_map[code] = parameter_model.search([('code', '=', code)], limit=1)
parameter = parameter_map[code]
if not parameter or parameter.update_locked:
continue
# watch out for versions of Odoo where this is not datetime.date
parameter_version = parameter.parameter_version_ids.filtered(lambda p: p.date_from == date_from)
if not parameter_version:
parameter.write({
'parameter_version_ids': [(0, 0, {
'date_from': date_from,
'parameter_value': pv,
})]
})
elif parameter_version.parameter_value != pv:
parameter_version.write({
'parameter_value': pv,
})
# We have applied all of the updates. Set statistics.
self.write({
'state': 'done',
'error': '',
'parameter_codes_retrieved': '\n'.join('%s%s' % (c, '' if p and not p.update_locked else ' (LOCKED)' if p.update_locked else ' (MISSING)') for c, p in parameter_map.items()),
})
except Exception as e:
self.set_error_state(str(e))
class PublisherWarrantyContract(models.AbstractModel):
_inherit = 'publisher_warranty.contract'
CONFIG_HIBOU_URL_PAYROLL = 'https://api.hibou.io/hibouapi/v1/professional/payroll'
@api.model
def hibou_payroll_modules_to_update(self):
# Filled downstream
return []
@api.model
def hibou_payroll_update(self, update_request):
# Check status locally
status = self.hibou_professional_status()
if status['expired']:
raise UserError('Hibou Professional Subscription Expired, you cannot retrieve updates.')
if status['expiration_reason'] == 'trial':
raise UserError('Hibou Professional Subscription Trial, not eligible for updates.')
if not status['professional_code']:
raise UserError('Hibou Professional Subscription Missing, please setup your subscription.')
if self.env.context.get('test_payroll_update_result'):
update_request.set_result(self.env.context.get('test_payroll_update_result'))
return
try:
res = self._hibou_payroll_update(update_request.request_modules)
if res.get('error'):
update_request.set_error_state(str(res.get('error')))
else:
update_request.set_result(json.dumps(res))
except Exception as e:
update_request.set_error_state(str(e))
def _hibou_payroll_update(self, payroll_modules):
data = self._get_hibou_message()
data['payroll_modules'] = payroll_modules
data = {
'jsonrpc': '2.0',
'method': 'call',
'params': data,
}
r = requests.post(self.CONFIG_HIBOU_URL_PAYROLL, json=data, timeout=30)
r.raise_for_status()
wrapper = r.json()
return wrapper.get('result', {})

View File

@@ -1,2 +0,0 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
hr_payroll_hibou.access_hr_payroll_publisher_update,access_hr_payroll_publisher_update,hr_payroll_hibou.model_hr_payroll_publisher_update,base.group_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 hr_payroll_hibou.access_hr_payroll_publisher_update access_hr_payroll_publisher_update hr_payroll_hibou.model_hr_payroll_publisher_update base.group_user 1 1 1 1

View File

@@ -1,7 +0,0 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
from . import common
from . import test_contract_wage_type
from . import test_special
from . import test_update

View File

@@ -1,166 +0,0 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
from logging import getLogger
from sys import float_info as sys_float_info
from collections import defaultdict
from odoo.tests import common
from odoo.tools.float_utils import float_round as odoo_float_round
def process_payslip(payslip):
try:
return payslip.action_payslip_done()
except AttributeError:
# v9
return payslip.process_sheet()
class TestPayslip(common.TransactionCase):
debug = False
_logger = getLogger(__name__)
def process_payslip(self, payslip=None):
if not payslip:
return process_payslip(self.payslip)
return process_payslip(payslip)
def setUp(self):
super(TestPayslip, self).setUp()
self.contract_model = self.env['hr.contract']
self.env.user.tz = 'PST8PDT'
self.env.ref('resource.resource_calendar_std').tz = 'PST8PDT'
self.env['ir.config_parameter'].set_param('hr_payroll.payslip.sum_behavior', 'date_to')
self.structure_type = self.env['hr.payroll.structure.type'].create({
'name': 'Test Structure Type',
})
self.structure = self.env['hr.payroll.structure'].create({
'name': 'Test Structure',
'type_id': self.structure_type.id,
})
self._log('structue_type %s and structure %s' % (self.structure_type, self.structure))
self.structure_type.default_struct_id = self.structure
self.resource_calendar = self.ref('resource.resource_calendar_std')
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.warning(message)
def _createEmployee(self):
employee = 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'
})
employee.address_home_id = self.env['res.partner'].create({
'name': 'Jared (private)',
})
return employee
def _get_contract_defaults(self, contract_values):
if not contract_values.get('state'):
contract_values['state'] = 'open' # Running
if not contract_values.get('structure_type_id'):
contract_values['structure_type_id'] = self.structure_type.id
if not contract_values.get('date_start'):
contract_values['date_start'] = '2016-01-01'
if not contract_values.get('date_end'):
contract_values['date_end'] = '2030-12-31'
if not contract_values.get('resource_calendar_id'):
contract_values['resource_calendar_id'] = self.resource_calendar
# Compatibility with earlier Odoo versions
if not contract_values.get('journal_id') and hasattr(self.contract_model, 'journal_id'):
try:
contract_values['journal_id'] = self.env['account.journal'].search([('type', '=', 'general')], limit=1).id
except KeyError:
# Accounting not installed
pass
def _createContract(self, employee, **kwargs):
if not 'schedule_pay' in kwargs:
kwargs['schedule_pay'] = 'monthly'
schedule_pay = kwargs['schedule_pay']
contract_values = {
'name': 'Test Contract',
'employee_id': employee.id,
}
for key, val in kwargs.items():
# Assume any Odoo object is in a Many2one
if hasattr(val, 'id'):
val = val.id
found = False
if hasattr(self.contract_model, key):
contract_values[key] = val
found = True
if not found:
self._logger.warning('cannot locate attribute names "%s" on hr.contract().' % (key, ))
self._get_contract_defaults(contract_values)
contract = self.contract_model.create(contract_values)
# Compatibility with Odoo 14
contract.structure_type_id.default_struct_id.schedule_pay = schedule_pay
return contract
def _createPayslip(self, employee, date_from, date_to, skip_compute=False, other_values=False):
if not other_values:
other_values = {}
create_values = {
'name': 'Test %s From: %s To: %s' % (employee.name, date_from, date_to),
'employee_id': employee.id,
'date_from': date_from,
'date_to': date_to
}
create_values.update(other_values)
slip = self.env['hr.payslip'].create(create_values)
# Included in hr.payslip.action_refresh_from_work_entries() as ov 14.0 EE
# slip._onchange_employee()
# as is the 'compute' that is almost always called immediaately after
if not skip_compute:
slip.action_refresh_from_work_entries()
return slip
def _getCategories(self, payslip):
categories = defaultdict(float)
for line in payslip.line_ids:
self._log(' line code: ' + str(line.code) +
' category code: ' + line.category_id.code +
' total: ' + str(line.total) +
' rate: ' + str(line.rate) +
' amount: ' + str(line.amount))
category_id = line.category_id
category_code = line.category_id.code
while category_code:
categories[category_code] += line.total
category_id = category_id.parent_id
category_code = category_id.code
return categories
def _getRules(self, payslip):
rules = defaultdict(float)
for line in payslip.line_ids:
rules[line.code] += line.total
return rules
def assertPayrollEqual(self, first, second):
self.assertAlmostEqual(first, second, self.payroll_digits)
def assertPayrollAlmostEqual(self, first, second):
self.assertAlmostEqual(first, second, self.payroll_digits-1)

View File

@@ -1,26 +0,0 @@
from .common import TestPayslip, process_payslip
class TestContractWageType(TestPayslip):
def test_per_contract_wage_type_salary(self):
self.debug = True
salary = 80000.0
employee = self._createEmployee()
contract = self._createContract(employee, wage=salary, hourly_wage=salary/100.0, wage_type='monthly', schedule_pay='bi-weekly')
payslip = self._createPayslip(employee, '2019-12-30', '2020-01-12')
self.assertEqual(contract.wage_type, 'monthly')
self.assertEqual(payslip.wage_type, 'monthly')
cats = self._getCategories(payslip)
self.assertEqual(cats['BASIC'], salary)
def test_per_contract_wage_type_hourly(self):
self.debug = True
hourly_wage = 21.50
employee = self._createEmployee()
contract = self._createContract(employee, wage=hourly_wage*100.0, hourly_wage=hourly_wage, wage_type='hourly', schedule_pay='bi-weekly')
payslip = self._createPayslip(employee, '2019-12-30', '2020-01-12')
self.assertEqual(contract.wage_type, 'hourly')
self.assertEqual(payslip.wage_type, 'hourly')
cats = self._getCategories(payslip)
self.assertEqual(cats['BASIC'], hourly_wage * 80.0)

View File

@@ -1,127 +0,0 @@
from .common import TestPayslip, process_payslip
class TestSpecial(TestPayslip):
def test_get_year(self):
salary = 80000.0
employee = self._createEmployee()
# so the schedule_pay is now on the Structure...
contract = self._createContract(employee, wage=salary)
payslip = self._createPayslip(employee, '2020-01-01', '2020-01-14')
self.assertEqual(payslip.get_year(), 2020)
def test_semi_monthly(self):
salary = 80000.0
employee = self._createEmployee()
# so the schedule_pay is now on the Structure...
contract = self._createContract(employee, wage=salary, schedule_pay='semi-monthly')
payslip = self._createPayslip(employee, '2019-01-01', '2019-01-14')
def test_payslip_sum_behavior(self):
self.env['ir.config_parameter'].set_param('hr_payroll.payslip.sum_behavior', 'date')
rule_category_comp = self.env.ref('hr_payroll.COMP')
test_rule_category = self.env['hr.salary.rule.category'].create({
'name': 'Test Sum Behavior',
'code': 'test_sum_behavior',
'parent_id': rule_category_comp.id,
})
test_rule = self.env['hr.salary.rule'].create({
'sequence': 450,
'struct_id': self.structure.id,
'category_id': test_rule_category.id,
'name': 'Test Sum Behavior',
'code': 'test_sum_behavior',
'condition_select': 'python',
'condition_python': 'result = 1',
'amount_select': 'code',
'amount_python_compute': '''
ytd_category = payslip.sum_category('test_sum_behavior', '2020-01-01', '2021-01-01')
ytd_rule = payslip.sum('test_sum_behavior', '2020-01-01', '2021-01-01')
result = 0.0
if ytd_category != ytd_rule:
# error
result = -1.0
elif ytd_rule == 0.0:
# first payslip in period
result = 1.0
'''
})
salary = 80000.0
employee = self._createEmployee()
contract = self._createContract(employee, wage=salary, schedule_pay='bi-weekly')
payslip = self._createPayslip(employee, '2019-12-30', '2020-01-12')
cats = self._getCategories(payslip)
self.assertEqual(cats['BASIC'], salary)
self.assertEqual(cats['test_sum_behavior'], 1.0)
process_payslip(payslip)
# Basic date_from behavior.
self.env['ir.config_parameter'].set_param('hr_payroll.payslip.sum_behavior', 'date_from')
# The the date_from on the last payslip will not be found
payslip = self._createPayslip(employee, '2020-01-13', '2020-01-27')
payslip.compute_sheet()
cats = self._getCategories(payslip)
self.assertEqual(cats['test_sum_behavior'], 1.0)
# date_to behavior.
self.env['ir.config_parameter'].set_param('hr_payroll.payslip.sum_behavior', 'date_to')
# The date_to on the last payslip is found
payslip = self._createPayslip(employee, '2020-01-13', '2020-01-27')
payslip.compute_sheet()
cats = self._getCategories(payslip)
self.assertEqual(cats['test_sum_behavior'], 0.0)
def test_recursive_salary_rule_category(self):
self.debug = True
# In this scenario, you are in rule code that will check for the category
# and a subcategory will also
alw_category = self.env.ref('hr_payroll.ALW')
ded_category = self.env.ref('hr_payroll.DED')
test_category = self.env['hr.salary.rule.category'].create({
'name': 'Special ALW',
'code': 'ALW_SPECIAL_RECURSIVE',
'parent_id': alw_category.id,
})
test_special_alw = self.env['hr.salary.rule'].create({
'name': 'Flat amount 200',
'code': 'ALW_SPECIAL_RECURSIVE',
'category_id': test_category.id,
'condition_select': 'none',
'amount_select': 'fix',
'amount_fix': 200.0,
'struct_id': self.structure.id,
})
test_recursion = self.env['hr.salary.rule'].create({
'name': 'Actual Test Behavior',
'code': 'RECURSION_TEST',
'category_id': ded_category.id,
'condition_select': 'none',
'amount_select': 'code',
'amount_python_compute': """
# this rule will always be the total of the ALW category and YTD ALW category
result = categories.ALW
# Note, this tests the hr.payslip.get_year() to return an integer rep of year
year = payslip.dict.get_year()
result += payslip.sum_category('ALW', str(year) + '-01-01', str(year+1) + '-01-01')
""",
'sequence': 101,
'struct_id': self.structure.id,
})
salary = 80000.0
employee = self._createEmployee()
contract = self._createContract(employee, wage=salary, schedule_pay='bi-weekly')
payslip = self._createPayslip(employee, '2020-01-01', '2020-01-14')
payslip.compute_sheet()
cats = self._getCategories(payslip)
rules = self._getRules(payslip)
self.assertEqual(rules['RECURSION_TEST'], 200.0)
process_payslip(payslip)
payslip = self._createPayslip(employee, '2020-01-15', '2020-01-27')
payslip.compute_sheet()
cats = self._getCategories(payslip)
rules = self._getRules(payslip)
# two hundred is in the YTD ALW
self.assertEqual(rules['RECURSION_TEST'], 200.0 + 200.0)

View File

@@ -1,93 +0,0 @@
import datetime
from odoo import fields
from odoo.exceptions import ValidationError
from odoo.tests import common
class TestUpdate(common.TransactionCase):
def setUp(self):
super().setUp()
# setup the database to run in general
today = datetime.date.today()
tomorrow = today + datetime.timedelta(days=1)
self.param_model = self.env['ir.config_parameter'].sudo()
self.param_model.set_param('database.hibou_professional_expiration_date', fields.Date.to_string(tomorrow))
self.param_model.set_param('database.hibou_professional_code', 'TESTCODE')
def test_01_database_state(self):
today = datetime.date.today()
tomorrow = today + datetime.timedelta(days=1)
yesterday = today - datetime.timedelta(days=1)
self.param_model.set_param('database.hibou_professional_expiration_date', fields.Date.to_string(yesterday))
update = self.env['hr.payroll.publisher.update']\
.with_context(test_payroll_update_result='{"payroll_parameter_values":[]}')\
.create({
'request_modules': 'test',
})
self.assertEqual(update.state, 'draft')
update.button_send()
self.assertEqual(update.state, 'error')
self.param_model.set_param('database.hibou_professional_expiration_date', fields.Date.to_string(tomorrow))
update.button_send()
self.assertEqual(update.state, 'done')
self.assertEqual(update.parameter_codes_retrieved, '')
# Reset to a degree.
update = update.with_context(test_payroll_update_result='{"payroll_parameter_values":[["missing_code", "2021-01-01", "5.0"]]}')
update.write({
'state': 'draft',
'result': '',
})
update.button_send()
self.assertEqual(update.state, 'done')
self.assertEqual(update.parameter_codes_retrieved, 'missing_code (MISSING)')
# Actually add to a rule.
test_parameter = self.env['hr.rule.parameter'].create({
'code': 'test_parameter_1',
'name': 'Test Parameter 1',
})
update = update.with_context(test_payroll_update_result='{"payroll_parameter_values":[["test_parameter_1", "2021-01-01", "5.0"]]}')
update.write({
'state': 'draft',
'result': '',
})
update.button_send()
self.assertEqual(update.state, 'done')
self.assertEqual(update.parameter_codes_retrieved, 'test_parameter_1')
self.assertTrue(test_parameter.parameter_version_ids)
self.assertEqual(test_parameter.parameter_version_ids.parameter_value, '5.0')
self.assertEqual(str(test_parameter.parameter_version_ids.date_from), '2021-01-01')
test_parameter.parameter_version_ids.write({
'parameter_value': '',
})
self.assertEqual(test_parameter.parameter_version_ids.parameter_value, '')
update.write({
'state': 'draft',
'result': '',
})
update.button_send()
# doesn't make a new one, updates existing...
self.assertEqual(test_parameter.parameter_version_ids.parameter_value, '5.0')
# Test that we can lock the parameter
test_parameter.parameter_version_ids.write({
'parameter_value': 'locked',
})
test_parameter.write({
'update_locked': True,
})
self.assertEqual(test_parameter.parameter_version_ids.parameter_value, 'locked')
update.write({
'state': 'draft',
'result': '',
})
update.button_send()
# doesn't make a new one, updates existing...
self.assertEqual(test_parameter.parameter_version_ids.parameter_value, 'locked')
self.assertEqual(update.parameter_codes_retrieved, 'test_parameter_1 (LOCKED)')

View File

@@ -1,15 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="hr_rule_parameter_view_form_inherit" model="ir.ui.view">
<field name="name">hr.rule.parameter.form.inherit</field>
<field name="model">hr.rule.parameter</field>
<field name="inherit_id" ref="hr_payroll.hr_rule_parameter_view_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='code']" position="after">
<field name="update_locked" />
</xpath>
</field>
</record>
</odoo>

View File

@@ -1,100 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="res_config_settings_view_form_hibou" model="ir.ui.view">
<field name="name">res.config.settings.view.form.inherit.hr.payroll.hibou</field>
<field name="model">res.config.settings</field>
<field name="priority" eval="50"/>
<field name="inherit_id" ref="hr_payroll.res_config_settings_view_form"/>
<field name="arch" type="xml">
<xpath expr="//div[@data-key='hr_payroll']" position="inside">
<field name="module_l10n_us_hr_payroll" invisible="1"/>
<!-- TODO payment, attendance, timesheet, ovetrtime... -->
<h2>Hibou Payroll</h2>
<div class="row mt16 o_settings_container" id="hr_payroll_hibou">
<div class="col-lg-6 col-12 o_setting_box">
<div class="o_setting_left_pane"/>
<div class="o_setting_right_pane">
<label for="payslip_sum_type" string="Payslip Sum Behavior"/>
<div class="text-muted">
Customize the behavior of what payslips are eligible when summing over date ranges in rules.
Generally, "Date To" or "Accounting Date" would be preferred in the United States and anywhere
else where the ending date on the payslip is used to calculate wage bases.
</div>
<field name="payslip_sum_type"/>
</div>
</div>
<div class="col-lg-6 col-12 o_setting_box">
<div class="o_setting_left_pane">
<field name="module_l10n_us_hr_payroll_401k"/>
</div>
<div class="o_setting_right_pane">
<label for="module_l10n_us_hr_payroll_401k" string="USA Payroll 401k"/>
<div class="text-muted">
Provide retirement plans with optional company matching.
<strong>Hibou Professional</strong>
</div>
<div class="mt8" id="l10n_us_hr_payroll_401k_match">
<button name="%(hr_payroll.hr_rule_parameter_action)d" icon="fa-arrow-right" type="action"
string="Configure Matching &amp; Limits" class="btn-link"
context="{'search_default_us_payroll_401k': True}"
attrs="{'invisible': [('module_l10n_us_hr_payroll_401k', '=', False)]}"/>
</div>
</div>
</div>
<div class="col-lg-6 col-12 o_setting_box">
<div class="o_setting_left_pane">
<field name="module_hr_payroll_payment"/>
</div>
<div class="o_setting_right_pane">
<label for="module_hr_payroll_payment"/>
<div class="text-muted">
Register payments on payslips! Have control over journal entries created from
payroll to include partner details, set grouping options, and apply fiscal position
account mappings.
<strong>Hibou Professional</strong>
</div>
</div>
</div>
<div class="col-lg-6 col-12 o_setting_box">
<div class="o_setting_left_pane">
<field name="module_hr_payroll_attendance"/>
</div>
<div class="o_setting_right_pane">
<label for="module_hr_payroll_attendance"/>
<div class="text-muted">
Extend Attendance into Payroll with Work Types, Overtime!
<strong>Hibou Professional</strong>
</div>
</div>
</div>
<div class="col-lg-6 col-12 o_setting_box">
<div class="o_setting_left_pane">
<field name="module_hr_payroll_timesheet"/>
</div>
<div class="o_setting_right_pane">
<label for="module_hr_payroll_timesheet"/>
<div class="text-muted">
Extend Timesheets into Payroll with Work Types, Overtime!
<strong>Hibou Professional</strong>
</div>
</div>
</div>
<div class="col-lg-6 col-12 o_setting_box">
<div class="o_setting_left_pane">
<field name="module_hr_payroll_commission"/>
</div>
<div class="o_setting_right_pane">
<label for="module_hr_payroll_commission"/>
<div class="text-muted">
Pay Commissions in Payroll!
<strong>Hibou Professional</strong>
</div>
</div>
</div>
</div>
</xpath>
</field>
</record>
</odoo>

View File

@@ -1,63 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="hr_payroll_publisher_update_tree" model="ir.ui.view">
<field name="name">hr.payroll.publisher.update.tree</field>
<field name="model">hr.payroll.publisher.update</field>
<field name="arch" type="xml">
<tree string="Payroll Updates">
<field name="create_date"/>
<field name="write_date"/>
<field name="request_modules"/>
<field name="state"/>
</tree>
</field>
</record>
<record id="hr_payroll_publisher_update_form" model="ir.ui.view">
<field name="name">hr.payroll.publisher.update.form</field>
<field name="model">hr.payroll.publisher.update</field>
<field name="arch" type="xml">
<form string="Payroll Udate">
<header>
<button name="button_send" type="object" string="Send" attrs="{'invisible': [('state', 'not in', ('draft', 'error'))]}" />
<button name="button_process_result" type="object" string="Process Results" attrs="{'invisible': [('state', 'not in', ('result', 'error'))]}" />
<field name="state" widget="statusbar" statusbar_visible="draft,result,done" />
</header>
<sheet>
<group>
<group name="general" string="General">
<field name="create_date" />
<field name="write_date" />
<field name="request_modules" widget="text" />
<field name="error" attrs="{'invisible': [('error', '=', False)]}" />
</group>
<group name="parameter_codes" string="Parameter Codes">
<field name="parameter_codes_retrieved" />
<p colspan="2">Note missing codes probably means code upgrades are required. Locked codes are only locked in this database.</p>
</group>
<group name="result" string="Result" groups="base.group_no_one" colspan="4">
<field name="result" nolabel="1" colspan="4" />
</group>
</group>
</sheet>
</form>
</field>
</record>
<record id="action_payroll_updates" model="ir.actions.act_window">
<field name="name">Payroll Updates</field>
<field name="res_model">hr.payroll.publisher.update</field>
<field name="view_mode">tree,form</field>
<field name="help" type="html">
<p>
No Updates
</p>
</field>
</record>
<menuitem id="menu_hr_payroll_configuration_updates" name="Payroll Updates"
action="action_payroll_updates"
sequence="20" parent="hr_work_entry_contract_enterprise.menu_hr_payroll_configuration"/>
</odoo>