diff --git a/l10n_pe_hr_payroll/__manifest__.py b/l10n_pe_hr_payroll/__manifest__.py index 65927b42..0ae186ac 100644 --- a/l10n_pe_hr_payroll/__manifest__.py +++ b/l10n_pe_hr_payroll/__manifest__.py @@ -6,7 +6,7 @@ 'version': '13.0.2022.0.0', 'category': 'Payroll Localization', 'depends': [ - 'hr_payroll', + 'hr_payroll_hibou', 'hr_contract_reports', 'hibou_professional', ], @@ -17,11 +17,15 @@ Peru - Payroll Rules. """, 'data': [ + 'security/ir.model.access.csv', 'data/base.xml', 'data/integration_rules.xml', 'data/afp_rules.xml', + 'data/onp_rules.xml', + 'data/ir_5ta_cat_rules.xml', 'data/er_rules.xml', - # 'views/hr_contract_views.xml', + 'views/hr_contract_views.xml', + 'views/pe_payroll_config_views.xml', 'views/res_config_settings_views.xml', ], 'demo': [ diff --git a/l10n_pe_hr_payroll/data/afp_rules.xml b/l10n_pe_hr_payroll/data/afp_rules.xml index aa991d9f..9b5fc102 100644 --- a/l10n_pe_hr_payroll/data/afp_rules.xml +++ b/l10n_pe_hr_payroll/data/afp_rules.xml @@ -2,36 +2,20 @@ - - EE: AFP Pensiones - ee_afp_pensiones + + EE: AFP + ee_afp - - 10.0 - - - - - - EE: AFP Seguro - ee_afp_seguro - - - - 1.74 - - - - - - EE: AFP Comisión - ee_afp_comision - - - - 0.18 - + + { + # 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), + } + @@ -48,9 +32,9 @@ EE: PE AFP Pensiones EE_PE_AFP_PENSIONES python - result = categories.BASIC + result = categories.GROSS and contract.pe_payroll_config_value('retirement_type') == 'afp' code - result, result_rate = categories.BASIC, -payslip.rule_parameter('ee_afp_pensiones') + result, result_rate = categories.GROSS, -payslip.rule_parameter('ee_afp')[contract.pe_payroll_config_value('afp_type')][4] @@ -62,23 +46,44 @@ EE: PE AFP Seguro EE_PE_AFP_SEGURO python - result = categories.BASIC + result = categories.GROSS and contract.pe_payroll_config_value('retirement_type') == 'afp' code - result, result_rate = categories.BASIC, -payslip.rule_parameter('ee_afp_seguro') + +year = payslip.dict.get_year() +ytd_wage = payslip.sum_category('GROSS', str(year) + '-01-01', str(year+1) + '-01-01') +wage = categories.GROSS +wage_base = payslip.rule_parameter('ee_afp')[contract.pe_payroll_config_value('afp_type')][5] +rate = payslip.rule_parameter('ee_afp')[contract.pe_payroll_config_value('afp_type')][3] +result, result_rate = general_rate(payslip, wage, ytd_wage, wage_base=wage_base, rate=rate) + - + - EE: PE AFP Comisión - EE_PE_AFP_COMISION + EE: PE AFP Comisión Mixta + EE_PE_AFP_COMISION_MIXTA python - result = categories.BASIC + 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.BASIC, -payslip.rule_parameter('ee_afp_comision') + result, result_rate = categories.GROSS, -payslip.rule_parameter('ee_afp')[contract.pe_payroll_config_value('afp_type')][1] + + + + + + + + + EE: PE AFP Comisión (Non-Mixta) + 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 index 1a4d2236..fffaf5bf 100644 --- a/l10n_pe_hr_payroll/data/base.xml +++ b/l10n_pe_hr_payroll/data/base.xml @@ -29,4 +29,28 @@ + + + EE: ONP + EE_PE_ONP + + + + ER: ONP + ER_PE_ONP + + + + + + EE: IR 5ta Cat. + EE_PE_IR_5TA_CAT + + + + ER: IR 5ta Cat. + ER_PE_IR_5TA_CAT + + + 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..8d293ad7 --- /dev/null +++ b/l10n_pe_hr_payroll/data/ir_5ta_cat_rules.xml @@ -0,0 +1,80 @@ + + + + + + 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), + ] + + + + + + + IR 5ta Cat. + + + + + + + + EE: PE IR 5TA Cat. + EE_PE_IR_5TA_CAT + python + result = categories.GROSS + code + +# TODO normalize anual wage based on pay period +uit = payslip.rule_parameter('pe_uit') +wage = categories.GROSS +wage_year = wage * 12.0 +# additional 2 months +wage_year += wage * 2.0 +over_7uit = wage_year - (7.0 * uit) +if over_7uit <= 0.0: + result = 0.0 +else: + total_tax = 0.0 + last_uit = 0.0 + for _uit, rate in payslip.rule_parameter('ee_ir_5ta_cat'): + # marginal brackets + _uit = float(_uit) + if over_7uit > (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 + tax = -total_tax / 12.0 + result, result_rate = wage, (tax / wage * 100.0) + + + + + 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..b1d9df0c --- /dev/null +++ b/l10n_pe_hr_payroll/data/onp_rules.xml @@ -0,0 +1,36 @@ + + + + + + EE: ONP + ee_onp + + + + 13.0 + + + + + + + ONP + + + + + + + + EE: PE ONP/SNP + EE_PE_ONP + python + result = categories.GROSS and contract.pe_payroll_config_value('retirement_type') == 'onp' and rule == contract.pe_payroll_config_value('onp_rule_id') + code + result, result_rate = categories.GROSS, -payslip.rule_parameter('ee_onp') + + + + + diff --git a/l10n_pe_hr_payroll/models/__init__.py b/l10n_pe_hr_payroll/models/__init__.py index 34b51524..e45c0ad8 100644 --- a/l10n_pe_hr_payroll/models/__init__.py +++ b/l10n_pe_hr_payroll/models/__init__.py @@ -3,5 +3,6 @@ from . import browsable_object from . import hr_contract from . import hr_payslip +from . import pe_payroll_config from . import res_config_settings from . import update diff --git a/l10n_pe_hr_payroll/models/hr_contract.py b/l10n_pe_hr_payroll/models/hr_contract.py index 316411f1..b7980e38 100644 --- a/l10n_pe_hr_payroll/models/hr_contract.py +++ b/l10n_pe_hr_payroll/models/hr_contract.py @@ -1,33 +1,12 @@ # Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details. from odoo import api, fields, models -# from .us_payroll_config import FUTA_TYPE_NORMAL, \ -# FUTA_TYPE_BASIC, \ -# FUTA_TYPE_EXEMPT - - -# class HrPayrollStructureType(models.Model): -# _inherit = 'hr.payroll.structure.type' -# default_schedule_pay = fields.Selection(selection_add=[('semi-monthly', 'Semi-monthly')]) - - -# class HrPayrollStructure(models.Model): -# _inherit = 'hr.payroll.structure' -# schedule_pay = fields.Selection(selection_add=[('semi-monthly', 'Semi-monthly')]) class PEHRContract(models.Model): _inherit = 'hr.contract' - - # FUTA_TYPE_NORMAL = FUTA_TYPE_NORMAL - # FUTA_TYPE_BASIC = FUTA_TYPE_BASIC - # FUTA_TYPE_EXEMPT = FUTA_TYPE_EXEMPT - - # 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] + + pe_payroll_config_id = fields.Many2one('hr.contract.pe_payroll_config', 'Payroll Forms') + + 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 index 90a27025..432337e2 100644 --- a/l10n_pe_hr_payroll/models/hr_payslip.py +++ b/l10n_pe_hr_payroll/models/hr_payslip.py @@ -2,133 +2,71 @@ from odoo import api, fields, models -# from .federal.fed_940 import er_us_940_futa -# from .federal.fed_941 import ee_us_941_fica_ss, \ -# ee_us_941_fica_m, \ -# ee_us_941_fica_m_add,\ -# er_us_941_fica_ss, \ -# er_us_941_fica_m, \ -# ee_us_941_fit -# from .state.general import general_state_unemployment, \ -# general_state_income_withholding, \ -# is_us_state -# from .state.al_alabama import al_alabama_state_income_withholding -# from .state.ar_arkansas import ar_arkansas_state_income_withholding -# from .state.az_arizona import az_arizona_state_income_withholding -# from .state.ca_california import ca_california_state_income_withholding -# from .state.co_colorado import co_colorado_state_income_withholding -# from .state.ct_connecticut import ct_connecticut_state_income_withholding -# from .state.de_delaware import de_delaware_state_income_withholding -# from .state.ga_georgia import ga_georgia_state_income_withholding -# from .state.hi_hawaii import hi_hawaii_state_income_withholding -# from .state.ia_iowa import ia_iowa_state_income_withholding -# from .state.id_idaho import id_idaho_state_income_withholding -# from .state.il_illinois import il_illinois_state_income_withholding -# from .state.in_indiana import in_indiana_state_income_withholding -# from .state.ks_kansas import ks_kansas_state_income_withholding -# from .state.ky_kentucky import ky_kentucky_state_income_withholding -# from .state.la_louisiana import la_louisiana_state_income_withholding -# from .state.me_maine import me_maine_state_income_withholding -# from .state.mi_michigan import mi_michigan_state_income_withholding -# from .state.mn_minnesota import mn_minnesota_state_income_withholding -# from .state.mo_missouri import mo_missouri_state_income_withholding -# from .state.ms_mississippi import ms_mississippi_state_income_withholding -# from .state.mt_montana import mt_montana_state_income_withholding -# from .state.nc_northcarolina import nc_northcarolina_state_income_withholding -# from .state.nd_north_dakota import nd_north_dakota_state_income_withholding -# from .state.ne_nebraska import ne_nebraska_state_income_withholding -# from .state.nj_newjersey import nj_newjersey_state_income_withholding -# from .state.nm_new_mexico import nm_new_mexico_state_income_withholding -# from .state.ny_new_york import ny_new_york_state_income_withholding -# from .state.oh_ohio import oh_ohio_state_income_withholding -# from .state.ok_oklahoma import ok_oklahoma_state_income_withholding -# from .state.ri_rhode_island import ri_rhode_island_state_income_withholding -# from .state.sc_south_carolina import sc_south_carolina_state_income_withholding -# from .state.ut_utah import ut_utah_state_income_withholding -# from .state.vt_vermont import vt_vermont_state_income_withholding -# from .state.va_virginia import va_virginia_state_income_withholding -# from .state.wa_washington import wa_washington_fml_er, \ -# wa_washington_fml_ee, \ -# wa_washington_cares_ee -# from .state.wi_wisconsin import wi_wisconsin_state_income_withholding -# from .state.wv_west_virginia import wv_west_virginia_state_income_withholding + +def _general_rate(payslip, wage, ytd_wage, wage_base=None, wage_start=None, rate=None): + """ + Function parameters: + wage_base, wage_start, rate can either be strings (rule_parameters) or floats + :return: result, result_rate(wage, percent) + """ + + # Resolve parameters. On exception, return (probably missing a year, would rather not have exception) + if wage_base and isinstance(wage_base, str): + try: + wage_base = payslip.rule_parameter(wage_base) + except (KeyError, UserError): + return 0.0, 0.0 + + if wage_start and isinstance(wage_start, str): + try: + wage_start = payslip.rule_parameter(wage_start) + except (KeyError, UserError): + return 0.0, 0.0 + + if rate and isinstance(rate, str): + try: + rate = payslip.rule_parameter(rate) + except (KeyError, UserError): + return 0.0, 0.0 + + if not rate: + return 0.0, 0.0 + else: + # Rate assumed positive percentage! + rate = -rate + + if wage_base: + remaining = wage_base - ytd_wage + if remaining < 0.0: + result = 0.0 + elif remaining < wage: + result = remaining + else: + result = wage + + # _logger.warn(' wage_base method result: ' + str(result) + ' rate: ' + str(rate)) + return result, rate + if wage_start: + if ytd_wage >= wage_start: + # _logger.warn(' wage_start 1 method result: ' + str(wage) + ' rate: ' + str(rate)) + return wage, rate + if ytd_wage + wage <= wage_start: + # _logger.warn(' wage_start 2 method result: ' + str(0.0) + ' rate: ' + str(0.0)) + return 0.0, 0.0 + # _logger.warn(' wage_start 3 method result: ' + str((wage - (wage_start - ytd_wage))) + ' rate: ' + str(rate)) + return (wage - (wage_start - ytd_wage)), rate + + # If the wage doesn't have a start or a base + # _logger.warn(' basic result: ' + str(wage) + ' rate: ' + str(rate)) + return wage, rate class HRPayslip(models.Model): _inherit = 'hr.payslip' - # From IRS Publication 15-T or logically (annually, bi-monthly) - PAY_PERIODS_IN_YEAR = { - 'annually': 1, - 'semi-annually': 2, - 'quarterly': 4, - 'bi-monthly': 6, - 'monthly': 12, - 'semi-monthly': 24, - 'bi-weekly': 26, - 'weekly': 52, - 'daily': 260, - } - def _get_base_local_dict(self): res = super()._get_base_local_dict() res.update({ - # 'er_us_940_futa': er_us_940_futa, - # 'ee_us_941_fica_ss': ee_us_941_fica_ss, - # 'ee_us_941_fica_m': ee_us_941_fica_m, - # 'ee_us_941_fica_m_add': ee_us_941_fica_m_add, - # 'er_us_941_fica_ss': er_us_941_fica_ss, - # 'er_us_941_fica_m': er_us_941_fica_m, - # 'ee_us_941_fit': ee_us_941_fit, - # 'general_state_unemployment': general_state_unemployment, - # 'general_state_income_withholding': general_state_income_withholding, - # 'is_us_state': is_us_state, - # 'al_alabama_state_income_withholding': al_alabama_state_income_withholding, - # 'ar_arkansas_state_income_withholding': ar_arkansas_state_income_withholding, - # 'az_arizona_state_income_withholding': az_arizona_state_income_withholding, - # 'ca_california_state_income_withholding': ca_california_state_income_withholding, - # 'co_colorado_state_income_withholding': co_colorado_state_income_withholding, - # 'ct_connecticut_state_income_withholding': ct_connecticut_state_income_withholding, - # 'de_delaware_state_income_withholding': de_delaware_state_income_withholding, - # 'ga_georgia_state_income_withholding': ga_georgia_state_income_withholding, - # 'hi_hawaii_state_income_withholding': hi_hawaii_state_income_withholding, - # 'ia_iowa_state_income_withholding': ia_iowa_state_income_withholding, - # 'id_idaho_state_income_withholding': id_idaho_state_income_withholding, - # 'il_illinois_state_income_withholding': il_illinois_state_income_withholding, - # 'in_indiana_state_income_withholding': in_indiana_state_income_withholding, - # 'ks_kansas_state_income_withholding': ks_kansas_state_income_withholding, - # 'ky_kentucky_state_income_withholding':ky_kentucky_state_income_withholding, - # 'la_louisiana_state_income_withholding': la_louisiana_state_income_withholding, - # 'me_maine_state_income_withholding': me_maine_state_income_withholding, - # 'mi_michigan_state_income_withholding': mi_michigan_state_income_withholding, - # 'mn_minnesota_state_income_withholding': mn_minnesota_state_income_withholding, - # 'mo_missouri_state_income_withholding': mo_missouri_state_income_withholding, - # 'ms_mississippi_state_income_withholding': ms_mississippi_state_income_withholding, - # 'mt_montana_state_income_withholding': mt_montana_state_income_withholding, - # 'nc_northcarolina_state_income_withholding': nc_northcarolina_state_income_withholding, - # 'nd_north_dakota_state_income_withholding': nd_north_dakota_state_income_withholding, - # 'ne_nebraska_state_income_withholding': ne_nebraska_state_income_withholding, - # 'nj_newjersey_state_income_withholding': nj_newjersey_state_income_withholding, - # 'nm_new_mexico_state_income_withholding': nm_new_mexico_state_income_withholding, - # 'ny_new_york_state_income_withholding': ny_new_york_state_income_withholding, - # 'oh_ohio_state_income_withholding': oh_ohio_state_income_withholding, - # 'ok_oklahoma_state_income_withholding': ok_oklahoma_state_income_withholding, - # 'ri_rhode_island_state_income_withholding': ri_rhode_island_state_income_withholding, - # 'sc_south_carolina_state_income_withholding': sc_south_carolina_state_income_withholding, - # 'ut_utah_state_income_withholding': ut_utah_state_income_withholding, - # 'vt_vermont_state_income_withholding': vt_vermont_state_income_withholding, - # 'va_virginia_state_income_withholding': va_virginia_state_income_withholding, - # 'wa_washington_fml_er': wa_washington_fml_er, - # 'wa_washington_fml_ee': wa_washington_fml_ee, - # 'wa_washington_cares_ee': wa_washington_cares_ee, - # 'wi_wisconsin_state_income_withholding': wi_wisconsin_state_income_withholding, - # 'wv_west_virginia_state_income_withholding': wv_west_virginia_state_income_withholding, + 'general_rate': _general_rate, }) return res - - def get_year(self): - # Helper method to get the year (normalized between Odoo Versions) - return self.date_to.year - - def get_pay_periods_in_year(self): - return self.PAY_PERIODS_IN_YEAR.get(self.contract_id.schedule_pay, 0) 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..ec7da289 --- /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) + + retirement_type = fields.Selection([ + ('afp', 'AFP'), + ('onp', 'ONP'), + ('retired', 'Retired'), + ], string='Retirement Type', required=True, default='afp') + + onp_rule_id = fields.Many2one('hr.salary.rule', string='ONP Rule', domain=[('code', '=like', 'EE_PE_ONP%')]) + + # AFP Type may actually be company specific.... + afp_type = fields.Selection([ + ('habitat', 'Habitat'), + ('integra', 'Integra'), + ('prima', 'Prima'), + ('profuturo', 'Profuturo'), + ], string='AFP Type', default='profuturo') + afp_comision_type = fields.Selection([ + ('mixta', 'Mixta'), + ('non_mixta', 'Non-Mixta'), + ], string='AFP Commission Type', default='mixta') + + comp_ss_type = fields.Selection([ + ('essalud', 'Essalud'), + ('eps', 'EPS'), + ], string='Company Social Services', default='essalud') + comp_ss_eps_rule_id = fields.Many2one('hr.salary.rule', string='Company Social Security EPS Rule') + comp_life_insurance_rule_id = fields.Many2one('hr.salary.rule', string='Company Life Insurance Rule') + comp_risk_insurance_rule_id = fields.Many2one('hr.salary.rule', string='Company Risk Insurance Rule') 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/tests/__init__.py b/l10n_pe_hr_payroll/tests/__init__.py index 761fda6c..05472054 100644 --- a/l10n_pe_hr_payroll/tests/__init__.py +++ b/l10n_pe_hr_payroll/tests/__init__.py @@ -5,4 +5,5 @@ # common remains for site specific tests from . import common -from . import test_2022 +from . import test_2020 +# from . import test_2022 diff --git a/l10n_pe_hr_payroll/tests/common.py b/l10n_pe_hr_payroll/tests/common.py index a47a2cfd..2eb46fa7 100755 --- a/l10n_pe_hr_payroll/tests/common.py +++ b/l10n_pe_hr_payroll/tests/common.py @@ -58,7 +58,12 @@ class TestPePayslip(common.TransactionCase): if not 'schedule_pay' in kwargs: kwargs['schedule_pay'] = 'monthly' schedule_pay = kwargs['schedule_pay'] + config_model = self.env['hr.contract.pe_payroll_config'] contract_model = self.env['hr.contract'] + config_values = { + 'name': 'Test Config Values', + 'employee_id': employee.id, + } contract_values = { 'name': 'Test Contract', 'employee_id': employee.id, @@ -69,12 +74,18 @@ class TestPePayslip(common.TransactionCase): if hasattr(val, 'id'): val = val.id found = False + if hasattr(config_model, key): + config_values[key] = val + found = True if hasattr(contract_model, key): contract_values[key] = val found = True if not found: self._logger.warn('cannot locate attribute names "%s" on contract' % (key, )) + # PE Payroll Config Defaults Should be set on the Model + config = config_model.create(config_values) + contract_values['pe_payroll_config_id'] = config.id # Some Basic Defaults if not contract_values.get('state'): diff --git a/l10n_pe_hr_payroll/tests/test_2020.py b/l10n_pe_hr_payroll/tests/test_2020.py new file mode 100644 index 00000000..6e72d03e --- /dev/null +++ b/l10n_pe_hr_payroll/tests/test_2020.py @@ -0,0 +1,142 @@ +# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details. + +from .common import TestPePayslip, process_payslip + + +class Test2020(TestPePayslip): + + ### + # 2020 Taxes and Rates + ### + + def test_2020_taxes(self): + # High salary to hit the maximum for AFP_SEGURO + salary = 8000.00 + + employee = self._createEmployee() + + contract = self._createContract(employee, + wage=salary, + retirement_type='afp', + afp_type='profuturo', + afp_comision_type='mixta', + comp_ss_type='essalud', + ) + self._log(contract.read()) + + self._log('2020 tax first payslip:') + payslip = self._createPayslip(employee, '2020-01-01', '2020-01-31') + payslip.compute_sheet() + + cats = self._getCategories(payslip) + rules = self._getRules(payslip) + # Employee + self.assertPayrollEqual(cats['GROSS'], salary) + self.assertPayrollEqual(rules['EE_PE_AFP_PENSIONES'], -cats['GROSS'] * (10.0 / 100.0)) + self.assertPayrollEqual(rules['EE_PE_AFP_SEGURO'], -cats['GROSS'] * (1.35 / 100.0)) + self.assertPayrollEqual(rules['EE_PE_AFP_COMISION_MIXTA'], -cats['GROSS'] * (0.67 / 100.0)) + # Employer + self.assertPayrollEqual(rules['ER_PE_ESSALUD'], -cats['GROSS'] * (6.75 / 100.0)) + + process_payslip(payslip) + + self._log('2020 tax second payslip:') + payslip = self._createPayslip(employee, '2020-02-01', '2020-02-28') + payslip.compute_sheet() + + cats = self._getCategories(payslip) + rules = self._getRules(payslip) + # Employee + self.assertPayrollEqual(cats['GROSS'], salary) + self.assertPayrollEqual(rules['EE_PE_AFP_PENSIONES'], -cats['GROSS'] * (10.0 / 100.0)) + + self.assertTrue(cats['GROSS'] < 9707.03) + # Seguro has a wage base. + second_seguro = -(9707.03 - cats['GROSS']) * (1.35 / 100.0) + self.assertPayrollEqual(rules['EE_PE_AFP_SEGURO'], second_seguro) + self.assertPayrollEqual(rules['EE_PE_AFP_COMISION_MIXTA'], -cats['GROSS'] * (0.67 / 100.0)) + # Employer + self.assertPayrollEqual(rules['ER_PE_ESSALUD'], -cats['GROSS'] * (6.75 / 100.0)) + + process_payslip(payslip) + + self._log('2020 tax third payslip:') + payslip = self._createPayslip(employee, '2020-03-01', '2020-03-31') + payslip.compute_sheet() + + cats = self._getCategories(payslip) + rules = self._getRules(payslip) + # Employee + self.assertPayrollEqual(cats['GROSS'], salary) + self.assertPayrollEqual(rules['EE_PE_AFP_PENSIONES'], -cats['GROSS'] * (10.0 / 100.0)) + + self.assertTrue(cats['GROSS'] < 9707.03) + self.assertPayrollEqual(rules['EE_PE_AFP_SEGURO'], 0.0) + self.assertPayrollEqual(rules['EE_PE_AFP_COMISION_MIXTA'], -cats['GROSS'] * (0.67 / 100.0)) + # Employer + self.assertPayrollEqual(rules['ER_PE_ESSALUD'], -cats['GROSS'] * (6.75 / 100.0)) + + process_payslip(payslip) + + def test_2020_onp(self): + salary = 3500.00 + + employee = self._createEmployee() + + contract = self._createContract(employee, + wage=salary, + retirement_type='onp', + onp_rule_id=self.env.ref('l10n_pe_hr_payroll.hr_payroll_rule_ee_onp').id, + comp_ss_type='essalud', + ) + self._log(contract.read()) + + self._log('2020 tax first payslip:') + payslip = self._createPayslip(employee, '2020-01-01', '2020-01-31') + payslip.compute_sheet() + + cats = self._getCategories(payslip) + rules = self._getRules(payslip) + # Employee + self.assertPayrollEqual(cats['GROSS'], salary) + self.assertPayrollEqual(rules['EE_PE_AFP_PENSIONES'], 0.0) + self.assertPayrollEqual(rules['EE_PE_AFP_SEGURO'], 0.0) + self.assertPayrollEqual(rules['EE_PE_AFP_COMISION_MIXTA'], 0.0) + self.assertPayrollEqual(cats['EE_PE_ONP'], -cats['GROSS'] * (13.0 / 100.0)) + # Employer + self.assertPayrollEqual(rules['ER_PE_ESSALUD'], -cats['GROSS'] * (6.75 / 100.0)) + + process_payslip(payslip) + + def test_2020_ir_5ta_cat(self): + salary = 1500.00 + + employee = self._createEmployee() + + contract = self._createContract(employee, + wage=salary, + retirement_type='onp', + onp_rule_id=self.env.ref('l10n_pe_hr_payroll.hr_payroll_rule_ee_onp').id, + comp_ss_type='essalud', + ) + + self._log('2020 tax first payslip:') + payslip = self._createPayslip(employee, '2020-01-01', '2020-01-31') + payslip.compute_sheet() + + cats = self._getCategories(payslip) + rules = self._getRules(payslip) + self.assertPayrollEqual(cats['GROSS'], salary) + self.assertPayrollEqual(rules['EE_PE_IR_5TA_CAT'], 0.0) + payslip.state = 'cancel' + payslip.unlink() + + # larger salary to trigger calculation + salary = 3000.0 + contract.wage = salary + payslip = self._createPayslip(employee, '2020-01-01', '2020-01-31') + payslip.compute_sheet() + cats = self._getCategories(payslip) + rules = self._getRules(payslip) + self.assertPayrollEqual(cats['GROSS'], salary) + self.assertPayrollEqual(rules['EE_PE_IR_5TA_CAT'], -74.67) diff --git a/l10n_pe_hr_payroll/views/hr_contract_views.xml b/l10n_pe_hr_payroll/views/hr_contract_views.xml index b7532699..ece8dc38 100644 --- a/l10n_pe_hr_payroll/views/hr_contract_views.xml +++ b/l10n_pe_hr_payroll/views/hr_contract_views.xml @@ -1,17 +1,17 @@ - + hr.contract.form.inherit hr.contract - + attrs="{'invisible': [('structure_type_id', '!=', %(l10n_pe_hr_payroll.structure_type_employee)s)], + 'required': [('structure_type_id', '=', %(l10n_pe_hr_payroll.structure_type_employee)s)]}" + context="{'default_employee_id': employee_id}"/> 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..c2e86f30 --- /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 + +
+ + + + + + + + + + + + + + + + + + + + +
+
+
+ + + hr.contract.pe_payroll_config.search + hr.contract.pe_payroll_config + + + + + + + + + + Peru Employee Payroll Forms + hr.contract.pe_payroll_config + tree,form + +

+ No Forms +

+
+
+ + +