Skip to content
Permalink
Browse files

[IMP] l10n_be_hr_payroll: Improve 13th month structure

Purpose
=======

Currently, 13th month computation doesn't take into account
several things:

- An employee should not receive a 13th month if he arrive
after the 1st July

- The amount should be proportional to the employee seniority if
he arrived between the 1st January and 1st July.

- Several benefit type should be deducted from the 13th month:
  - Unpaid
  - Time Credit
  - Parental Time Off
  - Unpredictable Reason
  - Long Term Sick
  - Long Term Sick > 12 months
  - Part Time Sick
  - Notice

Specification
=============
Take into account points above
  • Loading branch information...
LucasLefevre committed Feb 20, 2019
1 parent 078d225 commit 597c5a9aaa2d9a0d18b5d714a940b9153b2ad3ef
@@ -29,11 +29,6 @@ def setUp(self):
})
self.richard_emp.resource_calendar_id = self.resource_calendar_id
self.richard_emp.contract_id = contract
self.benefit_type = self.env['hr.benefit.type'].create({
'name': 'Extra attendance',
'is_leave': False,
'code': 'WORK200'
})

def test_no_duplicate(self):
self.richard_emp.generate_benefit(self.start, self.end)
@@ -1,10 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="hr_payroll.hr_rule_basic" model="hr.salary.rule">
<field name="amount_python_compute">
result = contract.wage_with_holidays - payslip.unpaid_amount
</field>
</record>

<!-- CONTRIBUTION REGISTERS -->
<record id="res_partner_onss" model="res.partner">
@@ -205,7 +200,7 @@ wage = categories.BASIC
if employee.resident_bool:
result = 0.0
elif employee.marital in ['divorced', 'single', 'widower'] or (employee.marital in ['married', 'cohabitant'] and employee.spouse_fiscal_status=='without income'):
if wage &gt;= 0.01 and wage &lt;= 1095.09:
if wage &gt;= 0.0 and wage &lt;= 1095.09:
result = 0.0
elif wage &gt;= 1095.10 and wage &lt;= 1945.38:
result = 0.0
@@ -4,3 +4,4 @@
from . import l10n_be_hr_payroll
from . import res_config_settings
from . import res_users
from . import hr_benefit
@@ -0,0 +1,12 @@
from odoo import api, models


class BenefitType(models.Model):
_inherit = 'hr.benefit.type'

@api.model
def _load_from_xmlid(self, *xmlids):
benefit_types = self.env['hr.benefit.type']
for xmlid in xmlids:
benefit_types |= self.env.ref(xmlid, raise_if_not_found=False)
return benefit_types
@@ -1,9 +1,90 @@
# -*- coding:utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.

from dateutil import rrule
from dateutil.relativedelta import relativedelta
from datetime import date, datetime
from pytz import timezone
from odoo import api, fields, models, _

from odoo.exceptions import ValidationError
from odoo.osv.expression import AND


class HrPayslip(models.Model):
_inherit = 'hr.payslip'

def _get_13th_month_basic(self, struct_cp_200):
contracts = self.employee_id.contract_ids.filtered(lambda c: c.state not in ['draft', 'cancel'] and c.struct_id == struct_cp_200)
if not contracts:
return 0.0

# 1. Seniority: an employee must have a seniority of at least 6 months to be granted the 13th month
first_contract = min(contracts, key=lambda c: c.date_start)
start_of_service = first_contract.date_start
year = self.date_to.year
if start_of_service >= date(year, 7, 1):
return 0.0

# 2. Working months
n_months = 12
if start_of_service > date(year, 1, 1):
n_months = 12 - start_of_service.month
# only count full months
work_days = {int(d) for d in first_contract.resource_calendar_id.mapped('normal_attendance_ids.dayofweek')}
# working days between the 1st and the start of service (including start of service)
first_days_of_month = rrule.rrule(rrule.DAILY, byweekday=work_days, dtstart=start_of_service.replace(day=1), until=start_of_service)
if len(list(first_days_of_month)) <= 1:
n_months += 1

