[MIG] l10n_us_hr_payroll: to Odoo 14.0

Require `hr_payroll_hibou`, remove a lot of 'fixes' and stuff that is useful for 'all' payroll.

Failing tests:

Deleware 2020 is low by ~0.1% in SIT only.
Pennsylvania 2019, 2020 is low by ~0.01% in SIT only.
I did not try to correct, only verify that the data and calculation is the same between versions. (which it is)

Washington 2019, 2020 hours was off  (183.99972222222223 != 184)
Fixed by turning it into assertAlmostEqual
This commit is contained in:
Jared Kipe
2020-11-21 16:57:52 -08:00
parent be1fd5c8d2
commit fe423e0d0b
14 changed files with 29 additions and 459 deletions

View File

@@ -3,12 +3,10 @@
{
'name': 'United States of America - Payroll',
'author': 'Hibou Corp. <hello@hibou.io>',
'version': '13.0.2020.0.0',
'version': '14.0.2020.0.0',
'category': 'Payroll Localization',
'depends': [
'hr_payroll',
'hr_contract_reports',
'hibou_professional',
'hr_payroll_hibou',
],
'description': """
United States of America - Payroll Rules.
@@ -74,7 +72,6 @@ United States of America - Payroll Rules.
'data/state/wv_west_virginia.xml',
'data/state/wy_wyoming.xml',
'views/hr_contract_views.xml',
'views/res_config_settings_views.xml',
'views/us_payroll_config_views.xml',
],
'demo': [

View File

@@ -7,16 +7,21 @@
<field name="country_id" ref="base.us"/>
</record>
<record id="hr_payroll_structure" model="hr.payroll.structure">
<field name="name">USA Employee Standard</field>
<field name="country_id" ref="base.us"/>
<field name="type_id" ref="l10n_us_hr_payroll.structure_type_employee"/>
<field name="regular_pay" eval="True"/>
<field name="unpaid_work_entry_type_ids" eval="[
(4, ref('hr_payroll.work_entry_type_unpaid_leave')),
(4, ref('hr_work_entry_contract.work_entry_type_unpaid_leave')),
]"/>
</record>
<record id="l10n_us_hr_payroll.structure_type_employee" model="hr.payroll.structure.type">
<field name="default_struct_id" ref="hr_payroll_structure"/>
</record>
<!-- State Unemployment -->
<record id="hr_payroll_category_ee_us_suta" model="hr.salary.rule.category">
<field name="name">EE: State Unemployment SUTA</field>

View File

@@ -1,8 +1,6 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
from . import browsable_object
from . import hr_contract
from . import hr_payslip
from . import res_config_settings
from . import update
from . import us_payroll_config

View File

@@ -1,148 +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 _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 = 'done'
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 = 'done'
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 = 'done'
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 = 'done'
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()
# 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

View File

@@ -6,16 +6,6 @@ from .us_payroll_config import FUTA_TYPE_NORMAL, \
FUTA_TYPE_EXEMPT
class HrPayrollStructureType(models.Model):
_inherit = 'hr.payroll.structure.type'
default_schedule_pay = fields.Selection(selection_add=[('semi-monthly', 'Semi-monthly')])
class HrPayrollStructure(models.Model):
_inherit = 'hr.payroll.structure'
schedule_pay = fields.Selection(selection_add=[('semi-monthly', 'Semi-monthly')])
class USHRContract(models.Model):
_inherit = 'hr.contract'

View File

@@ -124,9 +124,5 @@ class HRPayslip(models.Model):
})
return res
def get_year(self):
# Helper method to get the year (normalized between Odoo Versions)
return self.date_to.year
def get_pay_periods_in_year(self):
return self.PAY_PERIODS_IN_YEAR.get(self.contract_id.schedule_pay, 0)

View File

@@ -1,24 +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'
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

@@ -7,6 +7,7 @@ from . import test_special
from . import test_us_payslip_2019
from . import test_us_payslip_2020
from . import test_us_ak_alaska_payslip_2019
from . import test_us_ak_alaska_payslip_2020

View File

@@ -1,60 +1,26 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
from logging import getLogger
from sys import float_info as sys_float_info
from collections import defaultdict
from datetime import timedelta
from odoo.tests import common
from odoo.tools.float_utils import float_round as odoo_float_round
from odoo.addons.l10n_us_hr_payroll.models.hr_contract import USHRContract
def process_payslip(payslip):
try:
payslip.action_payslip_done()
except AttributeError:
# v9
payslip.process_sheet()
from odoo.addons.hr_payroll_hibou.tests import common
class TestUsPayslip(common.TransactionCase):
debug = False
_logger = getLogger(__name__)
process_payslip = common.process_payslip
class TestUsPayslip(common.TestPayslip):
def setUp(self):
super(TestUsPayslip, self).setUp()
self.env['ir.config_parameter'].set_param('hr_payroll.payslip.sum_behavior', 'date_to')
self.structure_type_id = self.ref('l10n_us_hr_payroll.structure_type_employee')
self.resource_calendar_id = 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.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'
})
self.structure_type = self.env.ref('l10n_us_hr_payroll.structure_type_employee')
self.structure = self.env.ref('l10n_us_hr_payroll.hr_payroll_structure')
# self.structure_type.default_struct_id = self.structure
self._log('US structue_type %s and structure %s' % (self.structure_type, self.structure))
def _createContract(self, employee, **kwargs):
# Override
if not 'schedule_pay' in kwargs:
kwargs['schedule_pay'] = 'monthly'
schedule_pay = kwargs['schedule_pay']
@@ -90,71 +56,14 @@ class TestUsPayslip(common.TransactionCase):
# US Payroll Config Defaults Should be set on the Model
config = config_model.create(config_values)
contract_values['us_payroll_config_id'] = config.id
# Some Basic Defaults
if not contract_values.get('state'):
contract_values['state'] = 'open' # Running
if not contract_values.get('structure_type_id'):
contract_values['structure_type_id'] = self.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_id
# Compatibility with earlier Odoo versions
if not contract_values.get('journal_id') and hasattr(contract_model, 'journal_id'):
try:
contract_values['journal_id'] = self.env['account.journal'].search([('type', '=', 'general')], limit=1).id
except KeyError:
# Accounting not installed
pass
self._get_contract_defaults(contract_values)
self._log('creating contract with finial values: %s' % (contract_values, ))
contract = contract_model.create(contract_values)
# Compatibility with Odoo 13
# Compatibility with Odoo 13/14
contract.structure_type_id.default_struct_id.schedule_pay = schedule_pay
return contract
def _createPayslip(self, employee, date_from, date_to):
slip = self.env['hr.payslip'].create({
'name': 'Test %s From: %s To: %s' % (employee.name, date_from, date_to),
'employee_id': employee.id,
'date_from': date_from,
'date_to': date_to
})
slip._onchange_employee()
return slip
def _getCategories(self, payslip):
categories = defaultdict(float)
for line in payslip.line_ids:
self._log(' line code: ' + str(line.code) +
' category code: ' + line.category_id.code +
' total: ' + str(line.total) +
' rate: ' + str(line.rate) +
' amount: ' + str(line.amount))
category_id = line.category_id
category_code = line.category_id.code
while category_code:
categories[category_code] += line.total
category_id = category_id.parent_id
category_code = category_id.code
return categories
def _getRules(self, payslip):
rules = defaultdict(float)
for line in payslip.line_ids:
rules[line.code] += line.total
return rules
def assertPayrollEqual(self, first, second):
self.assertAlmostEqual(first, second, self.payroll_digits)
def assertPayrollAlmostEqual(self, first, second):
self.assertAlmostEqual(first, second, self.payroll_digits-1)
def get_us_state(self, code, cache={}):
country_key = 'US_COUNTRY'
if code in cache:

View File

@@ -2,118 +2,4 @@ from .common import TestUsPayslip, process_payslip
class TestSpecial(TestUsPayslip):
def test_semi_monthly(self):
salary = 80000.0
employee = self._createEmployee()
# so the schedule_pay is now on the Structure...
contract = self._createContract(employee, wage=salary, schedule_pay='semi-monthly')
payslip = self._createPayslip(employee, '2019-01-01', '2019-01-14')
payslip.compute_sheet()
def test_payslip_sum_behavior(self):
us_structure = self.env.ref('l10n_us_hr_payroll.hr_payroll_structure')
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': us_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')
payslip.compute_sheet()
cats = self._getCategories(payslip)
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
us_structure = self.env.ref('l10n_us_hr_payroll.hr_payroll_structure')
# 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': us_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
year = payslip.dict.get_year()
result += payslip.sum_category('ALW', str(year) + '-01-01', str(year+1) + '-01-01')
""",
'sequence': 101,
'struct_id': us_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)
pass

