diff --git a/hr_payroll_hibou/models/browsable_object.py b/hr_payroll_hibou/models/browsable_object.py index 60543eb8..d93e0eee 100644 --- a/hr_payroll_hibou/models/browsable_object.py +++ b/hr_payroll_hibou/models/browsable_object.py @@ -138,6 +138,10 @@ class Payslips(BrowsableObject): def paid_amount(self): return self.dict._get_paid_amount() + # Hibou helper + @property + def pay_periods_in_year(self): + return self.dict.get_pay_periods_in_year() # Patch over Core browsable_object.BrowsableObject.__init__ = BrowsableObject.__init__ @@ -149,3 +153,4 @@ 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 +browsable_object.Payslips.pay_periods_in_year = Payslips.pay_periods_in_year diff --git a/hr_payroll_hibou/models/hr_payslip.py b/hr_payroll_hibou/models/hr_payslip.py index 06f1031f..c6737e57 100644 --- a/hr_payroll_hibou/models/hr_payslip.py +++ b/hr_payroll_hibou/models/hr_payslip.py @@ -6,6 +6,19 @@ from odoo import api, fields, models 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, + } + # normal_wage is an integer field, but that lacks precision. normal_wage = fields.Float(compute='_compute_normal_wage', store=True) # We need to be able to support more complexity, @@ -35,3 +48,6 @@ class HrPayslip(models.Model): # This would be a good place to override though with a 'work type' # based mechanism, like a minimum rate or 'rate card' implementation return self.contract_id._get_contract_wage(work_type=work_type) + + 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/__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..6530714a --- /dev/null +++ b/l10n_pe_hr_payroll/__manifest__.py @@ -0,0 +1,36 @@ +# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details. + +{ + 'name': 'Peru - Payroll', + 'author': 'Hibou Corp. ', + 'version': '15.0.2022.0.0', + 'category': 'Payroll Localization', + 'depends': [ + 'hr_payroll_hibou', + 'hr_contract_reports', + 'hibou_professional', + ], + 'description': """ +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_4ta_cat_rules.xml', + 'data/ir_5ta_cat_rules.xml', + 'data/er_rules.xml', + 'views/hr_contract_views.xml', + 'views/pe_payroll_config_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..ffeed643 --- /dev/null +++ b/l10n_pe_hr_payroll/data/afp_rules.xml @@ -0,0 +1,85 @@ + + + + + + EE: AFP + ee_afp + + + + { + # 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), + } + + + + + + + + + + EE: PE AFP Pensions + EE_PE_AFP_PENSIONES + python + result = categories.GROSS and contract.pe_payroll_config_value('retirement_type') == 'afp' + code + result, result_rate = categories.GROSS, -payslip.rule_parameter('ee_afp')[contract.pe_payroll_config_value('afp_type')][4] + + + + + + + + EE: PE AFP Insurance + EE_PE_AFP_SEGURO + python + result = categories.GROSS and contract.pe_payroll_config_value('retirement_type') == 'afp' + code + +wage = categories.GROSS +# wage_max is monthly +wage_max = payslip.rule_parameter('ee_afp')[contract.pe_payroll_config_value('afp_type')][5] +# normalize wage_max to pay period +pay_periods_in_year = payslip.pay_periods_in_year +wage_max = (wage_max * 12.0) / pay_periods_in_year +eligible_wage = min(wage, wage_max) +rate = -payslip.rule_parameter('ee_afp')[contract.pe_payroll_config_value('afp_type')][3] +result, result_rate = eligible_wage, rate + + + + + + + + + EE: PE AFP Mixed Commission + EE_PE_AFP_COMISION_MIXTA + python + result = categories.GROSS and contract.pe_payroll_config_value('retirement_type') == 'afp' and contract.pe_payroll_config_value('afp_comision_type') == 'mixta' + code + result, result_rate = categories.GROSS, -payslip.rule_parameter('ee_afp')[contract.pe_payroll_config_value('afp_type')][1] + + + + + + + + EE: PE AFP Comission (Non-Mixed) + EE_PE_AFP_COMISION_NON_MIXTA + python + result = categories.GROSS and contract.pe_payroll_config_value('retirement_type') == 'afp' and contract.pe_payroll_config_value('afp_comision_type') == 'non_mixta' + code + result, result_rate = categories.GROSS, -payslip.rule_parameter('ee_afp')[contract.pe_payroll_config_value('afp_type')][0] + + + + diff --git a/l10n_pe_hr_payroll/data/base.xml b/l10n_pe_hr_payroll/data/base.xml new file mode 100644 index 00000000..09d6d494 --- /dev/null +++ b/l10n_pe_hr_payroll/data/base.xml @@ -0,0 +1,116 @@ + + + + + Peru Employee (5ta Cat.) + + + + + + Peru Employee (5ta Cat.) + + + + + + + + + + + Peru Employee (4ta Cat.) + + + + + + Peru Employee (4ta Cat.) + + + + + + + + + + + EE: AFP + EE_PE_AFP + + + + ER: AFP + ER_PE_AFP + + + + + + EE: ONP + EE_PE_ONP + + + + ER: ONP + ER_PE_ONP + + + + + + EE: IR 5th Cat. + EE_PE_IR_5TA_CAT + + + + ER: IR 5th Cat. + ER_PE_IR_5TA_CAT + + + + + + EE: IR 4th Cat. + EE_PE_IR_4TA_CAT + + + + + + EE: Essalud (rem) + EE_PE_ESSALUD + + + + EE: Essalud + ER_PE_ESSALUD + + + + + + Bonus + BONO + + + + + + python + result = inputs.BONO.amount > 0.0 if inputs.BONO else False + code + result = inputs.BONO.amount if inputs.BONO else 0 + BASIC_BONO + + Bonus + + + + + 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..a5d97b44 --- /dev/null +++ b/l10n_pe_hr_payroll/data/er_rules.xml @@ -0,0 +1,30 @@ + + + + + + ER: Essalud + er_essalud + + + + 6.75 + + + + + + + + + + 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/data/ir_4ta_cat_rules.xml b/l10n_pe_hr_payroll/data/ir_4ta_cat_rules.xml new file mode 100644 index 00000000..d18d87f3 --- /dev/null +++ b/l10n_pe_hr_payroll/data/ir_4ta_cat_rules.xml @@ -0,0 +1,30 @@ + + + + + + EE: IR 4ta Cat. + ee_ir_4ta_cat + + + + 8.0 + + + + + + + + + + EE: PE IR 4th Cat. + EE_PE_IR_4TA_CAT + python + result, _ = ir_4ta_cat(payslip, categories, worked_days, inputs) + code + result, result_rate = ir_4ta_cat(payslip, categories, worked_days, inputs) + + + + diff --git a/l10n_pe_hr_payroll/data/ir_5ta_cat_rules.xml b/l10n_pe_hr_payroll/data/ir_5ta_cat_rules.xml new file mode 100644 index 00000000..bafd7b5b --- /dev/null +++ b/l10n_pe_hr_payroll/data/ir_5ta_cat_rules.xml @@ -0,0 +1,60 @@ + + + + + + Peru UIT + pe_uit + + + + 4400.0 + + + + + + EE: IR 5ta Cat. + ee_ir_5ta_cat + + + + + [ + ( 5.0, 8.0), + ( 20.0, 14.0), + ( 35.0, 17.0), + ( 45.0, 20.0), + ('inf', 30.0), + ] + + + + + + EE: IR 5ta Cat. Bonificación Extraordinaria (Ley 29351) + ee_ir_5ta_cat_ley_29351 + + + + 9.0 + + + + + + + + + + + EE: PE IR 5th Cat. + EE_PE_IR_5TA_CAT + python + result, _ = ir_5ta_cat(payslip, categories, worked_days, inputs, BASIC) + code + result, result_rate = ir_5ta_cat(payslip, categories, worked_days, inputs, BASIC) + + + + diff --git a/l10n_pe_hr_payroll/data/onp_rules.xml b/l10n_pe_hr_payroll/data/onp_rules.xml new file mode 100644 index 00000000..b2f81e34 --- /dev/null +++ b/l10n_pe_hr_payroll/data/onp_rules.xml @@ -0,0 +1,30 @@ + + + + + + EE: ONP + ee_onp + + + + 13.0 + + + + + + + + + + EE: PE ONP/SNP + EE_PE_ONP + python + result = categories.GROSS and contract.pe_payroll_config_value('retirement_type') == 'onp' + code + result, result_rate = categories.GROSS, -payslip.rule_parameter('ee_onp') + + + + diff --git a/l10n_pe_hr_payroll/i18n/es.po b/l10n_pe_hr_payroll/i18n/es.po new file mode 100644 index 00000000..da3d014e --- /dev/null +++ b/l10n_pe_hr_payroll/i18n/es.po @@ -0,0 +1,323 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * l10n_pe_hr_payroll +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 13.0+e\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2022-05-06 01:45+0000\n" +"PO-Revision-Date: 2022-05-06 01:45+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: l10n_pe_hr_payroll +#: model:ir.model.fields.selection,name:l10n_pe_hr_payroll.selection__hr_contract_pe_payroll_config__retirement_type__afp +msgid "AFP" +msgstr "AFP" + +#. module: l10n_pe_hr_payroll +#: model:ir.model.fields,field_description:l10n_pe_hr_payroll.field_hr_contract_pe_payroll_config__afp_comision_type +msgid "AFP Commission Type" +msgstr "Tipo de Comisión de AFP" + +#. module: l10n_pe_hr_payroll +#: model:ir.model.fields,field_description:l10n_pe_hr_payroll.field_hr_contract_pe_payroll_config__afp_type +msgid "AFP Type" +msgstr "Tipo de AFP" + +#. module: l10n_pe_hr_payroll +#: model:hr.salary.rule,name:l10n_pe_hr_payroll.hr_salary_rule_gamification +msgid "Badges" +msgstr "Insignias" + +#. module: l10n_pe_hr_payroll +#: model:hr.salary.rule,name:l10n_pe_hr_payroll.hr_salary_rule_bonus +msgid "Bonus" +msgstr "Bono" + +#. module: l10n_pe_hr_payroll +#: model:hr.salary.rule,name:l10n_pe_hr_payroll.hr_salary_rule_commission +msgid "Commissions" +msgstr "Comisiones" + +#. module: l10n_pe_hr_payroll +#: model:ir.model.fields,field_description:l10n_pe_hr_payroll.field_hr_contract_pe_payroll_config__comp_ss_eps_rule_id +msgid "Company Social Security EPS Rule" +msgstr "Regla EPS de la Seguridad Social de la Empresa" + +#. module: l10n_pe_hr_payroll +#: model:ir.model.fields,field_description:l10n_pe_hr_payroll.field_hr_contract_pe_payroll_config__comp_ss_type +msgid "Company Social Services" +msgstr "Servicios Social de la Empresa" + +#. module: l10n_pe_hr_payroll +#: model:ir.model,name:l10n_pe_hr_payroll.model_hr_contract_pe_payroll_config +msgid "Contract PE Payroll Forms" +msgstr "Formularios de Nómina del Contrato PE" + +#. module: l10n_pe_hr_payroll +#: model:ir.model.fields,field_description:l10n_pe_hr_payroll.field_hr_contract_pe_payroll_config__create_uid +msgid "Created by" +msgstr "Creado Por" + +#. module: l10n_pe_hr_payroll +#: model:ir.model.fields,field_description:l10n_pe_hr_payroll.field_hr_contract_pe_payroll_config__create_date +msgid "Created on" +msgstr "Creado En" + +#. module: l10n_pe_hr_payroll +#: model:ir.model.fields,field_description:l10n_pe_hr_payroll.field_hr_contract_pe_payroll_config__date_hired +msgid "Date Hired" +msgstr "Fecha de Contratación" + +#. module: l10n_pe_hr_payroll +#: model:ir.model.fields,field_description:l10n_pe_hr_payroll.field_hr_contract_pe_payroll_config__name +msgid "Description" +msgstr "Descripción" + +#. module: l10n_pe_hr_payroll +#: model:ir.model.fields,field_description:l10n_pe_hr_payroll.field_hr_contract_pe_payroll_config__display_name +msgid "Display Name" +msgstr "Nombre para Mostrar" + +#. module: l10n_pe_hr_payroll +#: model:hr.salary.rule.category,name:l10n_pe_hr_payroll.hr_payroll_category_ee_pe_afp +msgid "EE: AFP" +msgstr "EE: AFP" + +#. module: l10n_pe_hr_payroll +#: model:hr.salary.rule.category,name:l10n_pe_hr_payroll.hr_payroll_category_er_essalud +msgid "EE: Essalud" +msgstr "EE: Essalud" + +#. module: l10n_pe_hr_payroll +#: model:hr.salary.rule.category,name:l10n_pe_hr_payroll.hr_payroll_category_ee_essalud +msgid "EE: Essalud (rem)" +msgstr "EE: Essalud (rem)" + +#. module: l10n_pe_hr_payroll +#: model:hr.salary.rule.category,name:l10n_pe_hr_payroll.hr_payroll_category_ee_ir_4ta_cat +msgid "EE: IR 4th Cat." +msgstr "EE: IR 4ta Cat." + +#. module: l10n_pe_hr_payroll +#: model:hr.salary.rule.category,name:l10n_pe_hr_payroll.hr_payroll_category_ee_ir_5ta_cat +msgid "EE: IR 5th Cat." +msgstr "EE: IR 5ta Cat." + +#. module: l10n_pe_hr_payroll +#: model:hr.salary.rule.category,name:l10n_pe_hr_payroll.hr_payroll_category_ee_pe_onp +msgid "EE: ONP" +msgstr "EE: ONP" + +#. module: l10n_pe_hr_payroll +#: model:hr.salary.rule,name:l10n_pe_hr_payroll.hr_payroll_rule_ee_afp_comision_non_mixta +msgid "EE: PE AFP Comission (Non-Mixed)" +msgstr "EE: PE AFP Comisión (No-Mixta)" + +#. module: l10n_pe_hr_payroll +#: model:hr.salary.rule,name:l10n_pe_hr_payroll.hr_payroll_rule_ee_afp_seguro +msgid "EE: PE AFP Insurance" +msgstr "EE: PE AFP Seguro" + +#. module: l10n_pe_hr_payroll +#: model:hr.salary.rule,name:l10n_pe_hr_payroll.hr_payroll_rule_ee_afp_comision_mixta +msgid "EE: PE AFP Mixed Commission" +msgstr "EE: PE AFP Comisión Mixta" + +#. module: l10n_pe_hr_payroll +#: model:hr.salary.rule,name:l10n_pe_hr_payroll.hr_payroll_rule_ee_afp_pensiones +msgid "EE: PE AFP Pensions" +msgstr "EE: PE AFP Pensiones" + +#. module: l10n_pe_hr_payroll +#: model:hr.salary.rule,name:l10n_pe_hr_payroll.hr_payroll_rule_ee_ir_4ta_cat +msgid "EE: PE IR 4th Cat." +msgstr "EE: PE IR 4ta Cat." + +#. module: l10n_pe_hr_payroll +#: model:hr.salary.rule,name:l10n_pe_hr_payroll.hr_payroll_rule_ee_ir_5ta_cat +msgid "EE: PE IR 5th Cat." +msgstr "EE: PE IR 5ta Cat." + +#. module: l10n_pe_hr_payroll +#: model:hr.salary.rule,name:l10n_pe_hr_payroll.hr_payroll_rule_ee_onp +msgid "EE: PE ONP/SNP" +msgstr "EE: PE ONP/SNP" + +#. module: l10n_pe_hr_payroll +#: model:ir.model.fields.selection,name:l10n_pe_hr_payroll.selection__hr_contract_pe_payroll_config__comp_ss_type__eps +msgid "EPS" +msgstr "EPS" + +#. module: l10n_pe_hr_payroll +#: model:hr.salary.rule.category,name:l10n_pe_hr_payroll.hr_payroll_category_er_pe_afp +msgid "ER: AFP" +msgstr "ER: AFP" + +#. module: l10n_pe_hr_payroll +#: model:hr.salary.rule.category,name:l10n_pe_hr_payroll.hr_payroll_category_er_ir_5ta_cat +msgid "ER: IR 5th Cat." +msgstr "ER: IR 5ta Cat." + +#. module: l10n_pe_hr_payroll +#: model:hr.salary.rule.category,name:l10n_pe_hr_payroll.hr_payroll_category_er_pe_onp +msgid "ER: ONP" +msgstr "ER: ONP" + +#. module: l10n_pe_hr_payroll +#: model:hr.salary.rule,name:l10n_pe_hr_payroll.hr_payroll_rule_er_essalud +msgid "ER: PE Essalud" +msgstr "ER: PE Essalud" + +#. module: l10n_pe_hr_payroll +#: model:ir.model.fields,field_description:l10n_pe_hr_payroll.field_hr_contract_pe_payroll_config__employee_id +#: model_terms:ir.ui.view,arch_db:l10n_pe_hr_payroll.pe_payroll_config_form +msgid "Employee" +msgstr "Empleado" + +#. module: l10n_pe_hr_payroll +#: model:ir.model,name:l10n_pe_hr_payroll.model_hr_contract +msgid "Employee Contract" +msgstr "Contrato de Empleado" + +#. module: l10n_pe_hr_payroll +#: model_terms:ir.ui.view,arch_db:l10n_pe_hr_payroll.pe_payroll_config_form +msgid "Employee Payroll Forms" +msgstr "Formularios de Nómina del Empleado" + +#. module: l10n_pe_hr_payroll +#: model_terms:ir.ui.view,arch_db:l10n_pe_hr_payroll.pe_payroll_config_form +msgid "Employer" +msgstr "Empleador" + +#. module: l10n_pe_hr_payroll +#: model:ir.model.fields.selection,name:l10n_pe_hr_payroll.selection__hr_contract_pe_payroll_config__comp_ss_type__essalud +msgid "Essalud" +msgstr "Essalud" + +#. module: l10n_pe_hr_payroll +#: model:ir.model.fields,field_description:l10n_pe_hr_payroll.field_hr_contract__pe_payroll_ee_4ta_cat_exempt +msgid "Exempt from 4th Cat. withholding." +msgstr "Exento de Retención de la 4ta Cat." + +#. module: l10n_pe_hr_payroll +#: model:ir.model.fields,help:l10n_pe_hr_payroll.field_hr_contract_pe_payroll_config__date_hired +msgid "For calculations like IR 5TH CAT." +msgstr "Para Cálculos como IR 5TA CAT." + +#. module: l10n_pe_hr_payroll +#: model:ir.model.fields.selection,name:l10n_pe_hr_payroll.selection__hr_contract_pe_payroll_config__afp_type__habitat +msgid "Habitat" +msgstr "Habitat" + +#. module: l10n_pe_hr_payroll +#: model:ir.model.fields,field_description:l10n_pe_hr_payroll.field_hr_contract_pe_payroll_config__id +msgid "ID" +msgstr "ID" + +#. module: l10n_pe_hr_payroll +#: model:ir.model.fields.selection,name:l10n_pe_hr_payroll.selection__hr_contract_pe_payroll_config__afp_type__integra +msgid "Integra" +msgstr "Integra" + +#. module: l10n_pe_hr_payroll +#: model:ir.model.fields,field_description:l10n_pe_hr_payroll.field_hr_contract_pe_payroll_config____last_update +msgid "Last Modified on" +msgstr "Última Modificación En" + +#. module: l10n_pe_hr_payroll +#: model:ir.model.fields,field_description:l10n_pe_hr_payroll.field_hr_contract_pe_payroll_config__write_uid +msgid "Last Updated by" +msgstr "Última Actualización Por" + +#. module: l10n_pe_hr_payroll +#: model:ir.model.fields,field_description:l10n_pe_hr_payroll.field_hr_contract_pe_payroll_config__write_date +msgid "Last Updated on" +msgstr "Ultima Actualización En" + +#. module: l10n_pe_hr_payroll +#: model:ir.model.fields.selection,name:l10n_pe_hr_payroll.selection__hr_contract_pe_payroll_config__afp_comision_type__mixta +msgid "Mixed" +msgstr "Mixta" + +#. module: l10n_pe_hr_payroll +#: model_terms:ir.actions.act_window,help:l10n_pe_hr_payroll.pe_payroll_config_action_main +msgid "No Forms" +msgstr "Sin Formularios" + +#. module: l10n_pe_hr_payroll +#: model:ir.model.fields.selection,name:l10n_pe_hr_payroll.selection__hr_contract_pe_payroll_config__afp_comision_type__non_mixta +msgid "Non-Mixed" +msgstr "No-Mixta" + +#. module: l10n_pe_hr_payroll +#: model_terms:ir.ui.view,arch_db:l10n_pe_hr_payroll.pe_payroll_config_form +msgid "Not supported. Specify rule." +msgstr "No Soportado. Indique una Regla" + +#. module: l10n_pe_hr_payroll +#: model:ir.model.fields.selection,name:l10n_pe_hr_payroll.selection__hr_contract_pe_payroll_config__retirement_type__onp +msgid "ONP" +msgstr "ONP" + +#. module: l10n_pe_hr_payroll +#: model:ir.model,name:l10n_pe_hr_payroll.model_hr_payslip +msgid "Pay Slip" +msgstr "Recibo de Nómina" + +#. module: l10n_pe_hr_payroll +#: model:ir.model.fields,field_description:l10n_pe_hr_payroll.field_hr_contract__pe_payroll_config_id +msgid "Payroll Forms" +msgstr "Formularios de Nómina" + +#. module: l10n_pe_hr_payroll +#: model:ir.actions.act_window,name:l10n_pe_hr_payroll.pe_payroll_config_action_main +#: model_terms:ir.ui.view,arch_db:l10n_pe_hr_payroll.pe_payroll_config_tree +msgid "Peru Employee Payroll Forms" +msgstr "Formularios de Nómina del Empleado de Perú" + +#. module: l10n_pe_hr_payroll +#: model_terms:ir.ui.view,arch_db:l10n_pe_hr_payroll.pe_payroll_config_search +msgid "Peru Employee Payroll Forms Search" +msgstr "Búsqueda de Formularios de Nómina del Empleado de Perú" + +#. module: l10n_pe_hr_payroll +#: model:ir.ui.menu,name:l10n_pe_hr_payroll.pe_payroll_config_menu_main +msgid "Peru Payroll Forms" +msgstr "Formularios de Nómina de Perú" + +#. module: l10n_pe_hr_payroll +#: model:ir.model.fields.selection,name:l10n_pe_hr_payroll.selection__hr_contract_pe_payroll_config__afp_type__prima +msgid "Prima" +msgstr "Prima" + +#. module: l10n_pe_hr_payroll +#: model:ir.model.fields.selection,name:l10n_pe_hr_payroll.selection__hr_contract_pe_payroll_config__afp_type__profuturo +msgid "Profuturo" +msgstr "Profuturo" + +#. module: l10n_pe_hr_payroll +#: model:ir.model,name:l10n_pe_hr_payroll.model_publisher_warranty_contract +msgid "Publisher Warranty Contract" +msgstr "Contrato de Garantía del Editorial" + +#. module: l10n_pe_hr_payroll +#: model:ir.model.fields.selection,name:l10n_pe_hr_payroll.selection__hr_contract_pe_payroll_config__retirement_type__retired +msgid "Retired" +msgstr "Jubilado" + +#. module: l10n_pe_hr_payroll +#: model:ir.model.fields,field_description:l10n_pe_hr_payroll.field_hr_contract_pe_payroll_config__retirement_type +msgid "Retirement Type" +msgstr "Tipo de Jubilación" + +#. module: l10n_pe_hr_payroll +#: model:ir.model.fields,help:l10n_pe_hr_payroll.field_hr_contract_pe_payroll_config__comp_ss_eps_rule_id +msgid "Rule code prefix 'ER_PE_EPS' to select here." +msgstr "Prefijo del Código de la Regla 'ER_PE_EPS' a Seleccionar Aquí." diff --git a/l10n_pe_hr_payroll/models/__init__.py b/l10n_pe_hr_payroll/models/__init__.py new file mode 100644 index 00000000..30e1b8cc --- /dev/null +++ b/l10n_pe_hr_payroll/models/__init__.py @@ -0,0 +1,6 @@ +# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details. + +from . import hr_contract +from . import hr_payslip +from . import pe_payroll_config +from . import update 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..f6bfbacf --- /dev/null +++ b/l10n_pe_hr_payroll/models/hr_contract.py @@ -0,0 +1,13 @@ +# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details. + +from odoo import api, fields, models + + +class PEHRContract(models.Model): + _inherit = 'hr.contract' + + pe_payroll_config_id = fields.Many2one('hr.contract.pe_payroll_config', 'Payroll Forms') + pe_payroll_ee_4ta_cat_exempt = fields.Boolean(string='Exempt from 4th Cat. withholding.') + + def pe_payroll_config_value(self, name): + return self.pe_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..df66de83 --- /dev/null +++ b/l10n_pe_hr_payroll/models/hr_payslip.py @@ -0,0 +1,19 @@ +# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details. + +from odoo import api, fields, models +from .rules.general import _general_rate +from .rules.ir_4ta_cat import ir_4ta_cat +from .rules.ir_5ta_cat import ir_5ta_cat + + +class HRPayslip(models.Model): + _inherit = 'hr.payslip' + + def _get_base_local_dict(self): + res = super()._get_base_local_dict() + res.update({ + 'general_rate': _general_rate, + 'ir_4ta_cat': ir_4ta_cat, + 'ir_5ta_cat': ir_5ta_cat, + }) + return res diff --git a/l10n_pe_hr_payroll/models/pe_payroll_config.py b/l10n_pe_hr_payroll/models/pe_payroll_config.py new file mode 100644 index 00000000..ebac2dc1 --- /dev/null +++ b/l10n_pe_hr_payroll/models/pe_payroll_config.py @@ -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) + date_hired = fields.Date(string='Date Hired', required=True, default=fields.Date.today, + help='For calculations like IR 5TH CAT.') + + retirement_type = fields.Selection([ + ('afp', 'AFP'), + ('onp', 'ONP'), + ('retired', 'Retired'), + ], string='Retirement Type', required=True, default='afp') + + # 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', 'Mixed'), + ('non_mixta', 'Non-Mixed'), + ], 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', + domain=[('code', '=like', 'ER_PE_EPS%')], + help="Rule code prefix 'ER_PE_EPS' to select here.") diff --git a/l10n_pe_hr_payroll/models/rules/__init__.py b/l10n_pe_hr_payroll/models/rules/__init__.py new file mode 100644 index 00000000..9b5a0ca1 --- /dev/null +++ b/l10n_pe_hr_payroll/models/rules/__init__.py @@ -0,0 +1,5 @@ +# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details. + +from . import general +from . import ir_4ta_cat +from . import ir_5ta_cat diff --git a/l10n_pe_hr_payroll/models/rules/general.py b/l10n_pe_hr_payroll/models/rules/general.py new file mode 100644 index 00000000..a061b934 --- /dev/null +++ b/l10n_pe_hr_payroll/models/rules/general.py @@ -0,0 +1,58 @@ +# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details. + +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 diff --git a/l10n_pe_hr_payroll/models/rules/ir_4ta_cat.py b/l10n_pe_hr_payroll/models/rules/ir_4ta_cat.py new file mode 100644 index 00000000..54bcfcf4 --- /dev/null +++ b/l10n_pe_hr_payroll/models/rules/ir_4ta_cat.py @@ -0,0 +1,10 @@ +# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details. + +def ir_4ta_cat(payslip, categories, worked_days, inputs): + if payslip.contract_id.pe_payroll_ee_4ta_cat_exempt: + return 0.0, 0.0 + wage = categories.GROSS + assert wage == 11000.00 + rate = payslip.rule_parameter('ee_ir_4ta_cat') + assert rate == 8.0 + return wage, -rate diff --git a/l10n_pe_hr_payroll/models/rules/ir_5ta_cat.py b/l10n_pe_hr_payroll/models/rules/ir_5ta_cat.py new file mode 100644 index 00000000..da802e61 --- /dev/null +++ b/l10n_pe_hr_payroll/models/rules/ir_5ta_cat.py @@ -0,0 +1,91 @@ +# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details. + +from datetime import date + +def ir_5ta_cat(payslip, categories, worked_days, inputs, basic_wage): + pay_periods_in_year = payslip.pay_periods_in_year + uit = payslip.rule_parameter('pe_uit') + payslip_date_end = payslip.dict.date_to + + # IF this is the last payroll in June or December + # THEN we need to 'true up' the last two quarters of withholding (e.g. give a refund) + last_payslip_june = payslip_date_end.month == 6 and payslip_date_end.day == 30 + # NOTE we do NOT currently support 'catch up' in June. Our formula genearlly already catches up. + last_payslip_june = False + last_payslip_december = payslip_date_end.month == 12 and payslip_date_end.day == 31 + + wage_period = categories.GROSS + if not any((basic_wage, wage_period, last_payslip_june, last_payslip_december)): + return 0.0, 0.0 + + period_additional_wage = max(wage_period - basic_wage, 0.0) # 0.0 or positive + + year = payslip_date_end.year + next_year = date(year+1, 1, 1) + prior_wage_year = payslip.sum_category('GROSS', str(year) + '-01-01', str(year+1) + '-01-01') + pay_periods_at_current = round(((next_year - payslip_date_end).days / 365) * pay_periods_in_year) + 1.0 + + wage_year = (basic_wage * pay_periods_at_current) + prior_wage_year + + # IF employee's `date_hired` is in current year + # THEN we can pro-rate the period (reduce withholding) + date_hired = payslip.dict.contract_id.pe_payroll_config_value('date_hired') + payslip_date_end = payslip.dict.date_to + hired_in_year = date_hired.year == payslip_date_end.year + periods_in_year_eligible = pay_periods_in_year + if hired_in_year: + periods_in_year_eligible = round(((next_year - date_hired).days / 365) * pay_periods_in_year) + + # normalize 1era Gratification + if hired_in_year and date_hired.month > 6: + wage_gratif_1 = 0.0 + elif hired_in_year: + wage_gratif_1 = basic_wage / 6 * (6 - date_hired.month + 1) + else: + wage_gratif_1 = basic_wage + + # normalize 2da Gratification + if hired_in_year and date_hired.month > 6: + wage_gratif_2 = basic_wage / 6 * (12 - date_hired.month + 1) + else: + wage_gratif_2 = basic_wage + + wage_year += wage_gratif_1 + wage_gratif_2 + cat_ley = (wage_gratif_1 + wage_gratif_2) * (payslip.rule_parameter('ee_ir_5ta_cat_ley_29351') / 100.0) + wage_year += cat_ley + wage_year += period_additional_wage + + over_7uit = wage_year - (7.0 * uit) + total_tax = 0.0 + if over_7uit > 0.0: + 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 > (last_uit * uit): + eligible_wage = min(over_7uit, _uit * uit) - (last_uit * uit) + if eligible_wage > 0.0: + total_tax += eligible_wage * (rate / 100.0) + else: + break + else: + break + last_uit = _uit + + # if total_tax: + ytd_tax = -payslip.sum_category('EE_PE_IR_5TA_CAT', str(year) + '-01-01', str(year+1) + '-01-01') + + if last_payslip_june or last_payslip_december: + if last_payslip_june: + # does not work right because the gratif_2 is already there + total_tax /= 2 + # remaining_tax may flip signs + remaining_tax = -(total_tax - ytd_tax) + if not wage_period: + # to give refund, cannot normalize to wage + return remaining_tax, 100.0 + return wage_period, (remaining_tax / wage_period * 100.0) + + tax = -(total_tax - ytd_tax) / pay_periods_at_current + return wage_period, (tax / wage_period * 100.0) 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/security/ir.model.access.csv b/l10n_pe_hr_payroll/security/ir.model.access.csv new file mode 100644 index 00000000..2ccba490 --- /dev/null +++ b/l10n_pe_hr_payroll/security/ir.model.access.csv @@ -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 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 00000000..586dd9e8 Binary files /dev/null and b/l10n_pe_hr_payroll/static/description/icon.png differ diff --git a/l10n_pe_hr_payroll/tests/__init__.py b/l10n_pe_hr_payroll/tests/__init__.py new file mode 100644 index 00000000..7a934474 --- /dev/null +++ b/l10n_pe_hr_payroll/tests/__init__.py @@ -0,0 +1,6 @@ +# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details. + +# Tests moved to `l10n_pe_hr_payroll_params` +# common remains for site specific tests + +from . import common diff --git a/l10n_pe_hr_payroll/tests/common.py b/l10n_pe_hr_payroll/tests/common.py new file mode 100755 index 00000000..e8b128f1 --- /dev/null +++ b/l10n_pe_hr_payroll/tests/common.py @@ -0,0 +1,65 @@ +# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details. + +from odoo.addons.hr_payroll_hibou.tests import common + + +process_payslip = common.process_payslip + + +class TestPePayslip(common.TestPayslip): + + def setUp(self): + super().setUp() + self.structure_type = self.env.ref('l10n_pe_hr_payroll.structure_type_employee') + self.structure = self.env.ref('l10n_pe_hr_payroll.hr_payroll_structure') + self.structure_type.default_struct_id = self.structure + # self.debug = True + self._log('PE structue_type %s %s and structure %s %s' % (self.structure_type, self.structure_type.name, self.structure, self.structure.name)) + self.country_pe = self.env.ref('base.pe') + + def _createEmployee(self): + employee = super()._createEmployee() + employee.country_id = self.country_pe + return employee + + def _createContract(self, employee, **kwargs): + if not 'schedule_pay' in kwargs: + kwargs['schedule_pay'] = 'monthly' + + config_model = self.env['hr.contract.pe_payroll_config'] + schedule_pay = kwargs['schedule_pay'] + config_values = { + 'name': 'Test Config Values', + 'employee_id': employee.id, + } + 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(config_model, key): + config_values[key] = val + found = True + if hasattr(self.contract_model, key): + contract_values[key] = val + found = True + if not found: + self._logger.warning('cannot locate attribute names "%s" on hr.contract().' % (key, )) + + # PE Payroll Config Defaults Should be set on the Model + if 'date_hired' not in config_values: + config_values['date_hired'] = '2016-01-01' + config = config_model.create(config_values) + contract_values['pe_payroll_config_id'] = config.id + + self._get_contract_defaults(contract_values) + contract = self.contract_model.create(contract_values) + + # Compatibility with Odoo 14 + contract.structure_type_id.default_struct_id.schedule_pay = schedule_pay + return contract 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..a3aa7a7f --- /dev/null +++ b/l10n_pe_hr_payroll/views/hr_contract_views.xml @@ -0,0 +1,21 @@ + + + + + hr.contract.form.inherit + hr.contract + + + + + + + + + + \ No newline at end of file diff --git a/l10n_pe_hr_payroll/views/pe_payroll_config_views.xml b/l10n_pe_hr_payroll/views/pe_payroll_config_views.xml new file mode 100644 index 00000000..4ae8e587 --- /dev/null +++ b/l10n_pe_hr_payroll/views/pe_payroll_config_views.xml @@ -0,0 +1,71 @@ + + + + hr.contract.pe_payroll_config.tree + hr.contract.pe_payroll_config + + + + + + + + + + + + + + hr.contract.pe_payroll_config.form + hr.contract.pe_payroll_config + +
+ + + + + + + + + + + + + + +

