[IMP] l10n_pe_hr_payroll: retirement type and other contract fields

This commit is contained in:
Jared Kipe
2022-04-22 21:45:25 +00:00
parent 6f7bbabfe1
commit cabadf0c42
15 changed files with 524 additions and 191 deletions

View File

@@ -6,7 +6,7 @@
'version': '13.0.2022.0.0',
'category': 'Payroll Localization',
'depends': [
'hr_payroll',
'hr_payroll_hibou',
'hr_contract_reports',
'hibou_professional',
],
@@ -17,11 +17,15 @@ Peru - Payroll Rules.
""",
'data': [
'security/ir.model.access.csv',
'data/base.xml',
'data/integration_rules.xml',
'data/afp_rules.xml',
'data/onp_rules.xml',
'data/ir_5ta_cat_rules.xml',
'data/er_rules.xml',
# 'views/hr_contract_views.xml',
'views/hr_contract_views.xml',
'views/pe_payroll_config_views.xml',
'views/res_config_settings_views.xml',
],
'demo': [

View File

@@ -2,36 +2,20 @@
<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>
<record id="rule_parameter_ee_afp" model="hr.rule.parameter">
<field name="name">EE: AFP</field>
<field name="code">ee_afp</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"/>
<record id="rule_parameter_ee_afp_2020" model="hr.rule.parameter.value">
<field name="parameter_value">{
# non-mixta, mixta_monthly, mixta_annually, iss, ley, maximum (iss wage base)
'habitat': (1.47, 0.38, 1.25, 1.35, 10.0, 9707.03),
'integra': (1.55, 0.00, 1.82, 1.35, 10.0, 9707.03),
'prima': (1.60, 0.18, 1.25, 1.35, 10.0, 9707.03),
'profuturo': (1.69, 0.67, 1.20, 1.35, 10.0, 9707.03),
}</field>
<field name="rule_parameter_id" ref="rule_parameter_ee_afp"/>
<field name="date_from" eval="datetime(2020, 1, 1).date()"/>
</record>
@@ -48,9 +32,9 @@
<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="condition_python">result = categories.GROSS and contract.pe_payroll_config_value('retirement_type') == 'afp'</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="amount_python_compute">result, result_rate = categories.GROSS, -payslip.rule_parameter('ee_afp')[contract.pe_payroll_config_value('afp_type')][4]</field>
<field name="partner_id" ref="res_partner_afp"/>
<field name="appears_on_payslip" eval="True"/>
</record>
@@ -62,23 +46,44 @@
<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="condition_python">result = categories.GROSS and contract.pe_payroll_config_value('retirement_type') == 'afp'</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="amount_python_compute">
year = payslip.dict.get_year()
ytd_wage = payslip.sum_category('GROSS', str(year) + '-01-01', str(year+1) + '-01-01')
wage = categories.GROSS
wage_base = payslip.rule_parameter('ee_afp')[contract.pe_payroll_config_value('afp_type')][5]
rate = payslip.rule_parameter('ee_afp')[contract.pe_payroll_config_value('afp_type')][3]
result, result_rate = general_rate(payslip, wage, ytd_wage, wage_base=wage_base, rate=rate)
</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">
<record id="hr_payroll_rule_ee_afp_comision_mixta" 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="name">EE: PE AFP Comisión Mixta</field>
<field name="code">EE_PE_AFP_COMISION_MIXTA</field>
<field name="condition_select">python</field>
<field name="condition_python">result = categories.BASIC</field>
<field name="condition_python">result = categories.GROSS and contract.pe_payroll_config_value('retirement_type') == 'afp' and contract.pe_payroll_config_value('afp_comision_type') == 'mixta'</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="amount_python_compute">result, result_rate = categories.GROSS, -payslip.rule_parameter('ee_afp')[contract.pe_payroll_config_value('afp_type')][1]</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_non_mixta" 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 (Non-Mixta)</field>
<field name="code">EE_PE_AFP_COMISION_NON_MIXTA</field>
<field name="condition_select">python</field>
<field name="condition_python">result = categories.GROSS and contract.pe_payroll_config_value('retirement_type') == 'afp' and contract.pe_payroll_config_value('afp_comision_type') == 'non_mixta'</field>
<field name="amount_select">code</field>
<field name="amount_python_compute">result, result_rate = categories.GROSS, -payslip.rule_parameter('ee_afp')[contract.pe_payroll_config_value('afp_type')][0]</field>
<field name="partner_id" ref="res_partner_afp"/>
<field name="appears_on_payslip" eval="True"/>
</record>

View File

@@ -29,4 +29,28 @@
<field name="parent_id" ref="hr_payroll.COMP"/>
</record>
<!-- ONP -->
<record id="hr_payroll_category_ee_pe_onp" model="hr.salary.rule.category">
<field name="name">EE: ONP</field>
<field name="code">EE_PE_ONP</field>
<field name="parent_id" ref="hr_payroll.DED"/>
</record>
<record id="hr_payroll_category_er_pe_onp" model="hr.salary.rule.category">
<field name="name">ER: ONP</field>
<field name="code">ER_PE_ONP</field>
<field name="parent_id" ref="hr_payroll.COMP"/>
</record>
<!-- IR 5TA CAT -->
<record id="hr_payroll_category_ee_ir_5ta_cat" model="hr.salary.rule.category">
<field name="name">EE: IR 5ta Cat.</field>
<field name="code">EE_PE_IR_5TA_CAT</field>
<field name="parent_id" ref="hr_payroll.DED"/>
</record>
<record id="hr_payroll_category_er_ir_5ta_cat" model="hr.salary.rule.category">
<field name="name">ER: IR 5ta Cat.</field>
<field name="code">ER_PE_IR_5TA_CAT</field>
<field name="parent_id" ref="hr_payroll.COMP"/>
</record>
</odoo>

View File

@@ -0,0 +1,80 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<!-- Parameters -->
<record id="rule_parameter_pe_uit" model="hr.rule.parameter">
<field name="name">Peru UIT</field>
<field name="code">pe_uit</field>
<field name="country_id" ref="base.pe"/>
</record>
<record id="rule_parameter_pe_uit_2020" model="hr.rule.parameter.value">
<field name="parameter_value">4400.0</field>
<field name="rule_parameter_id" ref="rule_parameter_pe_uit"/>
<field name="date_from" eval="datetime(2020, 1, 1).date()"/>
</record>
<record id="rule_parameter_ee_ir_5ta_cat" model="hr.rule.parameter">
<field name="name">EE: IR 5ta Cat.</field>
<field name="code">ee_ir_5ta_cat</field>
<field name="country_id" ref="base.pe"/>
</record>
<!-- 2015 chart -->
<record id="rule_parameter_ee_ir_5ta_cat_2020" model="hr.rule.parameter.value">
<field name="parameter_value">[
( 5.0, 8.0),
( 20.0, 14.0),
( 35.0, 17.0),
( 45.0, 20.0),
('inf', 30.0),
]</field>
<field name="rule_parameter_id" ref="rule_parameter_ee_ir_5ta_cat"/>
<field name="date_from" eval="datetime(2020, 1, 1).date()"/>
</record>
<!-- Partner -->
<record id="res_partner_ir_5ta_cat" model="res.partner">
<field name="name">IR 5ta Cat.</field>
</record>
<!-- EE Rules -->
<record id="hr_payroll_rule_ee_ir_5ta_cat" model="hr.salary.rule">
<field name="sequence" eval="196"/>
<field name="struct_id" ref="hr_payroll_structure"/>
<field name="category_id" ref="hr_payroll_category_ee_ir_5ta_cat"/>
<field name="name">EE: PE IR 5TA Cat.</field>
<field name="code">EE_PE_IR_5TA_CAT</field>
<field name="condition_select">python</field>
<field name="condition_python">result = categories.GROSS</field>
<field name="amount_select">code</field>
<field name="amount_python_compute">
# TODO normalize anual wage based on pay period
uit = payslip.rule_parameter('pe_uit')
wage = categories.GROSS
wage_year = wage * 12.0
# additional 2 months
wage_year += wage * 2.0
over_7uit = wage_year - (7.0 * uit)
if over_7uit &lt;= 0.0:
result = 0.0
else:
total_tax = 0.0
last_uit = 0.0
for _uit, rate in payslip.rule_parameter('ee_ir_5ta_cat'):
# marginal brackets
_uit = float(_uit)
if over_7uit &gt; (last_uit * uit):
eligible_wage = min(over_7uit, _uit * uit) - (last_uit * uit)
if eligible_wage &gt; 0.0:
total_tax += eligible_wage * (rate / 100.0)
else:
break
else:
break
last_uit = _uit
tax = -total_tax / 12.0
result, result_rate = wage, (tax / wage * 100.0)</field>
<field name="partner_id" ref="res_partner_ir_5ta_cat"/>
<field name="appears_on_payslip" eval="True"/>
</record>
</odoo>

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<!-- Parameters -->
<record id="rule_parameter_ee_onp" model="hr.rule.parameter">
<field name="name">EE: ONP</field>
<field name="code">ee_onp</field>
<field name="country_id" ref="base.pe"/>
</record>
<record id="rule_parameter_ee_onp_2020" model="hr.rule.parameter.value">
<field name="parameter_value">13.0</field>
<field name="rule_parameter_id" ref="rule_parameter_ee_onp"/>
<field name="date_from" eval="datetime(2020, 1, 1).date()"/>
</record>
<!-- Partner -->
<record id="res_partner_onp" model="res.partner">
<field name="name">ONP</field>
</record>
<!-- EE Rules -->
<record id="hr_payroll_rule_ee_onp" 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_onp"/>
<field name="name">EE: PE ONP/SNP</field>
<field name="code">EE_PE_ONP</field>
<field name="condition_select">python</field>
<field name="condition_python">result = categories.GROSS and contract.pe_payroll_config_value('retirement_type') == 'onp' and rule == contract.pe_payroll_config_value('onp_rule_id')</field>
<field name="amount_select">code</field>
<field name="amount_python_compute">result, result_rate = categories.GROSS, -payslip.rule_parameter('ee_onp')</field>
<field name="partner_id" ref="res_partner_onp"/>
<field name="appears_on_payslip" eval="True"/>
</record>
</odoo>

View File

@@ -3,5 +3,6 @@
from . import browsable_object
from . import hr_contract
from . import hr_payslip
from . import pe_payroll_config
from . import res_config_settings
from . import update

View File

@@ -1,33 +1,12 @@
# 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
pe_payroll_config_id = fields.Many2one('hr.contract.pe_payroll_config', 'Payroll Forms')
# 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]
def pe_payroll_config_value(self, name):
return self.pe_payroll_config_id[name]

View File

@@ -2,133 +2,71 @@
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
def _general_rate(payslip, wage, ytd_wage, wage_base=None, wage_start=None, rate=None):
"""
Function parameters:
wage_base, wage_start, rate can either be strings (rule_parameters) or floats
:return: result, result_rate(wage, percent)
"""
# Resolve parameters. On exception, return (probably missing a year, would rather not have exception)
if wage_base and isinstance(wage_base, str):
try:
wage_base = payslip.rule_parameter(wage_base)
except (KeyError, UserError):
return 0.0, 0.0
if wage_start and isinstance(wage_start, str):
try:
wage_start = payslip.rule_parameter(wage_start)
except (KeyError, UserError):
return 0.0, 0.0
if rate and isinstance(rate, str):
try:
rate = payslip.rule_parameter(rate)
except (KeyError, UserError):
return 0.0, 0.0
if not rate:
return 0.0, 0.0
else:
# Rate assumed positive percentage!
rate = -rate
if wage_base:
remaining = wage_base - ytd_wage
if remaining < 0.0:
result = 0.0
elif remaining < wage:
result = remaining
else:
result = wage
# _logger.warn(' wage_base method result: ' + str(result) + ' rate: ' + str(rate))
return result, rate
if wage_start:
if ytd_wage >= wage_start:
# _logger.warn(' wage_start 1 method result: ' + str(wage) + ' rate: ' + str(rate))
return wage, rate
if ytd_wage + wage <= wage_start:
# _logger.warn(' wage_start 2 method result: ' + str(0.0) + ' rate: ' + str(0.0))
return 0.0, 0.0
# _logger.warn(' wage_start 3 method result: ' + str((wage - (wage_start - ytd_wage))) + ' rate: ' + str(rate))
return (wage - (wage_start - ytd_wage)), rate
# If the wage doesn't have a start or a base
# _logger.warn(' basic result: ' + str(wage) + ' rate: ' + str(rate))
return wage, rate
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,
'general_rate': _general_rate,
})
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

@@ -0,0 +1,39 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
from odoo import api, fields, models
class HRContractPEPayrollConfig(models.Model):
_name = 'hr.contract.pe_payroll_config'
_description = 'Contract PE Payroll Forms'
name = fields.Char(string="Description")
employee_id = fields.Many2one('hr.employee', string="Employee", required=True)
retirement_type = fields.Selection([
('afp', 'AFP'),
('onp', 'ONP'),
('retired', 'Retired'),
], string='Retirement Type', required=True, default='afp')
onp_rule_id = fields.Many2one('hr.salary.rule', string='ONP Rule', domain=[('code', '=like', 'EE_PE_ONP%')])
# AFP Type may actually be company specific....
afp_type = fields.Selection([
('habitat', 'Habitat'),
('integra', 'Integra'),
('prima', 'Prima'),
('profuturo', 'Profuturo'),
], string='AFP Type', default='profuturo')
afp_comision_type = fields.Selection([
('mixta', 'Mixta'),
('non_mixta', 'Non-Mixta'),
], string='AFP Commission Type', default='mixta')
comp_ss_type = fields.Selection([
('essalud', 'Essalud'),
('eps', 'EPS'),
], string='Company Social Services', default='essalud')
comp_ss_eps_rule_id = fields.Many2one('hr.salary.rule', string='Company Social Security EPS Rule')
comp_life_insurance_rule_id = fields.Many2one('hr.salary.rule', string='Company Life Insurance Rule')
comp_risk_insurance_rule_id = fields.Many2one('hr.salary.rule', string='Company Risk Insurance Rule')

View File

@@ -0,0 +1,2 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_hr_contract_pe_payroll_config,hr.contract.pe_payroll_config,model_hr_contract_pe_payroll_config,hr_payroll.group_hr_payroll_manager,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_hr_contract_pe_payroll_config hr.contract.pe_payroll_config model_hr_contract_pe_payroll_config hr_payroll.group_hr_payroll_manager 1 1 1 1

View File

@@ -5,4 +5,5 @@
# common remains for site specific tests
from . import common
from . import test_2022
from . import test_2020
# from . import test_2022

View File

@@ -58,7 +58,12 @@ class TestPePayslip(common.TransactionCase):
if not 'schedule_pay' in kwargs:
kwargs['schedule_pay'] = 'monthly'
schedule_pay = kwargs['schedule_pay']
config_model = self.env['hr.contract.pe_payroll_config']
contract_model = self.env['hr.contract']
config_values = {
'name': 'Test Config Values',
'employee_id': employee.id,
}
contract_values = {
'name': 'Test Contract',
'employee_id': employee.id,
@@ -69,12 +74,18 @@ class TestPePayslip(common.TransactionCase):
if hasattr(val, 'id'):
val = val.id
found = False
if hasattr(config_model, key):
config_values[key] = val
found = True
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, ))
# PE Payroll Config Defaults Should be set on the Model
config = config_model.create(config_values)
contract_values['pe_payroll_config_id'] = config.id
# Some Basic Defaults
if not contract_values.get('state'):

View File

@@ -0,0 +1,142 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
from .common import TestPePayslip, process_payslip
class Test2020(TestPePayslip):
###
# 2020 Taxes and Rates
###
def test_2020_taxes(self):
# High salary to hit the maximum for AFP_SEGURO
salary = 8000.00
employee = self._createEmployee()
contract = self._createContract(employee,
wage=salary,
retirement_type='afp',
afp_type='profuturo',
afp_comision_type='mixta',
comp_ss_type='essalud',
)
self._log(contract.read())
self._log('2020 tax first payslip:')
payslip = self._createPayslip(employee, '2020-01-01', '2020-01-31')
payslip.compute_sheet()
cats = self._getCategories(payslip)
rules = self._getRules(payslip)
# Employee
self.assertPayrollEqual(cats['GROSS'], salary)
self.assertPayrollEqual(rules['EE_PE_AFP_PENSIONES'], -cats['GROSS'] * (10.0 / 100.0))
self.assertPayrollEqual(rules['EE_PE_AFP_SEGURO'], -cats['GROSS'] * (1.35 / 100.0))
self.assertPayrollEqual(rules['EE_PE_AFP_COMISION_MIXTA'], -cats['GROSS'] * (0.67 / 100.0))
# Employer
self.assertPayrollEqual(rules['ER_PE_ESSALUD'], -cats['GROSS'] * (6.75 / 100.0))
process_payslip(payslip)
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)
# Employee
self.assertPayrollEqual(cats['GROSS'], salary)
self.assertPayrollEqual(rules['EE_PE_AFP_PENSIONES'], -cats['GROSS'] * (10.0 / 100.0))
self.assertTrue(cats['GROSS'] < 9707.03)
# Seguro has a wage base.
second_seguro = -(9707.03 - cats['GROSS']) * (1.35 / 100.0)
self.assertPayrollEqual(rules['EE_PE_AFP_SEGURO'], second_seguro)
self.assertPayrollEqual(rules['EE_PE_AFP_COMISION_MIXTA'], -cats['GROSS'] * (0.67 / 100.0))
# Employer
self.assertPayrollEqual(rules['ER_PE_ESSALUD'], -cats['GROSS'] * (6.75 / 100.0))
process_payslip(payslip)
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)
# Employee
self.assertPayrollEqual(cats['GROSS'], salary)
self.assertPayrollEqual(rules['EE_PE_AFP_PENSIONES'], -cats['GROSS'] * (10.0 / 100.0))
self.assertTrue(cats['GROSS'] < 9707.03)
self.assertPayrollEqual(rules['EE_PE_AFP_SEGURO'], 0.0)
self.assertPayrollEqual(rules['EE_PE_AFP_COMISION_MIXTA'], -cats['GROSS'] * (0.67 / 100.0))
# Employer
self.assertPayrollEqual(rules['ER_PE_ESSALUD'], -cats['GROSS'] * (6.75 / 100.0))
process_payslip(payslip)
def test_2020_onp(self):
salary = 3500.00
employee = self._createEmployee()
contract = self._createContract(employee,
wage=salary,
retirement_type='onp',
onp_rule_id=self.env.ref('l10n_pe_hr_payroll.hr_payroll_rule_ee_onp').id,
comp_ss_type='essalud',
)
self._log(contract.read())
self._log('2020 tax first payslip:')
payslip = self._createPayslip(employee, '2020-01-01', '2020-01-31')
payslip.compute_sheet()
cats = self._getCategories(payslip)
rules = self._getRules(payslip)
# Employee
self.assertPayrollEqual(cats['GROSS'], salary)
self.assertPayrollEqual(rules['EE_PE_AFP_PENSIONES'], 0.0)
self.assertPayrollEqual(rules['EE_PE_AFP_SEGURO'], 0.0)
self.assertPayrollEqual(rules['EE_PE_AFP_COMISION_MIXTA'], 0.0)
self.assertPayrollEqual(cats['EE_PE_ONP'], -cats['GROSS'] * (13.0 / 100.0))
# Employer
self.assertPayrollEqual(rules['ER_PE_ESSALUD'], -cats['GROSS'] * (6.75 / 100.0))
process_payslip(payslip)
def test_2020_ir_5ta_cat(self):
salary = 1500.00
employee = self._createEmployee()
contract = self._createContract(employee,
wage=salary,
retirement_type='onp',
onp_rule_id=self.env.ref('l10n_pe_hr_payroll.hr_payroll_rule_ee_onp').id,
comp_ss_type='essalud',
)
self._log('2020 tax first payslip:')
payslip = self._createPayslip(employee, '2020-01-01', '2020-01-31')
payslip.compute_sheet()
cats = self._getCategories(payslip)
rules = self._getRules(payslip)
self.assertPayrollEqual(cats['GROSS'], salary)
self.assertPayrollEqual(rules['EE_PE_IR_5TA_CAT'], 0.0)
payslip.state = 'cancel'
payslip.unlink()
# larger salary to trigger calculation
salary = 3000.0
contract.wage = salary
payslip = self._createPayslip(employee, '2020-01-01', '2020-01-31')
payslip.compute_sheet()
cats = self._getCategories(payslip)
rules = self._getRules(payslip)
self.assertPayrollEqual(cats['GROSS'], salary)
self.assertPayrollEqual(rules['EE_PE_IR_5TA_CAT'], -74.67)

View File

@@ -1,17 +1,17 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="hr_contract_view_form_inherit_l10n_us" model="ir.ui.view">
<record id="hr_contract_view_form_inherit_l10n_pe" 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"
<field name="pe_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}"/> -->
attrs="{'invisible': [('structure_type_id', '!=', %(l10n_pe_hr_payroll.structure_type_employee)s)],
'required': [('structure_type_id', '=', %(l10n_pe_hr_payroll.structure_type_employee)s)]}"
context="{'default_employee_id': employee_id}"/>
</xpath>
</field>
</record>

View File

@@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="pe_payroll_config_tree" model="ir.ui.view">
<field name="name">hr.contract.pe_payroll_config.tree</field>
<field name="model">hr.contract.pe_payroll_config</field>
<field name="arch" type="xml">
<tree string="Peru Employee Payroll Forms">
<field name="employee_id"/>
<field name="name"/>
<field name="retirement_type"/>
<field name="create_date"/>
<field name="write_date"/>
</tree>
</field>
</record>
<record id="pe_payroll_config_form" model="ir.ui.view">
<field name="name">hr.contract.pe_payroll_config.form</field>
<field name="model">hr.contract.pe_payroll_config</field>
<field name="arch" type="xml">
<form string="Employee Payroll Forms">
<sheet>
<group name="General">
<field name="employee_id"/>
<field name="name"/>
</group>
<group>
<group name="employee" string="Employee">
<field name="retirement_type"/>
<field name="onp_rule_id" attrs="{'invisible': [('retirement_type', '!=', 'onp')]}"/>
<field name="afp_type" attrs="{'invisible': [('retirement_type', '!=', 'afp')]}"/>
<field name="afp_comision_type" attrs="{'invisible': [('retirement_type', '!=', 'afp')]}"/>
</group>
<group name="employer" string="Employer">
<field name="comp_ss_type" />
<field name="comp_ss_eps_rule_id" attrs="{'invisible': [('comp_ss_type', '!=', 'eps')]}"/>
<field name="comp_life_insurance_rule_id" />
<field name="comp_risk_insurance_rule_id" />
</group>
</group>
</sheet>
</form>
</field>
</record>
<record id="pe_payroll_config_search" model="ir.ui.view">
<field name="name">hr.contract.pe_payroll_config.search</field>
<field name="model">hr.contract.pe_payroll_config</field>
<field name="arch" type="xml">
<search string="Peru Employee Payroll Forms Search">
<field name="employee_id"/>
<field name="name"/>
</search>
</field>
</record>
<record id="pe_payroll_config_action_main" model="ir.actions.act_window">
<field name="name">Peru Employee Payroll Forms</field>
<field name="res_model">hr.contract.pe_payroll_config</field>
<field name="view_mode">tree,form</field>
<field name="help" type="html">
<p>
No Forms
</p>
</field>
</record>
<menuitem id="pe_payroll_config_menu_main" name="Peru Payroll Forms"
action="pe_payroll_config_action_main"
sequence="50" parent="hr_payroll.menu_hr_payroll_employees_root"/>
</odoo>