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..e4416df3 --- /dev/null +++ b/l10n_pe_hr_payroll/data/base.xml @@ -0,0 +1,108 @@ + + + + + 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..5b0a35aa --- /dev/null +++ b/l10n_pe_hr_payroll/tests/common.py @@ -0,0 +1,156 @@ +# 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'] + config_model = self.env['hr.contract.pe_payroll_config'] + contract_model = self.env['hr.contract'] + config_values = { + 'name': 'Test Config Values', + 'employee_id': employee.id, + } + contract_values = { + 'name': 'Test Contract', + 'employee_id': employee.id, + } + + 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(contract_model, key): + contract_values[key] = val + found = True + if not found: + self._logger.warn('cannot locate attribute names "%s" on contract' % (key, )) + + # PE Payroll Config Defaults Should be set on the Model + 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 + + # 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/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 +

+
+
+ + +