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 b0a11869..b2ad02c1 100644
--- a/l10n_pe_hr_payroll/data/ir_5ta_cat_rules.xml
+++ b/l10n_pe_hr_payroll/data/ir_5ta_cat_rules.xml
@@ -31,6 +31,18 @@
+
+ EE: IR 5ta Cat. Bonificación Extraordinaria (Ley 29351)
+ ee_ir_5ta_cat_ley_29351
+
+
+
+ 9.0
+
+
+
+
+
@@ -39,42 +51,9 @@
EE: PE IR 5TA Cat.
EE_PE_IR_5TA_CAT
python
- result = categories.GROSS
+ result, _ = ir_5ta_cat(payslip, categories, worked_days, inputs, BASIC)
code
-
-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)
+ result, result_rate = ir_5ta_cat(payslip, categories, worked_days, inputs, BASIC)
diff --git a/l10n_pe_hr_payroll/models/hr_payslip.py b/l10n_pe_hr_payroll/models/hr_payslip.py
index 432337e2..2788cb61 100644
--- a/l10n_pe_hr_payroll/models/hr_payslip.py
+++ b/l10n_pe_hr_payroll/models/hr_payslip.py
@@ -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
diff --git a/l10n_pe_hr_payroll/models/pe_payroll_config.py b/l10n_pe_hr_payroll/models/pe_payroll_config.py
index 93adf0f0..1f87801d 100644
--- a/l10n_pe_hr_payroll/models/pe_payroll_config.py
+++ b/l10n_pe_hr_payroll/models/pe_payroll_config.py
@@ -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'),
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..12ea88cf
--- /dev/null
+++ b/l10n_pe_hr_payroll/models/rules/__init__.py
@@ -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
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_5ta_cat.py b/l10n_pe_hr_payroll/models/rules/ir_5ta_cat.py
new file mode 100644
index 00000000..9e726f55
--- /dev/null
+++ b/l10n_pe_hr_payroll/models/rules/ir_5ta_cat.py
@@ -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
diff --git a/l10n_pe_hr_payroll/tests/common.py b/l10n_pe_hr_payroll/tests/common.py
index 2eb46fa7..5b0a35aa 100755
--- a/l10n_pe_hr_payroll/tests/common.py
+++ b/l10n_pe_hr_payroll/tests/common.py
@@ -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
diff --git a/l10n_pe_hr_payroll/views/pe_payroll_config_views.xml b/l10n_pe_hr_payroll/views/pe_payroll_config_views.xml
index 4ac41567..4ae8e587 100644
--- a/l10n_pe_hr_payroll/views/pe_payroll_config_views.xml
+++ b/l10n_pe_hr_payroll/views/pe_payroll_config_views.xml
@@ -8,6 +8,7 @@
+
@@ -23,6 +24,7 @@
+