diff --git a/hr_payroll_hibou/__manifest__.py b/hr_payroll_hibou/__manifest__.py index 41020cf0..31c5b903 100644 --- a/hr_payroll_hibou/__manifest__.py +++ b/hr_payroll_hibou/__manifest__.py @@ -3,7 +3,7 @@ { 'name': 'Hibou Payroll', 'author': 'Hibou Corp. ', - 'version': '13.0.2.0.0', + 'version': '13.0.2.1.0', 'category': 'Payroll Localization', 'depends': [ 'hr_payroll', diff --git a/hr_payroll_hibou/models/browsable_object.py b/hr_payroll_hibou/models/browsable_object.py index 60543eb8..1e727f1b 100644 --- a/hr_payroll_hibou/models/browsable_object.py +++ b/hr_payroll_hibou/models/browsable_object.py @@ -137,6 +137,11 @@ class Payslips(BrowsableObject): @property 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 diff --git a/hr_payroll_hibou/models/hr_payslip.py b/hr_payroll_hibou/models/hr_payslip.py index 323bd40f..95c6a3b9 100644 --- a/hr_payroll_hibou/models/hr_payslip.py +++ b/hr_payroll_hibou/models/hr_payslip.py @@ -7,6 +7,22 @@ from .browsable_object import BrowsableObject, InputLine, WorkedDays, Payslips 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_pay_periods_in_year(self): + return self.PAY_PERIODS_IN_YEAR.get(self.contract_id.schedule_pay, 0) + # We need to be able to support more complexity, # namely, that different employees will be paid by different wage types as 'salary' vs 'hourly' wage_type = fields.Selection(related='contract_id.wage_type') diff --git a/l10n_pe_hr_payroll/data/base.xml b/l10n_pe_hr_payroll/data/base.xml index fffaf5bf..bdb5a271 100644 --- a/l10n_pe_hr_payroll/data/base.xml +++ b/l10n_pe_hr_payroll/data/base.xml @@ -53,4 +53,34 @@ + + + EE: Essalud (rem) + EE_PE_ESSALUD + + + + EE: Essalud + ER_PE_ESSALUD + + + + + + Bono + 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 + + Bono + + + + diff --git a/l10n_pe_hr_payroll/data/er_rules.xml b/l10n_pe_hr_payroll/data/er_rules.xml index 36733690..169a025f 100644 --- a/l10n_pe_hr_payroll/data/er_rules.xml +++ b/l10n_pe_hr_payroll/data/er_rules.xml @@ -22,7 +22,7 @@ - + ER: PE Essalud ER_PE_ESSALUD python diff --git a/l10n_pe_hr_payroll/data/ir_5ta_cat_rules.xml b/l10n_pe_hr_payroll/data/ir_5ta_cat_rules.xml index 8d293ad7..ea3c3486 100644 --- a/l10n_pe_hr_payroll/data/ir_5ta_cat_rules.xml +++ b/l10n_pe_hr_payroll/data/ir_5ta_cat_rules.xml @@ -47,12 +47,17 @@ result = categories.GROSS code -# TODO normalize anual wage based on pay period +pay_periods_in_year = payslip.pay_periods_in_year uit = payslip.rule_parameter('pe_uit') -wage = categories.GROSS -wage_year = wage * 12.0 -# additional 2 months -wage_year += wage * 2.0 + +basic_wage = BASIC +wage_period = categories.GROSS +period_additional_wage = max(wage_period - basic_wage, 0.0) +wage_year = basic_wage * pay_periods_in_year +# additional 2 months (July and December) +wage_year += wage_year * (1/6) # 2 months 2/12 +wage_year += period_additional_wage + over_7uit = wage_year - (7.0 * uit) if over_7uit <= 0.0: result = 0.0 @@ -72,7 +77,7 @@ else: break last_uit = _uit tax = -total_tax / 12.0 - result, result_rate = wage, (tax / wage * 100.0) + result, result_rate = wage_period, (tax / wage_period * 100.0) diff --git a/l10n_pe_hr_payroll/models/__init__.py b/l10n_pe_hr_payroll/models/__init__.py index e45c0ad8..ce62c910 100644 --- a/l10n_pe_hr_payroll/models/__init__.py +++ b/l10n_pe_hr_payroll/models/__init__.py @@ -1,6 +1,5 @@ # Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details. -from . import browsable_object from . import hr_contract from . import hr_payslip from . import pe_payroll_config diff --git a/l10n_pe_hr_payroll/models/browsable_object.py b/l10n_pe_hr_payroll/models/browsable_object.py deleted file mode 100644 index bed067fd..00000000 --- a/l10n_pe_hr_payroll/models/browsable_object.py +++ /dev/null @@ -1,148 +0,0 @@ -# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details. - -from odoo import fields -from odoo.addons.hr_payroll.models import browsable_object - - -class BrowsableObject(object): - def __init__(self, employee_id, dict, env): - self.employee_id = employee_id - self.dict = dict - self.env = env - # Customization to allow changing the behavior of the discrete browsable objects. - # you can think of this as 'compiling' the query based on the configuration. - sum_field = env['ir.config_parameter'].sudo().get_param('hr_payroll.payslip.sum_behavior', 'date_from') - if sum_field == 'date' and 'date' not in env['hr.payslip']: - # missing attribute, closest by definition - sum_field = 'date_to' - if not sum_field: - sum_field = 'date_from' - self._compile_browsable_query(sum_field) - - def __getattr__(self, attr): - return attr in self.dict and self.dict.__getitem__(attr) or 0.0 - - def _compile_browsable_query(self, sum_field): - pass - - -class InputLine(BrowsableObject): - """a class that will be used into the python code, mainly for usability purposes""" - def _compile_browsable_query(self, sum_field): - self.__browsable_query = """ - SELECT sum(amount) as sum - FROM hr_payslip as hp, hr_payslip_input as pi - WHERE hp.employee_id = %s AND hp.state = 'done' - AND hp.{sum_field} >= %s AND hp.date_to <= %s AND hp.id = pi.payslip_id AND pi.code = %s""".format(sum_field=sum_field) - - def sum(self, code, from_date, to_date=None): - if to_date is None: - to_date = fields.Date.today() - self.env.cr.execute(self.__browsable_query, (self.employee_id, from_date, to_date, code)) - return self.env.cr.fetchone()[0] or 0.0 - - -class WorkedDays(BrowsableObject): - """a class that will be used into the python code, mainly for usability purposes""" - def _compile_browsable_query(self, sum_field): - self.__browsable_query = """ - SELECT sum(number_of_days) as number_of_days, sum(number_of_hours) as number_of_hours - FROM hr_payslip as hp, hr_payslip_worked_days as pi - WHERE hp.employee_id = %s AND hp.state = 'done' - AND hp.{sum_field} >= %s AND hp.date_to <= %s AND hp.id = pi.payslip_id AND pi.code = %s""".format(sum_field=sum_field) - - def _sum(self, code, from_date, to_date=None): - if to_date is None: - to_date = fields.Date.today() - self.env.cr.execute(self.__browsable_query, (self.employee_id, from_date, to_date, code)) - return self.env.cr.fetchone() - - def sum(self, code, from_date, to_date=None): - res = self._sum(code, from_date, to_date) - return res and res[0] or 0.0 - - def sum_hours(self, code, from_date, to_date=None): - res = self._sum(code, from_date, to_date) - return res and res[1] or 0.0 - - -class Payslips(BrowsableObject): - """a class that will be used into the python code, mainly for usability purposes""" - def _compile_browsable_query(self, sum_field): - # Note that the core odoo has this as `hp.credit_note = False` but what if it is NULL? - # reverse of the desired behavior. - self.__browsable_query_rule = """ - SELECT sum(case when hp.credit_note is not True then (pl.total) else (-pl.total) end) - FROM hr_payslip as hp, hr_payslip_line as pl - WHERE hp.employee_id = %s AND hp.state = 'done' - AND hp.{sum_field} >= %s AND hp.date_to <= %s AND hp.id = pl.slip_id AND pl.code = %s""".format(sum_field=sum_field) - # Original (non-recursive) - # self.__browsable_query_category = """ - # SELECT sum(case when hp.credit_note is not True then (pl.total) else (-pl.total) end) - # FROM hr_payslip as hp, hr_payslip_line as pl, hr_salary_rule_category as rc - # WHERE hp.employee_id = %s AND hp.state = 'done' - # AND hp.{sum_field} >= %s AND hp.date_to <= %s AND hp.id = pl.slip_id - # AND rc.id = pl.category_id AND rc.code = %s""".format(sum_field=sum_field) - - # Hibou Recursive version - self.__browsable_query_category = """ - WITH RECURSIVE - category_by_code as ( - SELECT id - FROM hr_salary_rule_category - WHERE code = %s - ), - category_ids as ( - SELECT COALESCE((SELECT id FROM category_by_code), -1) AS id - UNION ALL - SELECT rc.id - FROM hr_salary_rule_category AS rc - JOIN category_ids AS rcs ON rcs.id = rc.parent_id - ) - - SELECT sum(case when hp.credit_note is not True then (pl.total) else (-pl.total) end) - FROM hr_payslip as hp, hr_payslip_line as pl - WHERE hp.employee_id = %s AND hp.state = 'done' - AND hp.{sum_field} >= %s AND hp.date_to <= %s AND hp.id = pl.slip_id - AND pl.category_id in (SELECT id from category_ids)""".format(sum_field=sum_field) - - def sum(self, code, from_date, to_date=None): - if to_date is None: - to_date = fields.Date.today() - self.env.cr.execute(self.__browsable_query_rule, (self.employee_id, from_date, to_date, code)) - res = self.env.cr.fetchone() - return res and res[0] or 0.0 - - def rule_parameter(self, code): - return self.env['hr.rule.parameter']._get_parameter_from_code(code, self.dict.date_to) - - def sum_category(self, code, from_date, to_date=None): - if to_date is None: - to_date = fields.Date.today() - - self.env['hr.payslip'].flush(['credit_note', 'employee_id', 'state', 'date_from', 'date_to']) - self.env['hr.payslip.line'].flush(['total', 'slip_id', 'category_id']) - self.env['hr.salary.rule.category'].flush(['code']) - - # standard version - # self.env.cr.execute(self.__browsable_query_category, (self.employee_id, from_date, to_date, code)) - # recursive category version - self.env.cr.execute(self.__browsable_query_category, (code, self.employee_id, from_date, to_date)) - res = self.env.cr.fetchone() - return res and res[0] or 0.0 - - @property - def paid_amount(self): - return self.dict._get_paid_amount() - - -# Patch over Core -browsable_object.BrowsableObject.__init__ = BrowsableObject.__init__ -browsable_object.BrowsableObject._compile_browsable_query = BrowsableObject._compile_browsable_query -browsable_object.InputLine._compile_browsable_query = InputLine._compile_browsable_query -browsable_object.InputLine.sum = InputLine.sum -browsable_object.WorkedDays._compile_browsable_query = WorkedDays._compile_browsable_query -browsable_object.WorkedDays.sum = WorkedDays.sum -browsable_object.Payslips._compile_browsable_query = Payslips._compile_browsable_query -browsable_object.Payslips.sum = Payslips.sum -browsable_object.Payslips.sum_category = Payslips.sum_category diff --git a/l10n_pe_hr_payroll/tests/__init__.py b/l10n_pe_hr_payroll/tests/__init__.py index 05472054..7a934474 100644 --- a/l10n_pe_hr_payroll/tests/__init__.py +++ b/l10n_pe_hr_payroll/tests/__init__.py @@ -1,9 +1,6 @@ # Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details. -# TODO make/move to l10n_pe_hr_payroll_params -# Tests moved to `l10n_us_hr_payroll_params` +# Tests moved to `l10n_pe_hr_payroll_params` # common remains for site specific tests from . import common -from . import test_2020 -# from . import test_2022 diff --git a/l10n_pe_hr_payroll/tests/test_2020.py b/l10n_pe_hr_payroll/tests/test_2020.py deleted file mode 100644 index 6e72d03e..00000000 --- a/l10n_pe_hr_payroll/tests/test_2020.py +++ /dev/null @@ -1,142 +0,0 @@ -# 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/tests/test_2022.py b/l10n_pe_hr_payroll/tests/test_2022.py deleted file mode 100644 index 67499785..00000000 --- a/l10n_pe_hr_payroll/tests/test_2022.py +++ /dev/null @@ -1,42 +0,0 @@ -# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details. - -from .common import TestPePayslip, process_payslip - - -class Test2022(TestPePayslip): - - # AFP Constants - AFP_PENSIONES = 0.1 # 10% - AFP_SEGURO = 0.0174 # 1.74% - AFP_COMISION = 0.0018 # 0.18% - - # ER ESSALUD - ER_ESSALUD = 0.0675 # 6.75% - - ### - # 2022 Taxes and Rates - ### - - def test_2022_taxes(self): - salary = 3290.0 - - employee = self._createEmployee() - - contract = self._createContract(employee, wage=salary) - self._log(contract.read()) - - self._log('2022 tax first payslip:') - payslip = self._createPayslip(employee, '2022-01-01', '2022-01-31') - payslip.compute_sheet() - - cats = self._getCategories(payslip) - rules = self._getRules(payslip) - # Employee - self.assertPayrollEqual(cats['BASIC'], salary) - self.assertPayrollEqual(rules['EE_PE_AFP_PENSIONES'], -cats['BASIC'] * self.AFP_PENSIONES) - self.assertPayrollEqual(rules['EE_PE_AFP_SEGURO'], -cats['BASIC'] * self.AFP_SEGURO) - self.assertPayrollEqual(rules['EE_PE_AFP_COMISION'], -cats['BASIC'] * self.AFP_COMISION) - # Employer - self.assertPayrollEqual(rules['ER_PE_ESSALUD'], -cats['BASIC'] * self.ER_ESSALUD) - - process_payslip(payslip) diff --git a/l10n_pe_hr_payroll_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..39c2e347 --- /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': '13.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..0f65f6db --- /dev/null +++ b/l10n_pe_hr_payroll_account/data/l10n_pe_hr_payroll_account_data.xml @@ -0,0 +1,48 @@ + + + + + + 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_eps='1237', + # pay_ir_5ta_cat='1238', + # pay_essalud='1239', + # exp_salary='1240', + # 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_eps='1237', + # pay_ir_5ta_cat='1238', + # pay_essalud='1239', + # exp_salary='1240', + # 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/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..0bdc2f6f --- /dev/null +++ b/l10n_pe_hr_payroll_account/models/account_chart_template.py @@ -0,0 +1,136 @@ +# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details. + +from odoo import models + +EXP_SALARY = '6211000' +EXP_ESSALUD = '6271000' + +PAY_EE = '4111000' +PAY_AFP = '407' # TODO +PAY_AFP = '4032000' # TODO +PAY_ONP = '4032000' +PAY_EPS = '4075' # TODO EPS isn't implemented +PAY_IR_5TA_CAT = '4017' # TODO +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_eps=PAY_EPS, + pay_ir_5ta_cat=PAY_IR_5TA_CAT, + pay_essalud=PAY_ESSALUD, + exp_salary=EXP_SALARY, + exp_essalud=EXP_ESSALUD, + salary_rules=None, full_reset=False): + account_codes = ( + pay_ee, + pay_afp, + pay_onp, + pay_eps, + pay_ir_5ta_cat, + pay_essalud, + exp_salary, + 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_credit_account_id: + journal.default_credit_account_id = accounts[exp_salary].id + if not journal.default_debit_account_id: + journal.default_debit_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_credit_account_id': accounts[exp_salary].id, + 'default_debit_account_id': accounts[exp_salary].id, + }) + if hasattr(journal, 'payroll_entry_type'): + journal.payroll_entry_type = 'grouped' + + self.env['ir.property'].create([{ + 'name': 'structure_journal_id', + 'company_id': company.id, + 'fields_id': journal_field_id.id, + 'value_reference': 'account.journal,%s' % journal.id, + 'res_id': 'hr.payroll.structure,%s' % structure.id, + } 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']) + # 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_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])