From 18ee1e315dec587e97c927d2e0823e90fa0ea2be Mon Sep 17 00:00:00 2001 From: Jared Kipe Date: Tue, 19 Apr 2022 19:52:14 +0000 Subject: [PATCH] [WIP] l10n_pe_hr_payroll: for 13.0 --- l10n_pe_hr_payroll/__init__.py | 12 ++ l10n_pe_hr_payroll/__manifest__.py | 32 ++++ l10n_pe_hr_payroll/data/afp_rules.xml | 86 ++++++++++ l10n_pe_hr_payroll/data/base.xml | 32 ++++ l10n_pe_hr_payroll/data/er_rules.xml | 36 +++++ l10n_pe_hr_payroll/data/integration_rules.xml | 29 ++++ l10n_pe_hr_payroll/models/__init__.py | 7 + l10n_pe_hr_payroll/models/browsable_object.py | 148 ++++++++++++++++++ l10n_pe_hr_payroll/models/hr_contract.py | 33 ++++ l10n_pe_hr_payroll/models/hr_payslip.py | 134 ++++++++++++++++ .../models/res_config_settings.py | 24 +++ l10n_pe_hr_payroll/models/update.py | 32 ++++ .../static/description/icon.png | Bin 0 -> 8221 bytes l10n_pe_hr_payroll/tests/__init__.py | 8 + l10n_pe_hr_payroll/tests/common.py | 143 +++++++++++++++++ l10n_pe_hr_payroll/tests/test_2022.py | 60 +++++++ .../views/hr_contract_views.xml | 19 +++ .../views/res_config_settings_views.xml | 32 ++++ 18 files changed, 867 insertions(+) create mode 100644 l10n_pe_hr_payroll/__init__.py create mode 100644 l10n_pe_hr_payroll/__manifest__.py create mode 100644 l10n_pe_hr_payroll/data/afp_rules.xml create mode 100644 l10n_pe_hr_payroll/data/base.xml create mode 100644 l10n_pe_hr_payroll/data/er_rules.xml create mode 100644 l10n_pe_hr_payroll/data/integration_rules.xml create mode 100644 l10n_pe_hr_payroll/models/__init__.py create mode 100644 l10n_pe_hr_payroll/models/browsable_object.py create mode 100644 l10n_pe_hr_payroll/models/hr_contract.py create mode 100644 l10n_pe_hr_payroll/models/hr_payslip.py create mode 100644 l10n_pe_hr_payroll/models/res_config_settings.py create mode 100644 l10n_pe_hr_payroll/models/update.py create mode 100644 l10n_pe_hr_payroll/static/description/icon.png create mode 100644 l10n_pe_hr_payroll/tests/__init__.py create mode 100755 l10n_pe_hr_payroll/tests/common.py create mode 100644 l10n_pe_hr_payroll/tests/test_2022.py create mode 100644 l10n_pe_hr_payroll/views/hr_contract_views.xml create mode 100644 l10n_pe_hr_payroll/views/res_config_settings_views.xml diff --git a/l10n_pe_hr_payroll/__init__.py b/l10n_pe_hr_payroll/__init__.py new file mode 100644 index 00000000..013f4e73 --- /dev/null +++ b/l10n_pe_hr_payroll/__init__.py @@ -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');") diff --git a/l10n_pe_hr_payroll/__manifest__.py b/l10n_pe_hr_payroll/__manifest__.py new file mode 100644 index 00000000..65927b42 --- /dev/null +++ b/l10n_pe_hr_payroll/__manifest__.py @@ -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. ', + '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', +} diff --git a/l10n_pe_hr_payroll/data/afp_rules.xml b/l10n_pe_hr_payroll/data/afp_rules.xml new file mode 100644 index 00000000..d99d13bd --- /dev/null +++ b/l10n_pe_hr_payroll/data/afp_rules.xml @@ -0,0 +1,86 @@ + + + + + + EE: AFP Pensiones + ee_afp_pensiones + + + + 10.0 + + + + + + EE: AFP Seguro + ee_afp_seguro + + + + 1.74 + + + + + + EE: AFP Comisión + ee_afp_comision + + + + 0.18 + + + + + + + AFP + + + + + + + + EE: PE AFP Pensiones + EE_PE_AFP_PENSIONES + python + result = categories.BASIC + code + result, result_rate = categories.BASIC, payslip.rule_parameter('ee_afp_pensiones') + + + + + + + + + EE: PE AFP Seguro + EE_PE_AFP_SEGURO + python + result = categories.BASIC + code + result, result_rate = categories.BASIC, payslip.rule_parameter('ee_afp_seguro') + + + + + + + + + EE: PE AFP Comisión + EE_PE_AFP_COMISION + python + result = categories.BASIC + code + result, result_rate = categories.BASIC, payslip.rule_parameter('ee_afp_comision') + + + + + diff --git a/l10n_pe_hr_payroll/data/base.xml b/l10n_pe_hr_payroll/data/base.xml new file mode 100644 index 00000000..1a4d2236 --- /dev/null +++ b/l10n_pe_hr_payroll/data/base.xml @@ -0,0 +1,32 @@ + + + + + Peru Employee + + + + + + Peru Employee Standard + + + + + + + + + EE: AFP + EE_PE_AFP + + + + ER: AFP + ER_PE_AFP + + + + diff --git a/l10n_pe_hr_payroll/data/er_rules.xml b/l10n_pe_hr_payroll/data/er_rules.xml new file mode 100644 index 00000000..0c782704 --- /dev/null +++ b/l10n_pe_hr_payroll/data/er_rules.xml @@ -0,0 +1,36 @@ + + + + + + ER: Essalud + er_essalud + + + + 6.75 + + + + + + + Essalud + + + + + + + + ER: PE Essalud + ER_PE_ESSALUD + python + result = categories.BASIC + code + result, result_rate = categories.BASIC, payslip.rule_parameter('er_essalud') + + + + + diff --git a/l10n_pe_hr_payroll/data/integration_rules.xml b/l10n_pe_hr_payroll/data/integration_rules.xml new file mode 100644 index 00000000..af2684ed --- /dev/null +++ b/l10n_pe_hr_payroll/data/integration_rules.xml @@ -0,0 +1,29 @@ + + + + + python + result = inputs.COMMISSION.amount > 0.0 if inputs.COMMISSION else False + code + result = inputs.COMMISSION.amount if inputs.COMMISSION else 0 + BASIC_COM + + Commissions + + + + + + + python + result = inputs.BADGES.amount > 0.0 if inputs.BADGES else False + code + result = inputs.BADGES.amount if inputs.BADGES else 0 + BASIC_BADGES + + Badges + + + + + \ No newline at end of file diff --git a/l10n_pe_hr_payroll/models/__init__.py b/l10n_pe_hr_payroll/models/__init__.py new file mode 100644 index 00000000..34b51524 --- /dev/null +++ b/l10n_pe_hr_payroll/models/__init__.py @@ -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 diff --git a/l10n_pe_hr_payroll/models/browsable_object.py b/l10n_pe_hr_payroll/models/browsable_object.py new file mode 100644 index 00000000..bed067fd --- /dev/null +++ b/l10n_pe_hr_payroll/models/browsable_object.py @@ -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 diff --git a/l10n_pe_hr_payroll/models/hr_contract.py b/l10n_pe_hr_payroll/models/hr_contract.py new file mode 100644 index 00000000..316411f1 --- /dev/null +++ b/l10n_pe_hr_payroll/models/hr_contract.py @@ -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] diff --git a/l10n_pe_hr_payroll/models/hr_payslip.py b/l10n_pe_hr_payroll/models/hr_payslip.py new file mode 100644 index 00000000..90a27025 --- /dev/null +++ b/l10n_pe_hr_payroll/models/hr_payslip.py @@ -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) diff --git a/l10n_pe_hr_payroll/models/res_config_settings.py b/l10n_pe_hr_payroll/models/res_config_settings.py new file mode 100644 index 00000000..05af9430 --- /dev/null +++ b/l10n_pe_hr_payroll/models/res_config_settings.py @@ -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') diff --git a/l10n_pe_hr_payroll/models/update.py b/l10n_pe_hr_payroll/models/update.py new file mode 100644 index 00000000..b901db8a --- /dev/null +++ b/l10n_pe_hr_payroll/models/update.py @@ -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 diff --git a/l10n_pe_hr_payroll/static/description/icon.png b/l10n_pe_hr_payroll/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..586dd9e83651c9d9a5bb9b0ab08e1a4dcf033034 GIT binary patch literal 8221 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 literal 0 HcmV?d00001 diff --git a/l10n_pe_hr_payroll/tests/__init__.py b/l10n_pe_hr_payroll/tests/__init__.py new file mode 100644 index 00000000..761fda6c --- /dev/null +++ b/l10n_pe_hr_payroll/tests/__init__.py @@ -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 diff --git a/l10n_pe_hr_payroll/tests/common.py b/l10n_pe_hr_payroll/tests/common.py new file mode 100755 index 00000000..a47a2cfd --- /dev/null +++ b/l10n_pe_hr_payroll/tests/common.py @@ -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) diff --git a/l10n_pe_hr_payroll/tests/test_2022.py b/l10n_pe_hr_payroll/tests/test_2022.py new file mode 100644 index 00000000..ee388352 --- /dev/null +++ b/l10n_pe_hr_payroll/tests/test_2022.py @@ -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) diff --git a/l10n_pe_hr_payroll/views/hr_contract_views.xml b/l10n_pe_hr_payroll/views/hr_contract_views.xml new file mode 100644 index 00000000..b7532699 --- /dev/null +++ b/l10n_pe_hr_payroll/views/hr_contract_views.xml @@ -0,0 +1,19 @@ + + + + + hr.contract.form.inherit + hr.contract + + + + + + + + + \ No newline at end of file diff --git a/l10n_pe_hr_payroll/views/res_config_settings_views.xml b/l10n_pe_hr_payroll/views/res_config_settings_views.xml new file mode 100644 index 00000000..3c69b42f --- /dev/null +++ b/l10n_pe_hr_payroll/views/res_config_settings_views.xml @@ -0,0 +1,32 @@ + + + + + res.config.settings.view.form.inherit + res.config.settings + + + +
+
+
+ Payslip Sum Behavior +
+ 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. +
+
+
+
+
+
+
+
+
+
+
+ +