Not supported. Specify rule.

+ +
+
+
+
+
+
+ + + hr.contract.pe_payroll_config.search + hr.contract.pe_payroll_config + + + + + + + + + + Peru Employee Payroll Forms + hr.contract.pe_payroll_config + tree,form + +

+ No Forms +

+
+
+ + +
diff --git a/l10n_pe_hr_payroll_account/__init__.py b/l10n_pe_hr_payroll_account/__init__.py new file mode 100644 index 00000000..116ac075 --- /dev/null +++ b/l10n_pe_hr_payroll_account/__init__.py @@ -0,0 +1,13 @@ +# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details. + +from odoo import api, SUPERUSER_ID +from . import models + +def _post_install_hook_configure_journals(cr, registry): + """ + This method will create a salary journal for each company and allocate it to each Peru structure. + """ + env = api.Environment(cr, SUPERUSER_ID, {}) + companies = env['res.company'].search([('partner_id.country_id', '=', env.ref('base.pe').id)]) + for company in companies: + env['account.chart.template']._pe_configure_payroll_account_data(company) diff --git a/l10n_pe_hr_payroll_account/__manifest__.py b/l10n_pe_hr_payroll_account/__manifest__.py new file mode 100644 index 00000000..cd5bab95 --- /dev/null +++ b/l10n_pe_hr_payroll_account/__manifest__.py @@ -0,0 +1,25 @@ +# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details. + +{ + 'name': 'Peru - Payroll with Accounting', + 'author': 'Hibou Corp. ', + 'version': '15.0.2022.0.0', + 'category': 'Human Resources', + 'depends': [ + 'l10n_pe_hr_payroll', + 'hr_payroll_account', + ], + 'description': """ +Accounting Data for Peru Payroll Rules. +======================================= + """, + 'auto_install': True, + 'data': [ + 'data/l10n_pe_hr_payroll_account_data.xml', + ], + 'demo': [ + 'data/l10n_pe_hr_payroll_account_demo.xml', + ], + 'post_init_hook': '_post_install_hook_configure_journals', + 'license': 'OPL-1', +} diff --git a/l10n_pe_hr_payroll_account/data/l10n_pe_hr_payroll_account_data.xml b/l10n_pe_hr_payroll_account/data/l10n_pe_hr_payroll_account_data.xml new file mode 100644 index 00000000..b178e84c --- /dev/null +++ b/l10n_pe_hr_payroll_account/data/l10n_pe_hr_payroll_account_data.xml @@ -0,0 +1,52 @@ + + + + + + Peru Default Accounting + + + code + + if records: + companies = env.user.company_ids + # You can override the account codes with kwargs like: + # pay_ee='1234', + # pay_afp='1235', + # pay_onp='1236', + # pay_ir_4ta_cat='2222', + # pay_ir_5ta_cat='1238', + # pay_essalud='1239', + # exp_salary='1240', + # exp_com='1222', + # exp_bono='1223', + # exp_essalud='1241', + env['account.chart.template']._pe_configure_payroll_account_data(companies, salary_rules=records, full_reset=False) + + + + + Peru Default Accounting (Reset) + + + code + + if records: + companies = env.user.company_ids + # You can override the account codes with kwargs like: + # pay_ee='1234', + # pay_afp='1235', + # pay_onp='1236', + # pay_ir_4ta_cat='2222', + # pay_ir_5ta_cat='1238', + # pay_essalud='1239', + # exp_salary='1240', + # exp_com='1222', + # exp_bono='1223', + # exp_essalud='1241', + env['account.chart.template']._pe_configure_payroll_account_data(companies, salary_rules=records, full_reset=True) + + + + + diff --git a/l10n_pe_hr_payroll_account/data/l10n_pe_hr_payroll_account_demo.xml b/l10n_pe_hr_payroll_account/data/l10n_pe_hr_payroll_account_demo.xml new file mode 100644 index 00000000..58168767 --- /dev/null +++ b/l10n_pe_hr_payroll_account/data/l10n_pe_hr_payroll_account_demo.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/l10n_pe_hr_payroll_account/i18n/es.po b/l10n_pe_hr_payroll_account/i18n/es.po new file mode 100644 index 00000000..1ec384e8 --- /dev/null +++ b/l10n_pe_hr_payroll_account/i18n/es.po @@ -0,0 +1,31 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * l10n_pe_hr_payroll_account +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 13.0+e\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2022-04-29 22:13+0000\n" +"PO-Revision-Date: 2022-04-29 22:13+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: l10n_pe_hr_payroll_account +#: model:ir.model,name:l10n_pe_hr_payroll_account.model_account_chart_template +msgid "Account Chart Template" +msgstr "Plantila de Plan Contable" + +#. module: l10n_pe_hr_payroll_account +#: model:ir.actions.server,name:l10n_pe_hr_payroll_account.action_pe_salary_rule_account_set +msgid "Peru Default Accounting" +msgstr "Contabilidad para Perú por Defecto" + +#. module: l10n_pe_hr_payroll_account +#: model:ir.actions.server,name:l10n_pe_hr_payroll_account.action_pe_salary_rule_account_reset +msgid "Peru Default Accounting (Reset)" +msgstr "Contabilidad de Perú por Defecto (Resetear)" \ No newline at end of file diff --git a/l10n_pe_hr_payroll_account/models/__init__.py b/l10n_pe_hr_payroll_account/models/__init__.py new file mode 100644 index 00000000..21f7d3d7 --- /dev/null +++ b/l10n_pe_hr_payroll_account/models/__init__.py @@ -0,0 +1,3 @@ +# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details. + +from . import account_chart_template diff --git a/l10n_pe_hr_payroll_account/models/account_chart_template.py b/l10n_pe_hr_payroll_account/models/account_chart_template.py new file mode 100644 index 00000000..a572a058 --- /dev/null +++ b/l10n_pe_hr_payroll_account/models/account_chart_template.py @@ -0,0 +1,140 @@ +# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details. + +from odoo import models + +EXP_SALARY = '6211000' +EXP_COM = '6212000' +EXP_BONO = '6213000' +EXP_ESSALUD = '6271000' + +PAY_EE = '4111000' +PAY_AFP = '4170000' +PAY_ONP = '4032000' +PAY_IR_4TA_CAT = '4017200' +PAY_IR_5TA_CAT = '4017300' +PAY_ESSALUD = '4031000' + + +class AccountChartTemplate(models.Model): + _inherit = 'account.chart.template' + + def _load(self, sale_tax_rate, purchase_tax_rate, company): + """ + Override to configure payroll accounting data as well as accounting data. + """ + res = super()._load(sale_tax_rate, purchase_tax_rate, company) + self._pe_configure_payroll_account_data(company) + return res + + def _pe_configure_payroll_account_data(self, companies, + pay_ee=PAY_EE, + pay_afp=PAY_AFP, + pay_onp=PAY_ONP, + pay_ir_4ta_cat=PAY_IR_4TA_CAT, + pay_ir_5ta_cat=PAY_IR_5TA_CAT, + pay_essalud=PAY_ESSALUD, + exp_salary=EXP_SALARY, + exp_com=EXP_COM, + exp_bono=EXP_BONO, + exp_essalud=EXP_ESSALUD, + salary_rules=None, full_reset=False): + account_codes = ( + pay_ee, + pay_afp, + pay_onp, + pay_ir_4ta_cat, + pay_ir_5ta_cat, + pay_essalud, + exp_salary, + exp_com, + exp_bono, + exp_essalud, + ) + pe_structures = self.env['hr.payroll.structure'].search([('country_id', '=', self.env.ref('base.pe').id)]) + journal_field_id = self.env['ir.model.fields'].search([ + ('model', '=', 'hr.payroll.structure'), + ('name', '=', 'journal_id')], limit=1) + + for company in companies: + self = self.with_context({'allowed_company_ids': company.ids}) + accounts = { + code: self.env['account.account'].search( + [('company_id', '=', company.id), ('code', '=like', '%s%%' % code)], limit=1) + for code in account_codes + } + accounts['none'] = self.env['account.account'].browse() + + def set_rule_accounts(code, account_debit, account_credit): + rule_domain = [ + ('struct_id', 'in', pe_structures.ids), + ('code', '=like', code), + ] + if salary_rules: + rule_domain.append(('id', 'in', salary_rules.ids)) + rules = self.env['hr.salary.rule'].search(rule_domain) + if full_reset: + values = { + 'account_debit': account_debit.id, + 'account_credit': account_credit.id, + } + rules.write(values) + else: + # we need to ensure we do not update an account that is already set + for rule in rules: + values = {} + if account_debit and not rule.account_debit: + values['account_debit'] = account_debit.id + if account_credit and not rule.account_credit: + values['account_credit'] = account_credit.id + if values: + # save a write if no values to write + rule.write(values) + + journal = self.env['account.journal'].search([ + ('code', '=', 'PAYR'), + ('name', '=', 'Payroll'), + ('company_id', '=', company.id), + ]) + if journal: + if not journal.default_account_id: + journal.default_account_id = accounts[exp_salary].id + if hasattr(journal, 'payroll_entry_type'): + journal.payroll_entry_type = 'grouped' + else: + journal = self.env['account.journal'].create({ + 'name': 'Payroll', + 'code': 'PAYR', + 'type': 'general', + 'company_id': company.id, + 'default_account_id': accounts[exp_salary].id, + }) + if hasattr(journal, 'payroll_entry_type'): + journal.payroll_entry_type = 'grouped' + + self.env['ir.property']._set_multi( + "journal_id", + "hr.payroll.structure", + {structure.id: journal for structure in pe_structures}, + ) + + # Find rules and set accounts on them. + # Find all rules that are ... + + # BASIC* -> SALARY_EXPENSE debit account + set_rule_accounts('BASIC%', accounts[exp_salary], accounts['none']) + set_rule_accounts('BASIC_COM%', accounts[exp_com], accounts['none']) + set_rule_accounts('BASIC_BONO%', accounts[exp_bono], accounts['none']) + set_rule_accounts('BASIC_BADGES%', accounts[exp_bono], accounts['none']) + # ALW* -> SALARY_EXPENSE debit account + set_rule_accounts('ALW%', accounts[exp_salary], accounts['none']) + # EE_* -> AP debit + set_rule_accounts('EE_%', accounts[pay_ee], accounts['none']) # initialize + set_rule_accounts('EE_PE_AFP%', accounts[pay_afp], accounts['none']) + set_rule_accounts('EE_PE_ONP%', accounts[pay_onp], accounts['none']) + set_rule_accounts('EE_PE_IR_4TA_CAT%', accounts[pay_ir_4ta_cat], accounts['none']) + set_rule_accounts('EE_PE_IR_5TA_CAT%', accounts[pay_ir_5ta_cat], accounts['none']) + # ER_* -> AP debit, SE credit + set_rule_accounts('ER_%', accounts[pay_ee], accounts[exp_salary]) # initialize + set_rule_accounts('ER_PE_ESSALUD%', accounts[pay_essalud], accounts[exp_essalud]) + # NET* -> AP credit + set_rule_accounts('NET%', accounts['none'], accounts[pay_ee]) diff --git a/l10n_us_hr_payroll/models/hr_payslip.py b/l10n_us_hr_payroll/models/hr_payslip.py index 8f8b9422..ee0e1378 100644 --- a/l10n_us_hr_payroll/models/hr_payslip.py +++ b/l10n_us_hr_payroll/models/hr_payslip.py @@ -57,19 +57,6 @@ 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({ @@ -125,6 +112,3 @@ class HRPayslip(models.Model): 'wv_west_virginia_state_income_withholding': wv_west_virginia_state_income_withholding, }) return res - - def get_pay_periods_in_year(self): - return self.PAY_PERIODS_IN_YEAR.get(self.contract_id.schedule_pay, 0)