mirror of
https://gitlab.com/hibou-io/hibou-odoo/suite.git
synced 2025-01-20 12:37:31 +02:00
[WIP] l10n_pe_hr_payroll: for 13.0
This commit is contained in:
12
l10n_pe_hr_payroll/__init__.py
Normal file
12
l10n_pe_hr_payroll/__init__.py
Normal file
@@ -0,0 +1,12 @@
|
||||
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
|
||||
|
||||
from . import models
|
||||
|
||||
def _post_install_hook(cr, registry):
|
||||
"""
|
||||
This method will set the default for the Payslip Sum Behavior
|
||||
"""
|
||||
cr.execute("SELECT id FROM ir_config_parameter WHERE key = 'hr_payroll.payslip.sum_behavior';")
|
||||
existing = cr.fetchall()
|
||||
if not existing:
|
||||
cr.execute("INSERT INTO ir_config_parameter (key, value) VALUES ('hr_payroll.payslip.sum_behavior', 'date');")
|
||||
32
l10n_pe_hr_payroll/__manifest__.py
Normal file
32
l10n_pe_hr_payroll/__manifest__.py
Normal file
@@ -0,0 +1,32 @@
|
||||
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
|
||||
|
||||
{
|
||||
'name': 'Peru - Payroll',
|
||||
'author': 'Hibou Corp. <hello@hibou.io>',
|
||||
'version': '13.0.2022.0.0',
|
||||
'category': 'Payroll Localization',
|
||||
'depends': [
|
||||
'hr_payroll',
|
||||
'hr_contract_reports',
|
||||
'hibou_professional',
|
||||
],
|
||||
'description': """
|
||||
Peru - Payroll Rules.
|
||||
=====================
|
||||
|
||||
""",
|
||||
|
||||
'data': [
|
||||
'data/base.xml',
|
||||
'data/integration_rules.xml',
|
||||
'data/afp_rules.xml',
|
||||
'data/er_rules.xml',
|
||||
# 'views/hr_contract_views.xml',
|
||||
'views/res_config_settings_views.xml',
|
||||
],
|
||||
'demo': [
|
||||
],
|
||||
'auto_install': False,
|
||||
'post_init_hook': '_post_install_hook',
|
||||
'license': 'OPL-1',
|
||||
}
|
||||
86
l10n_pe_hr_payroll/data/afp_rules.xml
Normal file
86
l10n_pe_hr_payroll/data/afp_rules.xml
Normal file
@@ -0,0 +1,86 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
|
||||
<!-- Parameters -->
|
||||
<record id="rule_parameter_ee_afp_pensiones" model="hr.rule.parameter">
|
||||
<field name="name">EE: AFP Pensiones</field>
|
||||
<field name="code">ee_afp_pensiones</field>
|
||||
<field name="country_id" ref="base.pe"/>
|
||||
</record>
|
||||
<record id="rule_parameter_ee_afp_pensiones_2020" model="hr.rule.parameter.value">
|
||||
<field name="parameter_value">10.0</field>
|
||||
<field name="rule_parameter_id" ref="rule_parameter_ee_afp_pensiones"/>
|
||||
<field name="date_from" eval="datetime(2020, 1, 1).date()"/>
|
||||
</record>
|
||||
|
||||
<record id="rule_parameter_ee_afp_seguro" model="hr.rule.parameter">
|
||||
<field name="name">EE: AFP Seguro</field>
|
||||
<field name="code">ee_afp_seguro</field>
|
||||
<field name="country_id" ref="base.pe"/>
|
||||
</record>
|
||||
<record id="rule_parameter_ee_afp_seguro_2020" model="hr.rule.parameter.value">
|
||||
<field name="parameter_value">1.74</field>
|
||||
<field name="rule_parameter_id" ref="rule_parameter_ee_afp_seguro"/>
|
||||
<field name="date_from" eval="datetime(2020, 1, 1).date()"/>
|
||||
</record>
|
||||
|
||||
<record id="rule_parameter_ee_afp_comision" model="hr.rule.parameter">
|
||||
<field name="name">EE: AFP Comisión</field>
|
||||
<field name="code">ee_afp_comision</field>
|
||||
<field name="country_id" ref="base.pe"/>
|
||||
</record>
|
||||
<record id="rule_parameter_ee_afp_comision_2020" model="hr.rule.parameter.value">
|
||||
<field name="parameter_value">0.18</field>
|
||||
<field name="rule_parameter_id" ref="rule_parameter_ee_afp_comision"/>
|
||||
<field name="date_from" eval="datetime(2020, 1, 1).date()"/>
|
||||
</record>
|
||||
|
||||
<!-- Partner -->
|
||||
<record id="res_partner_afp" model="res.partner">
|
||||
<field name="name">AFP</field>
|
||||
</record>
|
||||
|
||||
<!-- EE Rules -->
|
||||
<record id="hr_payroll_rule_ee_afp_pensiones" model="hr.salary.rule">
|
||||
<field name="sequence" eval="190"/>
|
||||
<field name="struct_id" ref="hr_payroll_structure"/>
|
||||
<field name="category_id" ref="hr_payroll_category_ee_pe_afp"/>
|
||||
<field name="name">EE: PE AFP Pensiones</field>
|
||||
<field name="code">EE_PE_AFP_PENSIONES</field>
|
||||
<field name="condition_select">python</field>
|
||||
<field name="condition_python">result = categories.BASIC</field>
|
||||
<field name="amount_select">code</field>
|
||||
<field name="amount_python_compute">result, result_rate = categories.BASIC, payslip.rule_parameter('ee_afp_pensiones')</field>
|
||||
<field name="partner_id" ref="res_partner_afp"/>
|
||||
<field name="appears_on_payslip" eval="True"/>
|
||||
</record>
|
||||
|
||||
<record id="hr_payroll_rule_ee_afp_seguro" model="hr.salary.rule">
|
||||
<field name="sequence" eval="191"/>
|
||||
<field name="struct_id" ref="hr_payroll_structure"/>
|
||||
<field name="category_id" ref="hr_payroll_category_ee_pe_afp"/>
|
||||
<field name="name">EE: PE AFP Seguro</field>
|
||||
<field name="code">EE_PE_AFP_SEGURO</field>
|
||||
<field name="condition_select">python</field>
|
||||
<field name="condition_python">result = categories.BASIC</field>
|
||||
<field name="amount_select">code</field>
|
||||
<field name="amount_python_compute">result, result_rate = categories.BASIC, payslip.rule_parameter('ee_afp_seguro')</field>
|
||||
<field name="partner_id" ref="res_partner_afp"/>
|
||||
<field name="appears_on_payslip" eval="True"/>
|
||||
</record>
|
||||
|
||||
<record id="hr_payroll_rule_ee_afp_comision" model="hr.salary.rule">
|
||||
<field name="sequence" eval="192"/>
|
||||
<field name="struct_id" ref="hr_payroll_structure"/>
|
||||
<field name="category_id" ref="hr_payroll_category_ee_pe_afp"/>
|
||||
<field name="name">EE: PE AFP Comisión</field>
|
||||
<field name="code">EE_PE_AFP_COMISION</field>
|
||||
<field name="condition_select">python</field>
|
||||
<field name="condition_python">result = categories.BASIC</field>
|
||||
<field name="amount_select">code</field>
|
||||
<field name="amount_python_compute">result, result_rate = categories.BASIC, payslip.rule_parameter('ee_afp_comision')</field>
|
||||
<field name="partner_id" ref="res_partner_afp"/>
|
||||
<field name="appears_on_payslip" eval="True"/>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
32
l10n_pe_hr_payroll/data/base.xml
Normal file
32
l10n_pe_hr_payroll/data/base.xml
Normal file
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
|
||||
<record id="structure_type_employee" model="hr.payroll.structure.type">
|
||||
<field name="name">Peru Employee</field>
|
||||
<field name="default_resource_calendar_id" ref="resource.resource_calendar_std"/>
|
||||
<field name="country_id" ref="base.pe"/>
|
||||
</record>
|
||||
|
||||
<record id="hr_payroll_structure" model="hr.payroll.structure">
|
||||
<field name="name">Peru Employee Standard</field>
|
||||
<field name="country_id" ref="base.pe"/>
|
||||
<field name="type_id" ref="l10n_pe_hr_payroll.structure_type_employee"/>
|
||||
<field name="regular_pay" eval="True"/>
|
||||
<field name="unpaid_work_entry_type_ids" eval="[
|
||||
(4, ref('hr_payroll.work_entry_type_unpaid_leave')),
|
||||
]"/>
|
||||
</record>
|
||||
|
||||
<!-- AFP -->
|
||||
<record id="hr_payroll_category_ee_pe_afp" model="hr.salary.rule.category">
|
||||
<field name="name">EE: AFP</field>
|
||||
<field name="code">EE_PE_AFP</field>
|
||||
<field name="parent_id" ref="hr_payroll.DED"/>
|
||||
</record>
|
||||
<record id="hr_payroll_category_er_pe_afp" model="hr.salary.rule.category">
|
||||
<field name="name">ER: AFP</field>
|
||||
<field name="code">ER_PE_AFP</field>
|
||||
<field name="parent_id" ref="hr_payroll.COMP"/>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
36
l10n_pe_hr_payroll/data/er_rules.xml
Normal file
36
l10n_pe_hr_payroll/data/er_rules.xml
Normal file
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
|
||||
<!-- Parameters -->
|
||||
<record id="rule_parameter_er_essalud" model="hr.rule.parameter">
|
||||
<field name="name">ER: Essalud</field>
|
||||
<field name="code">er_essalud</field>
|
||||
<field name="country_id" ref="base.pe"/>
|
||||
</record>
|
||||
<record id="rule_parameter_er_essalud_2020" model="hr.rule.parameter.value">
|
||||
<field name="parameter_value">6.75</field>
|
||||
<field name="rule_parameter_id" ref="rule_parameter_er_essalud"/>
|
||||
<field name="date_from" eval="datetime(2020, 1, 1).date()"/>
|
||||
</record>
|
||||
|
||||
<!-- Partner -->
|
||||
<record id="res_partner_essalud" model="res.partner">
|
||||
<field name="name">Essalud</field>
|
||||
</record>
|
||||
|
||||
<!-- ER Rules -->
|
||||
<record id="hr_payroll_rule_er_essalud" model="hr.salary.rule">
|
||||
<field name="sequence" eval="220"/>
|
||||
<field name="struct_id" ref="hr_payroll_structure"/>
|
||||
<field name="category_id" ref="hr_payroll_category_ee_pe_afp"/>
|
||||
<field name="name">ER: PE Essalud</field>
|
||||
<field name="code">ER_PE_ESSALUD</field>
|
||||
<field name="condition_select">python</field>
|
||||
<field name="condition_python">result = categories.BASIC</field>
|
||||
<field name="amount_select">code</field>
|
||||
<field name="amount_python_compute">result, result_rate = categories.BASIC, payslip.rule_parameter('er_essalud')</field>
|
||||
<field name="partner_id" ref="res_partner_afp"/>
|
||||
<field name="appears_on_payslip" eval="True"/>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
29
l10n_pe_hr_payroll/data/integration_rules.xml
Normal file
29
l10n_pe_hr_payroll/data/integration_rules.xml
Normal file
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
<!-- Commissions from hr_payroll_commission -->
|
||||
<record id="hr_salary_rule_commission" model="hr.salary.rule">
|
||||
<field name="condition_select">python</field>
|
||||
<field name="condition_python">result = inputs.COMMISSION.amount > 0.0 if inputs.COMMISSION else False</field>
|
||||
<field name="amount_select">code</field>
|
||||
<field name="amount_python_compute">result = inputs.COMMISSION.amount if inputs.COMMISSION else 0</field>
|
||||
<field name="code">BASIC_COM</field>
|
||||
<field name="category_id" ref="hr_payroll.BASIC"/>
|
||||
<field name="name">Commissions</field>
|
||||
<field name="sequence" eval="20"/>
|
||||
<field name="struct_id" ref="l10n_pe_hr_payroll.hr_payroll_structure"/>
|
||||
</record>
|
||||
|
||||
<!-- Badges from hr_payroll_gamification -->
|
||||
<record id="hr_salary_rule_gamification" model="hr.salary.rule">
|
||||
<field name="condition_select">python</field>
|
||||
<field name="condition_python">result = inputs.BADGES.amount > 0.0 if inputs.BADGES else False</field>
|
||||
<field name="amount_select">code</field>
|
||||
<field name="amount_python_compute">result = inputs.BADGES.amount if inputs.BADGES else 0</field>
|
||||
<field name="code">BASIC_BADGES</field>
|
||||
<field name="category_id" ref="hr_payroll.BASIC"/>
|
||||
<field name="name">Badges</field>
|
||||
<field name="sequence" eval="20"/>
|
||||
<field name="struct_id" ref="l10n_pe_hr_payroll.hr_payroll_structure"/>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
7
l10n_pe_hr_payroll/models/__init__.py
Normal file
7
l10n_pe_hr_payroll/models/__init__.py
Normal file
@@ -0,0 +1,7 @@
|
||||
# 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
|
||||
148
l10n_pe_hr_payroll/models/browsable_object.py
Normal file
148
l10n_pe_hr_payroll/models/browsable_object.py
Normal file
@@ -0,0 +1,148 @@
|
||||
# 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
|
||||
33
l10n_pe_hr_payroll/models/hr_contract.py
Normal file
33
l10n_pe_hr_payroll/models/hr_contract.py
Normal file
@@ -0,0 +1,33 @@
|
||||
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models
|
||||
# from .us_payroll_config import FUTA_TYPE_NORMAL, \
|
||||
# FUTA_TYPE_BASIC, \
|
||||
# FUTA_TYPE_EXEMPT
|
||||
|
||||
|
||||
# class 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 PEHRContract(models.Model):
|
||||
_inherit = 'hr.contract'
|
||||
|
||||
# FUTA_TYPE_NORMAL = FUTA_TYPE_NORMAL
|
||||
# FUTA_TYPE_BASIC = FUTA_TYPE_BASIC
|
||||
# FUTA_TYPE_EXEMPT = FUTA_TYPE_EXEMPT
|
||||
|
||||
# us_payroll_config_id = fields.Many2one('hr.contract.us_payroll_config', 'Payroll Forms')
|
||||
# external_wages = fields.Float(string='External Existing Wages')
|
||||
|
||||
# Simplified fields for easier rules, state code will exempt based on contract's futa_type
|
||||
# futa_type = fields.Selection(related='us_payroll_config_id.fed_940_type')
|
||||
|
||||
# def us_payroll_config_value(self, name):
|
||||
# return self.us_payroll_config_id[name]
|
||||
134
l10n_pe_hr_payroll/models/hr_payslip.py
Normal file
134
l10n_pe_hr_payroll/models/hr_payslip.py
Normal file
@@ -0,0 +1,134 @@
|
||||
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models
|
||||
|
||||
# from .federal.fed_940 import er_us_940_futa
|
||||
# from .federal.fed_941 import ee_us_941_fica_ss, \
|
||||
# ee_us_941_fica_m, \
|
||||
# ee_us_941_fica_m_add,\
|
||||
# er_us_941_fica_ss, \
|
||||
# er_us_941_fica_m, \
|
||||
# ee_us_941_fit
|
||||
# from .state.general import general_state_unemployment, \
|
||||
# general_state_income_withholding, \
|
||||
# is_us_state
|
||||
# from .state.al_alabama import al_alabama_state_income_withholding
|
||||
# from .state.ar_arkansas import ar_arkansas_state_income_withholding
|
||||
# from .state.az_arizona import az_arizona_state_income_withholding
|
||||
# from .state.ca_california import ca_california_state_income_withholding
|
||||
# from .state.co_colorado import co_colorado_state_income_withholding
|
||||
# from .state.ct_connecticut import ct_connecticut_state_income_withholding
|
||||
# from .state.de_delaware import de_delaware_state_income_withholding
|
||||
# from .state.ga_georgia import ga_georgia_state_income_withholding
|
||||
# from .state.hi_hawaii import hi_hawaii_state_income_withholding
|
||||
# from .state.ia_iowa import ia_iowa_state_income_withholding
|
||||
# from .state.id_idaho import id_idaho_state_income_withholding
|
||||
# from .state.il_illinois import il_illinois_state_income_withholding
|
||||
# from .state.in_indiana import in_indiana_state_income_withholding
|
||||
# from .state.ks_kansas import ks_kansas_state_income_withholding
|
||||
# from .state.ky_kentucky import ky_kentucky_state_income_withholding
|
||||
# from .state.la_louisiana import la_louisiana_state_income_withholding
|
||||
# from .state.me_maine import me_maine_state_income_withholding
|
||||
# from .state.mi_michigan import mi_michigan_state_income_withholding
|
||||
# from .state.mn_minnesota import mn_minnesota_state_income_withholding
|
||||
# from .state.mo_missouri import mo_missouri_state_income_withholding
|
||||
# from .state.ms_mississippi import ms_mississippi_state_income_withholding
|
||||
# from .state.mt_montana import mt_montana_state_income_withholding
|
||||
# from .state.nc_northcarolina import nc_northcarolina_state_income_withholding
|
||||
# from .state.nd_north_dakota import nd_north_dakota_state_income_withholding
|
||||
# from .state.ne_nebraska import ne_nebraska_state_income_withholding
|
||||
# from .state.nj_newjersey import nj_newjersey_state_income_withholding
|
||||
# from .state.nm_new_mexico import nm_new_mexico_state_income_withholding
|
||||
# from .state.ny_new_york import ny_new_york_state_income_withholding
|
||||
# from .state.oh_ohio import oh_ohio_state_income_withholding
|
||||
# from .state.ok_oklahoma import ok_oklahoma_state_income_withholding
|
||||
# from .state.ri_rhode_island import ri_rhode_island_state_income_withholding
|
||||
# from .state.sc_south_carolina import sc_south_carolina_state_income_withholding
|
||||
# from .state.ut_utah import ut_utah_state_income_withholding
|
||||
# from .state.vt_vermont import vt_vermont_state_income_withholding
|
||||
# from .state.va_virginia import va_virginia_state_income_withholding
|
||||
# from .state.wa_washington import wa_washington_fml_er, \
|
||||
# wa_washington_fml_ee, \
|
||||
# wa_washington_cares_ee
|
||||
# from .state.wi_wisconsin import wi_wisconsin_state_income_withholding
|
||||
# from .state.wv_west_virginia import wv_west_virginia_state_income_withholding
|
||||
|
||||
|
||||
class HRPayslip(models.Model):
|
||||
_inherit = 'hr.payslip'
|
||||
|
||||
# From IRS Publication 15-T or logically (annually, bi-monthly)
|
||||
PAY_PERIODS_IN_YEAR = {
|
||||
'annually': 1,
|
||||
'semi-annually': 2,
|
||||
'quarterly': 4,
|
||||
'bi-monthly': 6,
|
||||
'monthly': 12,
|
||||
'semi-monthly': 24,
|
||||
'bi-weekly': 26,
|
||||
'weekly': 52,
|
||||
'daily': 260,
|
||||
}
|
||||
|
||||
def _get_base_local_dict(self):
|
||||
res = super()._get_base_local_dict()
|
||||
res.update({
|
||||
# 'er_us_940_futa': er_us_940_futa,
|
||||
# 'ee_us_941_fica_ss': ee_us_941_fica_ss,
|
||||
# 'ee_us_941_fica_m': ee_us_941_fica_m,
|
||||
# 'ee_us_941_fica_m_add': ee_us_941_fica_m_add,
|
||||
# 'er_us_941_fica_ss': er_us_941_fica_ss,
|
||||
# 'er_us_941_fica_m': er_us_941_fica_m,
|
||||
# 'ee_us_941_fit': ee_us_941_fit,
|
||||
# 'general_state_unemployment': general_state_unemployment,
|
||||
# 'general_state_income_withholding': general_state_income_withholding,
|
||||
# 'is_us_state': is_us_state,
|
||||
# 'al_alabama_state_income_withholding': al_alabama_state_income_withholding,
|
||||
# 'ar_arkansas_state_income_withholding': ar_arkansas_state_income_withholding,
|
||||
# 'az_arizona_state_income_withholding': az_arizona_state_income_withholding,
|
||||
# 'ca_california_state_income_withholding': ca_california_state_income_withholding,
|
||||
# 'co_colorado_state_income_withholding': co_colorado_state_income_withholding,
|
||||
# 'ct_connecticut_state_income_withholding': ct_connecticut_state_income_withholding,
|
||||
# 'de_delaware_state_income_withholding': de_delaware_state_income_withholding,
|
||||
# 'ga_georgia_state_income_withholding': ga_georgia_state_income_withholding,
|
||||
# 'hi_hawaii_state_income_withholding': hi_hawaii_state_income_withholding,
|
||||
# 'ia_iowa_state_income_withholding': ia_iowa_state_income_withholding,
|
||||
# 'id_idaho_state_income_withholding': id_idaho_state_income_withholding,
|
||||
# 'il_illinois_state_income_withholding': il_illinois_state_income_withholding,
|
||||
# 'in_indiana_state_income_withholding': in_indiana_state_income_withholding,
|
||||
# 'ks_kansas_state_income_withholding': ks_kansas_state_income_withholding,
|
||||
# 'ky_kentucky_state_income_withholding':ky_kentucky_state_income_withholding,
|
||||
# 'la_louisiana_state_income_withholding': la_louisiana_state_income_withholding,
|
||||
# 'me_maine_state_income_withholding': me_maine_state_income_withholding,
|
||||
# 'mi_michigan_state_income_withholding': mi_michigan_state_income_withholding,
|
||||
# 'mn_minnesota_state_income_withholding': mn_minnesota_state_income_withholding,
|
||||
# 'mo_missouri_state_income_withholding': mo_missouri_state_income_withholding,
|
||||
# 'ms_mississippi_state_income_withholding': ms_mississippi_state_income_withholding,
|
||||
# 'mt_montana_state_income_withholding': mt_montana_state_income_withholding,
|
||||
# 'nc_northcarolina_state_income_withholding': nc_northcarolina_state_income_withholding,
|
||||
# 'nd_north_dakota_state_income_withholding': nd_north_dakota_state_income_withholding,
|
||||
# 'ne_nebraska_state_income_withholding': ne_nebraska_state_income_withholding,
|
||||
# 'nj_newjersey_state_income_withholding': nj_newjersey_state_income_withholding,
|
||||
# 'nm_new_mexico_state_income_withholding': nm_new_mexico_state_income_withholding,
|
||||
# 'ny_new_york_state_income_withholding': ny_new_york_state_income_withholding,
|
||||
# 'oh_ohio_state_income_withholding': oh_ohio_state_income_withholding,
|
||||
# 'ok_oklahoma_state_income_withholding': ok_oklahoma_state_income_withholding,
|
||||
# 'ri_rhode_island_state_income_withholding': ri_rhode_island_state_income_withholding,
|
||||
# 'sc_south_carolina_state_income_withholding': sc_south_carolina_state_income_withholding,
|
||||
# 'ut_utah_state_income_withholding': ut_utah_state_income_withholding,
|
||||
# 'vt_vermont_state_income_withholding': vt_vermont_state_income_withholding,
|
||||
# 'va_virginia_state_income_withholding': va_virginia_state_income_withholding,
|
||||
# 'wa_washington_fml_er': wa_washington_fml_er,
|
||||
# 'wa_washington_fml_ee': wa_washington_fml_ee,
|
||||
# 'wa_washington_cares_ee': wa_washington_cares_ee,
|
||||
# 'wi_wisconsin_state_income_withholding': wi_wisconsin_state_income_withholding,
|
||||
# 'wv_west_virginia_state_income_withholding': wv_west_virginia_state_income_withholding,
|
||||
})
|
||||
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)
|
||||
24
l10n_pe_hr_payroll/models/res_config_settings.py
Normal file
24
l10n_pe_hr_payroll/models/res_config_settings.py
Normal file
@@ -0,0 +1,24 @@
|
||||
# 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')
|
||||
32
l10n_pe_hr_payroll/models/update.py
Normal file
32
l10n_pe_hr_payroll/models/update.py
Normal file
@@ -0,0 +1,32 @@
|
||||
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
|
||||
|
||||
import datetime
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class PublisherWarrantyContract(models.AbstractModel):
|
||||
_inherit = 'publisher_warranty.contract'
|
||||
|
||||
def _get_hibou_modules(self):
|
||||
modules = super(PublisherWarrantyContract, self)._get_hibou_modules()
|
||||
try:
|
||||
today_date = fields.Date.today()
|
||||
last_thirty_date = today_date - datetime.timedelta(days=30)
|
||||
today = fields.Date.to_string(today_date + datetime.timedelta(days=1)) # Dates vs Datetimes, pad out a day
|
||||
last_thirty = fields.Date.to_string(last_thirty_date)
|
||||
self.env.cr.execute(
|
||||
'SELECT COUNT(DISTINCT(employee_id)) FROM hr_payslip WHERE create_date BETWEEN %s AND %s',
|
||||
(last_thirty, today))
|
||||
employee_count = self.env.cr.fetchone()[0] or 0
|
||||
modules.update({
|
||||
'l10n_pe_hr_payroll': employee_count,
|
||||
})
|
||||
except:
|
||||
pass
|
||||
return modules
|
||||
|
||||
@api.model
|
||||
def hibou_payroll_modules_to_update(self):
|
||||
res = super().hibou_payroll_modules_to_update()
|
||||
res.append('l10n_pe_hr_payroll')
|
||||
return res
|
||||
BIN
l10n_pe_hr_payroll/static/description/icon.png
Normal file
BIN
l10n_pe_hr_payroll/static/description/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.0 KiB |
8
l10n_pe_hr_payroll/tests/__init__.py
Normal file
8
l10n_pe_hr_payroll/tests/__init__.py
Normal file
@@ -0,0 +1,8 @@
|
||||
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
|
||||
|
||||
# TODO make/move to l10n_pe_hr_payroll_params
|
||||
# Tests moved to `l10n_us_hr_payroll_params`
|
||||
# common remains for site specific tests
|
||||
|
||||
from . import common
|
||||
from . import test_2022
|
||||
143
l10n_pe_hr_payroll/tests/common.py
Executable file
143
l10n_pe_hr_payroll/tests/common.py
Executable file
@@ -0,0 +1,143 @@
|
||||
# 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
|
||||
|
||||
|
||||
def process_payslip(payslip):
|
||||
try:
|
||||
payslip.action_payslip_done()
|
||||
except AttributeError:
|
||||
# v9
|
||||
payslip.process_sheet()
|
||||
|
||||
|
||||
class TestPePayslip(common.TransactionCase):
|
||||
debug = False
|
||||
_logger = getLogger(__name__)
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
# TODO Question, is this the correct summing behavior for Peru?
|
||||
self.env['ir.config_parameter'].set_param('hr_payroll.payslip.sum_behavior', 'date_to')
|
||||
self.structure_type_id = self.ref('l10n_pe_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.pe'),
|
||||
'department_id': self.ref('hr.dep_rd'),
|
||||
'gender': 'male',
|
||||
'name': 'Jared'
|
||||
})
|
||||
|
||||
def _createContract(self, employee, **kwargs):
|
||||
if not 'schedule_pay' in kwargs:
|
||||
kwargs['schedule_pay'] = 'monthly'
|
||||
schedule_pay = kwargs['schedule_pay']
|
||||
contract_model = self.env['hr.contract']
|
||||
contract_values = {
|
||||
'name': 'Test Contract',
|
||||
'employee_id': employee.id,
|
||||
}
|
||||
|
||||
for key, val in kwargs.items():
|
||||
# Assume any Odoo object is in a Many2one
|
||||
if hasattr(val, 'id'):
|
||||
val = val.id
|
||||
found = False
|
||||
if hasattr(contract_model, key):
|
||||
contract_values[key] = val
|
||||
found = True
|
||||
if not found:
|
||||
self._logger.warn('cannot locate attribute names "%s" on contract' % (key, ))
|
||||
|
||||
|
||||
# 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
|
||||
|
||||
contract = contract_model.create(contract_values)
|
||||
|
||||
# Compatibility with Odoo 13
|
||||
contract.structure_type_id.default_struct_id.schedule_pay = schedule_pay
|
||||
return contract
|
||||
|
||||
def _createPayslip(self, employee, date_from, date_to):
|
||||
slip = self.env['hr.payslip'].create({
|
||||
'name': 'Test %s From: %s To: %s' % (employee.name, date_from, date_to),
|
||||
'employee_id': employee.id,
|
||||
'date_from': date_from,
|
||||
'date_to': date_to
|
||||
})
|
||||
slip._onchange_employee()
|
||||
slip._onchange_worked_days_inputs()
|
||||
self.assertTrue(slip.contract_id)
|
||||
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)
|
||||
60
l10n_pe_hr_payroll/tests/test_2022.py
Normal file
60
l10n_pe_hr_payroll/tests/test_2022.py
Normal file
@@ -0,0 +1,60 @@
|
||||
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
|
||||
|
||||
from .common import TestPePayslip, process_payslip
|
||||
|
||||
|
||||
class Test2022(TestPePayslip):
|
||||
|
||||
# AFP Constants
|
||||
AFP_PENSIONES = 0.1 # 10%
|
||||
AFP_SEGURO = 0.0174 # 1.74%
|
||||
AFP_COMISION = 0.0018 # 0.18%
|
||||
|
||||
# ER ESSALUD
|
||||
ER_ESSALUD = 0.0675 # 6.75%
|
||||
|
||||
# # FUTA Constants
|
||||
# FUTA_RATE_NORMAL = 0.6
|
||||
# FUTA_RATE_BASIC = 6.0
|
||||
# FUTA_RATE_EXEMPT = 0.0
|
||||
|
||||
# # Wage caps
|
||||
# FICA_SS_MAX_WAGE = 147000.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
|
||||
|
||||
###
|
||||
# 2022 Taxes and Rates
|
||||
###
|
||||
|
||||
def test_2022_taxes(self):
|
||||
self.debug = True
|
||||
salary = 3290.0
|
||||
|
||||
employee = self._createEmployee()
|
||||
|
||||
contract = self._createContract(employee, wage=salary)
|
||||
self._log(contract.read())
|
||||
|
||||
self._log('2022 tax first payslip:')
|
||||
payslip = self._createPayslip(employee, '2022-01-01', '2022-01-31')
|
||||
payslip.compute_sheet()
|
||||
|
||||
cats = self._getCategories(payslip)
|
||||
rules = self._getRules(payslip)
|
||||
# Employee
|
||||
self.assertPayrollEqual(cats['BASIC'], salary)
|
||||
self.assertPayrollEqual(rules['EE_PE_AFP_PENSIONES'], cats['BASIC'] * self.AFP_PENSIONES)
|
||||
self.assertPayrollEqual(rules['EE_PE_AFP_SEGURO'], cats['BASIC'] * self.AFP_SEGURO)
|
||||
self.assertPayrollEqual(rules['EE_PE_AFP_COMISION'], cats['BASIC'] * self.AFP_COMISION)
|
||||
# Employer
|
||||
self.assertPayrollEqual(rules['ER_PE_ESSALUD'], cats['BASIC'] * self.ER_ESSALUD)
|
||||
|
||||
process_payslip(payslip)
|
||||
19
l10n_pe_hr_payroll/views/hr_contract_views.xml
Normal file
19
l10n_pe_hr_payroll/views/hr_contract_views.xml
Normal file
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
|
||||
<record id="hr_contract_view_form_inherit_l10n_us" model="ir.ui.view">
|
||||
<field name="name">hr.contract.form.inherit</field>
|
||||
<field name="model">hr.contract</field>
|
||||
<field name="inherit_id" ref="hr_payroll.hr_contract_form_inherit"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='structure_type_id']" position="after">
|
||||
<!-- <field name="us_payroll_config_id"
|
||||
domain="[('employee_id', '=', employee_id)]"
|
||||
attrs="{'invisible': [('structure_type_id', '!=', %(l10n_us_hr_payroll.structure_type_employee)s)],
|
||||
'required': [('structure_type_id', '=', %(l10n_us_hr_payroll.structure_type_employee)s)]}"
|
||||
context="{'default_employee_id': employee_id}"/> -->
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
32
l10n_pe_hr_payroll/views/res_config_settings_views.xml
Normal file
32
l10n_pe_hr_payroll/views/res_config_settings_views.xml
Normal file
@@ -0,0 +1,32 @@
|
||||
<?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>
|
||||
Reference in New Issue
Block a user