View File

@@ -30,7 +30,6 @@ class TestUsPayslip2020(TestUsPayslip):
###
def test_2020_taxes(self):
self.debug = False
# salary is high so that second payslip runs over max
# social security salary
salary = 80000.0
@@ -42,17 +41,16 @@ class TestUsPayslip2020(TestUsPayslip):
self._log('2019 tax last slip')
payslip = self._createPayslip(employee, '2019-12-01', '2019-12-31')
payslip.compute_sheet()
self.assertEqual(payslip.contract_id, contract)
self._log(payslip.read())
process_payslip(payslip)
# Ensure amounts are there, they shouldn't be added in the next year...
cats = self._getCategories(payslip)
self.assertTrue(cats['ER_US_940_FUTA'], self.FUTA_MAX_WAGE * self.FUTA)
self.assertTrue(cats['ER_US_940_FUTA'], ' Value should be well above whatever was available that year!')
self._log('2020 tax first payslip:')
payslip = self._createPayslip(employee, '2020-01-01', '2020-01-31')
payslip.compute_sheet()
cats = self._getCategories(payslip)
rules = self._getRules(payslip)
@@ -74,8 +72,6 @@ class TestUsPayslip2020(TestUsPayslip):
self._log('2020 tax second payslip:')
payslip = self._createPayslip(employee, '2020-02-01', '2020-02-28')
payslip.compute_sheet()
cats = self._getCategories(payslip)
rules = self._getRules(payslip)
@@ -90,8 +86,6 @@ class TestUsPayslip2020(TestUsPayslip):
self._log('2020 tax third payslip:')
payslip = self._createPayslip(employee, '2020-03-01', '2020-03-31')
payslip.compute_sheet()
cats = self._getCategories(payslip)
rules = self._getRules(payslip)
@@ -104,8 +98,6 @@ class TestUsPayslip2020(TestUsPayslip):
self._log('2020 tax fourth payslip:')
payslip = self._createPayslip(employee, '2020-04-01', '2020-04-30')
payslip.compute_sheet()
cats = self._getCategories(payslip)
rules = self._getRules(payslip)