# 3. Deduct absences
unpaid_benefit_type = self.env['hr.benefit.type']._load_from_xmlid(
'hr_payroll.benefit_type_unpaid_leave',
'l10n_be_hr_payroll.benefit_type_credit',
'l10n_be_hr_payroll.benefit_type_unpredictable',
'l10n_be_hr_payroll.benefit_type_long_sick',
'l10n_be_hr_payroll.benefit_type_part_sick',
)
paid_benefit_type = self.env['hr.benefit.type']._load_from_xmlid(
'hr_payroll.benefit_type_attendance',
'hr_payroll.benefit_type_home_working',
'hr_payroll.benefit_type_legal_leave',
'hr_payroll.benefit_type_sick_leave',
'l10n_be_hr_payroll.benefit_type_additional_paid',
'l10n_be_hr_payroll.benefit_type_breast_feeding',
'l10n_be_hr_payroll.benefit_type_european',
'l10n_be_hr_payroll.benefit_type_extra_legal',
'l10n_be_hr_payroll.benefit_type_maternity',
'l10n_be_hr_payroll.benefit_type_notice',
'l10n_be_hr_payroll.benefit_type_paternity_company',
'l10n_be_hr_payroll.benefit_type_paternity_legal',
'l10n_be_hr_payroll.benefit_type_recovery',
'l10n_be_hr_payroll.benefit_type_recovery_additional',
'l10n_be_hr_payroll.benefit_type_small_unemployment',
'l10n_be_hr_payroll.benefit_type_training',
'l10n_be_hr_payroll.benefit_type_training_time_off',
)

paid_days = self.employee_id._get_contracts_benefit_days(self.date_from, self.date_to, paid_benefit_type)
unpaid_days = self.employee_id._get_contracts_benefit_days(self.date_from, self.date_to, unpaid_benefit_type)
presence_prorata = paid_days / (paid_days + unpaid_days)

basic = self.contract_id.wage_with_holidays
return basic * n_months / 12 * presence_prorata

@api.multi
def _get_basic(self):
self.ensure_one()
struct_13th_month = self.env.ref('l10n_be_hr_payroll.hr_payroll_salary_structure_end_of_year_bonus')
struct_double_holiday = self.env.ref('l10n_be_hr_payroll.hr_payroll_salary_structure_double_holiday_pay')
struct_cp_200 = self.env.ref('l10n_be_hr_payroll.hr_payroll_salary_structure_employee')

if self.struct_id == struct_13th_month:
return self._get_13th_month_basic(struct_cp_200)

elif self.struct_id in [struct_cp_200, struct_double_holiday]:
return self.contract_id.wage_with_holidays - self._get_unpaid_deduction()

return super(HrPayslip, self)._get_basic()


class HrContract(models.Model):
@@ -282,6 +363,24 @@ def _get_yearly_income(self, structures=None, code='BASIC'):
payslips = self.env['hr.payslip'].search(domain)
return sum(payslips.mapped('line_ids').filtered(lambda l: l.code == code).mapped('total'))

def _get_contracts_benefit_days(self, start, end, benefit_types):
"""
Return the number of days in the employee's calendar between two dates.
Only count days associated with provided benefit types.
For each contract between the two dates, use the contract's calendar to count
days within the contract time period.
"""
res = 0
contracts = self.contract_ids.filtered(lambda c: c.date_start < end and c.date_end or date.max > start and c.state in ['open', 'pending', 'close'])
for contract in contracts:
contract_start = max(contract.date_start, start)
contract_end = min(contract.date_end or date.max, end)
tz = timezone(self.tz)
contract_start = datetime.combine(contract_start, datetime.min.time()).replace(tzinfo=tz)
contract_end = datetime.combine(contract_end, datetime.max.time()).replace(tzinfo=tz)
res += self.get_benefit_days_data(benefit_types, contract_start, contract_end, calendar=contract.resource_calendar_id)['days']
return res

@api.depends('disabled_children_bool', 'disabled_children_number', 'children')
def _compute_dependent_children(self):
for employee in self:

0 comments on commit 597c5a9

Please sign in to comment.
You can’t perform that action at this time.