[IMP] l10n_pe_hr_payroll: ir_5ta_cat catch up and refactors

This commit is contained in:
Jared Kipe
2022-04-29 19:12:30 +00:00
parent f71a2ca6b7
commit 24629e56e7
8 changed files with 156 additions and 93 deletions

View File

@@ -31,6 +31,18 @@
<field name="date_from" eval="datetime(2020, 1, 1).date()"/> <field name="date_from" eval="datetime(2020, 1, 1).date()"/>
</record> </record>
<record id="rule_parameter_ee_ir_5ta_cat_ley_29351" model="hr.rule.parameter">
<field name="name">EE: IR 5ta Cat. Bonificación Extraordinaria (Ley 29351)</field>
<field name="code">ee_ir_5ta_cat_ley_29351</field>
<field name="country_id" ref="base.pe"/>
</record>
<record id="rule_parameter_ee_ir_5ta_cat_ley_29351_2020" model="hr.rule.parameter.value">
<field name="parameter_value">9.0</field>
<field name="rule_parameter_id" ref="rule_parameter_ee_ir_5ta_cat_ley_29351"/>
<field name="date_from" eval="datetime(2020, 1, 1).date()"/>
</record>
<!-- EE Rules --> <!-- EE Rules -->
<record id="hr_payroll_rule_ee_ir_5ta_cat" model="hr.salary.rule"> <record id="hr_payroll_rule_ee_ir_5ta_cat" model="hr.salary.rule">
<field name="sequence" eval="196"/> <field name="sequence" eval="196"/>
@@ -39,42 +51,9 @@
<field name="name">EE: PE IR 5TA Cat.</field> <field name="name">EE: PE IR 5TA Cat.</field>
<field name="code">EE_PE_IR_5TA_CAT</field> <field name="code">EE_PE_IR_5TA_CAT</field>
<field name="condition_select">python</field> <field name="condition_select">python</field>
<field name="condition_python">result = categories.GROSS</field> <field name="condition_python">result, _ = ir_5ta_cat(payslip, categories, worked_days, inputs, BASIC)</field>
<field name="amount_select">code</field> <field name="amount_select">code</field>
<field name="amount_python_compute"> <field name="amount_python_compute">result, result_rate = ir_5ta_cat(payslip, categories, worked_days, inputs, BASIC)</field>
pay_periods_in_year = payslip.pay_periods_in_year
uit = payslip.rule_parameter('pe_uit')
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_2 = wage_year * (1/6)
wage_3 = wage_2 * 0.09 # TODO paramatarize 9% # 2 months 2/12
wage_year += wage_2 + wage_3
wage_year += period_additional_wage
over_7uit = wage_year - (7.0 * uit)
if over_7uit &lt;= 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 &gt; (last_uit * uit):
eligible_wage = min(over_7uit, _uit * uit) - (last_uit * uit)
if eligible_wage &gt; 0.0:
total_tax += eligible_wage * (rate / 100.0)
else:
break
else:
break
last_uit = _uit
tax = -total_tax / pay_periods_in_year
result, result_rate = wage_period, (tax / wage_period * 100.0)</field>
<field name="appears_on_payslip" eval="True"/> <field name="appears_on_payslip" eval="True"/>
</record> </record>

View File

@@ -1,64 +1,8 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details. # Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
from odoo import api, fields, models from odoo import api, fields, models
from .rules.general import _general_rate
from .rules.ir_5ta_cat import ir_5ta_cat
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): class HRPayslip(models.Model):
@@ -68,5 +12,6 @@ class HRPayslip(models.Model):
res = super()._get_base_local_dict() res = super()._get_base_local_dict()
res.update({ res.update({
'general_rate': _general_rate, 'general_rate': _general_rate,
'ir_5ta_cat': ir_5ta_cat,
}) })
return res return res

View File

@@ -9,6 +9,8 @@ class HRContractPEPayrollConfig(models.Model):
name = fields.Char(string="Description") name = fields.Char(string="Description")
employee_id = fields.Many2one('hr.employee', string="Employee", required=True) 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 5TA CAT.')
retirement_type = fields.Selection([ retirement_type = fields.Selection([
('afp', 'AFP'), ('afp', 'AFP'),

View File

@@ -0,0 +1,4 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
from . import general
from . import ir_5ta_cat

View File

@@ -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

View File

@@ -0,0 +1,71 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
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')
# there are two special scenarios
# 1. 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
# 2. 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
last_payslip_december = payslip_date_end.month == 12 and payslip_date_end.day == 31
# basic_wage = BASIC # must be paramatarized as we will not have locals
wage_period = categories.GROSS
if not all((basic_wage, wage_period)) and not any((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
wage_year = basic_wage * pay_periods_in_year
# this can be reduced
remaining_months = 12
if hired_in_year:
# e.g. hired in March (3) gives us 12 - 3 + 1 = 10 (Jan, Feb are the 2 missing from 12)
remaining_months = 12 - date_hired.month + 1
# additional 2 months (July and December) (note 2/12 == 1/6)
wage_2 = wage_year * (1/6)
if remaining_months < 6:
wage_2 = wage_year * (1/12)
wage_3 = wage_2 * (payslip.rule_parameter('ee_ir_5ta_cat_ley_29351') / 100.0)
wage_year += wage_2 + wage_3
# now that we have wage_year, we may need to adjust it by remaining months
wage_year = wage_year * remaining_months / 12 # could be 12/12
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:
if last_payslip_june or last_payslip_december:
year = payslip_date_end.year
ytd_tax = -payslip.sum_category('EE_PE_IR_5TA_CAT', str(year) + '-01-01', str(year+1) + '-01-01')
if last_payslip_june:
total_tax /= 2
# remaining_tax may flip signs
remaining_tax = -(total_tax - ytd_tax)
return wage_period, (remaining_tax / wage_period * 100.0)
tax = -total_tax / remaining_months # TODO needs to be normalized to periods in year if not monthly...
return wage_period, (tax / wage_period * 100.0)
return 0.0, 0.0

View File

@@ -84,6 +84,8 @@ class TestPePayslip(common.TransactionCase):
self._logger.warn('cannot locate attribute names "%s" on contract' % (key, )) self._logger.warn('cannot locate attribute names "%s" on contract' % (key, ))
# PE Payroll Config Defaults Should be set on the Model # 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) config = config_model.create(config_values)
contract_values['pe_payroll_config_id'] = config.id contract_values['pe_payroll_config_id'] = config.id

View File

@@ -8,6 +8,7 @@
<field name="employee_id"/> <field name="employee_id"/>
<field name="name"/> <field name="name"/>
<field name="retirement_type"/> <field name="retirement_type"/>
<field name="date_hired"/>
<field name="create_date"/> <field name="create_date"/>
<field name="write_date"/> <field name="write_date"/>
</tree> </tree>
@@ -23,6 +24,7 @@
<group name="General"> <group name="General">
<field name="employee_id"/> <field name="employee_id"/>
<field name="name"/> <field name="name"/>
<field name="date_hired"/>
</group> </group>
<group> <group>
<group name="employee" string="Employee"> <group name="employee" string="Employee">