View File

@@ -60,7 +60,7 @@ class TestUsWAPayslip(TestUsPayslip):
self._log('2019 Washington tax first payslip:')
payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31')
hours_in_period = payslip.worked_days_line_ids.filtered(lambda l: l.code == 'WORK100').number_of_hours
self.assertEqual(hours_in_period, 184) # only asserted to test algorithm
self.assertAlmostEqual(hours_in_period, 184) # only asserted to test algorithm
payslip.compute_sheet()

View File

@@ -61,7 +61,7 @@ class TestUsWAPayslip(TestUsPayslip):
self._log('2020 Washington tax first payslip:')
payslip = self._createPayslip(employee, '2020-01-01', '2020-01-31')
hours_in_period = payslip.worked_days_line_ids.filtered(lambda l: l.code == 'WORK100').number_of_hours
self.assertEqual(hours_in_period, 184) # only asserted to test algorithm
self.assertAlmostEqual(hours_in_period, 184) # only asserted to test algorithm
payslip.compute_sheet()
rules = self._getRules(payslip)

View File

@@ -1,32 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="res_config_settings_view_form_inherit" model="ir.ui.view">
<field name="name">res.config.settings.view.form.inherit</field>
<field name="model">res.config.settings</field>
<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">
<div class="row mt16 o_settings_container" id="hr_payroll_accountant">
<div class="col-lg-6 col-12 o_setting_box">
<div class="o_setting_right_pane">
<span class="o_form_label">Payslip Sum Behavior</span>
<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>
<div class="content-group">
<div class="row mt16" id="mail_alias_domain">
<label for="payslip_sum_type" class="col-lg-3 o_light_label"/>
<field name="payslip_sum_type"/>
</div>
</div>
</div>
</div>
</div>
</xpath>
</field>
</record>
</odoo>