mirror of
https://gitlab.com/hibou-io/hibou-odoo/suite.git
synced 2025-01-20 12:37:31 +02:00
[IMP] l10n_pe_hr_payroll: ir_5ta_cat catch up and refactors
This commit is contained in:
@@ -31,6 +31,18 @@
|
||||
<field name="date_from" eval="datetime(2020, 1, 1).date()"/>
|
||||
</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 -->
|
||||
<record id="hr_payroll_rule_ee_ir_5ta_cat" model="hr.salary.rule">
|
||||
<field name="sequence" eval="196"/>
|
||||
@@ -39,42 +51,9 @@
|
||||
<field name="name">EE: PE IR 5TA Cat.</field>
|
||||
<field name="code">EE_PE_IR_5TA_CAT</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_python_compute">
|
||||
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 <= 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 / pay_periods_in_year
|
||||
result, result_rate = wage_period, (tax / wage_period * 100.0)</field>
|
||||
<field name="amount_python_compute">result, result_rate = ir_5ta_cat(payslip, categories, worked_days, inputs, BASIC)</field>
|
||||
<field name="appears_on_payslip" eval="True"/>
|
||||
</record>
|
||||
|
||||
|
||||
@@ -1,64 +1,8 @@
|
||||
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
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
|
||||
from .rules.general import _general_rate
|
||||
from .rules.ir_5ta_cat import ir_5ta_cat
|
||||
|
||||
|
||||
class HRPayslip(models.Model):
|
||||
@@ -68,5 +12,6 @@ class HRPayslip(models.Model):
|
||||
res = super()._get_base_local_dict()
|
||||
res.update({
|
||||
'general_rate': _general_rate,
|
||||
'ir_5ta_cat': ir_5ta_cat,
|
||||
})
|
||||
return res
|
||||
|
||||
@@ -9,6 +9,8 @@ class HRContractPEPayrollConfig(models.Model):
|
||||
|
||||
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 5TA CAT.')
|
||||
|
||||
retirement_type = fields.Selection([
|
||||
('afp', 'AFP'),
|
||||
|
||||
4
l10n_pe_hr_payroll/models/rules/__init__.py
Normal file
4
l10n_pe_hr_payroll/models/rules/__init__.py
Normal 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
|
||||
58
l10n_pe_hr_payroll/models/rules/general.py
Normal file
58
l10n_pe_hr_payroll/models/rules/general.py
Normal 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
|
||||
71
l10n_pe_hr_payroll/models/rules/ir_5ta_cat.py
Normal file
71
l10n_pe_hr_payroll/models/rules/ir_5ta_cat.py
Normal 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
|
||||
@@ -84,6 +84,8 @@ class TestPePayslip(common.TransactionCase):
|
||||
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
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
<field name="employee_id"/>
|
||||
<field name="name"/>
|
||||
<field name="retirement_type"/>
|
||||
<field name="date_hired"/>
|
||||
<field name="create_date"/>
|
||||
<field name="write_date"/>
|
||||
</tree>
|
||||
@@ -23,6 +24,7 @@
|
||||
<group name="General">
|
||||
<field name="employee_id"/>
|
||||
<field name="name"/>
|
||||
<field name="date_hired"/>
|
||||
</group>
|
||||
<group>
|
||||
<group name="employee" string="Employee">
|
||||
|
||||
Reference in New Issue
Block a user