[WIP] l10n_pe_hr_payroll: for 13.0

This commit is contained in:
Jared Kipe
2022-04-19 19:52:14 +00:00
parent c54f00976c
commit 18ee1e315d
18 changed files with 867 additions and 0 deletions

View 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');")

View 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',
}

View 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>

View 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>

View 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>

View 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>

View 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

View 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

View 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]

View 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)

View 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')

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

View 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

View 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)

View 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)

View 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>

View 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>