999 lines
55 KiB
Python
999 lines
55 KiB
Python
from collections import defaultdict
|
|
from datetime import datetime, date
|
|
from dateutil.relativedelta import relativedelta
|
|
|
|
from odoo import api, fields, models, _
|
|
from odoo.tools import float_round
|
|
from odoo.tools.float_utils import float_compare
|
|
from odoo.exceptions import ValidationError, UserError
|
|
|
|
EMPLOYER_ONSS = 0.2714
|
|
|
|
|
|
class HrVersion(models.Model):
|
|
_inherit = 'hr.version'
|
|
|
|
spouse_fiscal_status = fields.Selection([
|
|
('high_income', 'With High Income'),
|
|
('low_income', 'With Low Income'),
|
|
('without_income', 'Without Income'),
|
|
('high_pension', 'With High Pensions'),
|
|
('low_pension', 'With Low Pensions')
|
|
], string='Tax status for spouse', groups="hr.group_hr_user", default='high_income', required=False, tracking=True)
|
|
disabled_spouse_bool = fields.Boolean(string='Disabled Spouse', help='if recipient spouse is declared disabled by law', groups="hr.group_hr_user", tracking=True)
|
|
disabled_children_bool = fields.Boolean(string='Disabled Children', help='if recipient children is/are declared disabled by law', groups="hr.group_hr_user", tracking=True)
|
|
disabled_children_number = fields.Integer('Number of disabled children', groups="hr.group_hr_user", tracking=True)
|
|
dependent_children = fields.Integer(compute='_compute_dependent_children', string='Considered number of dependent children', groups="hr.group_hr_user")
|
|
l10n_be_dependent_children_attachment = fields.Integer(
|
|
string="# dependent children for salary attachement", groups="hr.group_hr_user", tracking=True,
|
|
help="""To benefit from this increase in the elusive or non-transferable quotas, the worker whose remuneration is subject to seizure or transfer, must declare it using a form, the model of which has been published in the Belgian Official Gazette. of 30 November 2006.
|
|
|
|
He must attach to this form the documents establishing the reality of the charge invoked.
|
|
|
|
Source: Opinion on the indexation of the amounts set in Article 1, paragraph 4, of the Royal Decree of 27 December 2004 implementing Articles 1409, § 1, paragraph 4, and 1409, § 1 bis, paragraph 4 , of the Judicial Code relating to the limitation of seizure when there are dependent children, MB, December 13, 2019.""")
|
|
other_dependent_people = fields.Boolean(string="Other Dependent People", help="If other people are dependent on the employee", groups="hr.group_hr_user", tracking=True)
|
|
other_senior_dependent = fields.Integer('# seniors (>=66)', help="Number of seniors dependent on the employee, including the disabled ones", groups="hr.group_hr_user", tracking=True)
|
|
other_disabled_senior_dependent = fields.Integer('# disabled seniors (>=66)', groups="hr.group_hr_user", tracking=True)
|
|
other_juniors_dependent = fields.Integer('# people (<66)', help="Number of juniors dependent on the employee, including the disabled ones", groups="hr.group_hr_user", tracking=True)
|
|
other_disabled_juniors_dependent = fields.Integer('# disabled people (<66)', groups="hr.group_hr_user", tracking=True)
|
|
dependent_seniors = fields.Integer(compute='_compute_dependent_people', string="Considered number of dependent seniors", groups="hr.group_hr_user")
|
|
dependent_juniors = fields.Integer(compute='_compute_dependent_people', string="Considered number of dependent juniors", groups="hr.group_hr_user")
|
|
fiscal_voluntarism = fields.Monetary(
|
|
string="Fiscal Voluntarism",
|
|
tracking=True,
|
|
groups="hr_payroll.group_hr_payroll_user")
|
|
|
|
transport_mode_car = fields.Boolean('Uses company car', groups="hr_payroll.group_hr_payroll_user", tracking=True)
|
|
transport_mode_private_car = fields.Boolean('Uses private car', groups="hr_payroll.group_hr_payroll_user", tracking=True)
|
|
transport_mode_train = fields.Boolean('Uses train transportation', groups="hr_payroll.group_hr_payroll_user", tracking=True)
|
|
transport_mode_public = fields.Boolean('Uses another public transportation', groups="hr_payroll.group_hr_payroll_user", tracking=True)
|
|
car_atn = fields.Monetary(string='Car BIK', help='Benefit in Kind (Company Car)', groups="hr_payroll.group_hr_payroll_user", tracking=True)
|
|
train_transport_employee_amount = fields.Monetary(
|
|
'Train transport paid by the employee (Monthly)', groups="hr_payroll.group_hr_payroll_user", tracking=True)
|
|
public_transport_employee_amount = fields.Monetary(
|
|
'Public transport paid by the employee (Monthly)', groups="hr_payroll.group_hr_payroll_user", tracking=True)
|
|
warrant_value_employee = fields.Monetary(
|
|
compute='_compute_commission_cost', string="Warrant monthly value for the employee", groups="hr_payroll.group_hr_payroll_user")
|
|
|
|
meal_voucher_paid_by_employer = fields.Monetary(
|
|
compute='_compute_meal_voucher_info', string="Meal Voucher Paid by Employer", groups="hr_payroll.group_hr_payroll_user")
|
|
meal_voucher_paid_monthly_by_employer = fields.Monetary(
|
|
compute='_compute_meal_voucher_info', groups="hr_payroll.group_hr_payroll_user")
|
|
company_car_total_depreciated_cost = fields.Monetary(groups="hr_payroll.group_hr_payroll_user", tracking=True)
|
|
private_car_reimbursed_amount = fields.Monetary(
|
|
compute='_compute_private_car_reimbursed_amount', groups="hr_payroll.group_hr_payroll_user")
|
|
train_transport_reimbursed_amount = fields.Monetary(
|
|
string='Train Transport Reimbursed amount', groups="hr_payroll.group_hr_payroll_user",
|
|
compute='_compute_train_transport_reimbursed_amount', readonly=False, store=True)
|
|
public_transport_reimbursed_amount = fields.Monetary(
|
|
string='Public Transport Reimbursed amount', groups="hr_payroll.group_hr_payroll_user",
|
|
compute='_compute_public_transport_reimbursed_amount', readonly=False, store=True)
|
|
warrants_cost = fields.Monetary(
|
|
compute='_compute_commission_cost', string="Warrant monthly cost for the employer", groups="hr_payroll.group_hr_payroll_user")
|
|
yearly_commission = fields.Monetary(compute='_compute_commission_cost', groups="hr_payroll.group_hr_payroll_user")
|
|
yearly_commission_cost = fields.Monetary(compute='_compute_commission_cost', groups="hr_payroll.group_hr_payroll_user")
|
|
|
|
# Advantages
|
|
commission_on_target = fields.Monetary(
|
|
string="Commission",
|
|
tracking=True,
|
|
help="Monthly gross amount that the employee receives if the target is reached.",
|
|
groups="hr_payroll.group_hr_payroll_user")
|
|
fuel_card = fields.Monetary(
|
|
string="Fuel Card",
|
|
tracking=True,
|
|
help="Monthly amount the employee receives on his fuel card.",
|
|
groups="hr_payroll.group_hr_payroll_user")
|
|
internet = fields.Monetary(
|
|
string="Internet Subscription",
|
|
tracking=True,
|
|
help="The employee's internet subcription will be paid up to this amount.",
|
|
groups="hr_payroll.group_hr_payroll_user")
|
|
representation_fees = fields.Monetary(
|
|
string="Expense Fees",
|
|
tracking=True,
|
|
help="Monthly net amount the employee receives to cover his representation fees.",
|
|
groups="hr_payroll.group_hr_payroll_user")
|
|
mobile = fields.Monetary(
|
|
string="Mobile Subscription",
|
|
tracking=True,
|
|
help="The employee's mobile subscription will be paid up to this amount.",
|
|
groups="hr_payroll.group_hr_payroll_user")
|
|
has_laptop = fields.Boolean(
|
|
string="Laptop",
|
|
tracking=True,
|
|
help="A benefit in kind is paid when the employee uses its laptop at home.",
|
|
groups="hr_payroll.group_hr_payroll_user")
|
|
has_bicycle = fields.Boolean(string="Bicycle to work", default=False, groups="hr.group_hr_user",
|
|
help="Use a bicycle as a transport mode to go to work", tracking=True)
|
|
meal_voucher_amount = fields.Monetary(
|
|
string="Meal Vouchers",
|
|
tracking=True,
|
|
help="Amount the employee receives in the form of meal vouchers per worked day.",
|
|
groups="hr_payroll.group_hr_payroll_user")
|
|
meal_voucher_average_monthly_amount = fields.Monetary(
|
|
compute="_compute_meal_voucher_info", groups="hr_payroll.group_hr_payroll_user")
|
|
eco_checks = fields.Monetary(
|
|
"Eco Vouchers", groups="hr_payroll.group_hr_payroll_user", tracking=True,
|
|
help="Yearly amount the employee receives in the form of eco vouchers.")
|
|
ip = fields.Boolean('Intellectual Property', default=False, tracking=True, groups="hr_payroll.group_hr_payroll_user")
|
|
ip_wage_rate = fields.Float(string="IP percentage", help="Should be between 0 and 100 %", groups="hr_payroll.group_hr_payroll_user", tracking=True)
|
|
ip_value = fields.Float(compute='_compute_ip_value', groups="hr_payroll.group_hr_payroll_user")
|
|
no_onss = fields.Boolean(string="No ONSS", groups="hr_payroll.group_hr_payroll_user", tracking=True)
|
|
no_withholding_taxes = fields.Boolean(groups="hr_payroll.group_hr_payroll_user", tracking=True)
|
|
rd_percentage = fields.Integer("Time in R&D", groups="hr_payroll.group_hr_payroll_user", tracking=True)
|
|
employee_age = fields.Integer('Age of Employee', compute='_compute_employee_age',
|
|
compute_sudo=True, groups="hr.group_hr_user", tracking=True)
|
|
l10n_be_impulsion_plan = fields.Selection([
|
|
('25yo', '< 25 years old'),
|
|
('12mo', '12 months +'),
|
|
('55yo', '55+ years old')], string="Impulsion Plan", groups="hr_payroll.group_hr_payroll_user", tracking=True)
|
|
l10n_be_onss_restructuring = fields.Boolean(
|
|
string="ONSS Reduction for Restructuring", groups="hr_payroll.group_hr_payroll_user", tracking=True)
|
|
|
|
has_hospital_insurance = fields.Boolean(string="Hospital Insurance", groups="hr_payroll.group_hr_payroll_user", tracking=True)
|
|
insured_relative_children = fields.Integer(string="# Insured Children < 19 y/o", groups="hr_payroll.group_hr_payroll_user", tracking=True)
|
|
insured_relative_adults = fields.Integer(string="# Insured Children >= 19 y/o", groups="hr_payroll.group_hr_payroll_user", tracking=True)
|
|
insured_relative_spouse = fields.Boolean(string="Insured Spouse", groups="hr_payroll.group_hr_payroll_user", tracking=True)
|
|
hospital_insurance_amount_per_child = fields.Float(string="Amount per Child", groups="hr_payroll.group_hr_payroll_user",
|
|
default=lambda self: float(self.env['ir.config_parameter'].sudo().get_param('hr_contract_salary.hospital_insurance_amount_child', default=7.2)), tracking=True)
|
|
hospital_insurance_amount_per_adult = fields.Float(string="Amount per Adult", groups="hr_payroll.group_hr_payroll_user",
|
|
default=lambda self: float(self.env['ir.config_parameter'].sudo().get_param('hr_contract_salary.hospital_insurance_amount_adult', default=20.5)), tracking=True)
|
|
insurance_amount = fields.Float(compute='_compute_insurance_amount', string="Insurance Amount", groups="hr_payroll.group_hr_payroll_user")
|
|
insured_relative_adults_total = fields.Integer(compute='_compute_insured_relative_adults_total', groups="hr_payroll.group_hr_payroll_user")
|
|
l10n_be_hospital_insurance_notes = fields.Text(
|
|
string="Hospital Insurance: Additional Info", groups="hr_payroll.group_hr_payroll_user", tracking=True)
|
|
|
|
wage_with_holidays = fields.Monetary(
|
|
string="Wage With Sacrifices", groups="hr_payroll.group_hr_payroll_user",
|
|
help="Adapted salary, according to the sacrifices defined on the contract (Example: Extra-legal time off, a percentage of the salary invested in a group insurance, etc...)", tracking=True)
|
|
# Group Insurance
|
|
l10n_be_group_insurance_rate = fields.Float(
|
|
string="Group Insurance Sacrifice Rate", tracking=True,
|
|
help="Should be between 0 and 100 %", groups="hr_payroll.group_hr_payroll_user")
|
|
l10n_be_group_insurance_amount = fields.Monetary(
|
|
compute='_compute_l10n_be_group_insurance_amount', store=True, groups="hr_payroll.group_hr_payroll_user")
|
|
l10n_be_group_insurance_cost = fields.Monetary(
|
|
compute='_compute_l10n_be_group_insurance_amount', store=True, groups="hr_payroll.group_hr_payroll_user")
|
|
# Ambulatory Insurance
|
|
l10n_be_has_ambulatory_insurance = fields.Boolean(
|
|
string="Ambulatory Insurance",
|
|
groups="hr_payroll.group_hr_payroll_user", tracking=True)
|
|
l10n_be_ambulatory_insured_children = fields.Integer(
|
|
string="Ambulatory: # Insured Children < 19 y/o",
|
|
groups="hr_payroll.group_hr_payroll_user", tracking=True)
|
|
l10n_be_ambulatory_insured_adults = fields.Integer(
|
|
string="Ambulatory: # Insured Children >= 19 y/o",
|
|
groups="hr_payroll.group_hr_payroll_user", tracking=True)
|
|
l10n_be_ambulatory_insured_spouse = fields.Boolean(
|
|
string="Ambulatory: Insured Spouse",
|
|
groups="hr_payroll.group_hr_payroll_user", tracking=True)
|
|
l10n_be_ambulatory_amount_per_child = fields.Float(
|
|
string="Ambulatory: Amount per Child", groups="hr_payroll.group_hr_payroll_user",
|
|
default=lambda self: float(self.env['ir.config_parameter'].sudo().get_param('hr_contract_salary.ambulatory_insurance_amount_child', default=7.2)), tracking=True)
|
|
l10n_be_ambulatory_amount_per_adult = fields.Float(
|
|
string="Ambulatory: Amount per Adult", groups="hr_payroll.group_hr_payroll_user",
|
|
default=lambda self: float(self.env['ir.config_parameter'].sudo().get_param('hr_contract_salary.ambulatory_insurance_amount_adult', default=20.5)), tracking=True)
|
|
l10n_be_ambulatory_insurance_amount = fields.Float(
|
|
compute='_compute_ambulatory_insurance_amount', string="Ambulatory: Insurance Amount",
|
|
groups="hr_payroll.group_hr_payroll_user", compute_sudo=True)
|
|
l10n_be_ambulatory_insured_adults_total = fields.Integer(
|
|
compute='_compute_ambulatory_insured_adults_total',
|
|
groups="hr_payroll.group_hr_payroll_user")
|
|
l10n_be_ambulatory_insurance_notes = fields.Text(
|
|
string="Ambulatory Insurance: Additional Info", groups="hr_payroll.group_hr_payroll_user", tracking=True)
|
|
|
|
l10n_be_mobility_budget = fields.Boolean(string="Mobility Budget", groups="hr_payroll.group_hr_payroll_user", tracking=True)
|
|
l10n_be_mobility_budget_amount = fields.Monetary(
|
|
string="Mobility Budget Amount", groups="hr_payroll.group_hr_payroll_user", tracking=True
|
|
)
|
|
l10n_be_mobility_budget_amount_monthly = fields.Monetary(
|
|
string="Mobility Budget Monthly Amount",
|
|
compute="_compute_l10n_be_mobility_budget_amount_monthly",
|
|
groups="hr_payroll.group_hr_payroll_user"
|
|
)
|
|
l10n_be_wage_with_mobility_budget = fields.Monetary(
|
|
tracking=True, string="Wage with Mobility Budget", groups="hr_payroll.group_hr_payroll_user"
|
|
)
|
|
|
|
l10n_be_is_below_scale = fields.Boolean(
|
|
string="Is below CP200 salary scale", compute='_compute_l10n_be_is_below_scale',
|
|
search='_search_l10n_be_is_below_scale', compute_sudo=True, groups="hr.group_hr_user")
|
|
l10n_be_is_below_scale_warning = fields.Char(
|
|
compute='_compute_l10n_be_is_below_scale', compute_sudo=True, groups="hr.group_hr_user")
|
|
l10n_be_canteen_cost = fields.Monetary(string="Canteen Cost", groups="hr_payroll.group_hr_payroll_user", tracking=True)
|
|
l10n_be_time_credit = fields.Boolean(
|
|
string="Credit time",
|
|
compute='_compute_l10n_be_time_credit',
|
|
groups="hr.group_hr_user",
|
|
tracking=True,
|
|
)
|
|
|
|
_check_percentage_ip_rate = models.Constraint(
|
|
'CHECK(ip_wage_rate >= 0 AND ip_wage_rate <= 100)',
|
|
"The IP rate on wage should be between 0 and 100.",
|
|
)
|
|
_check_percentage_group_insurance_rate = models.Constraint(
|
|
'CHECK(l10n_be_group_insurance_rate >= 0 AND l10n_be_group_insurance_rate <= 100)',
|
|
"The group insurance salary sacrifice rate on wage should be between 0 and 100.",
|
|
)
|
|
_positive_fiscal_voluntarism = models.Constraint(
|
|
'CHECK(fiscal_voluntarism >= 0)',
|
|
'The fiscal voluntarism should be above 0.')
|
|
|
|
@api.constrains('children', 'disabled_children_number',
|
|
'other_senior_dependent', 'other_disabled_senior_dependent',
|
|
'other_juniors_dependent', 'other_disabled_juniors_dependent',
|
|
'l10n_be_dependent_children_attachment')
|
|
def _check_dependent(self):
|
|
validation_error_message = []
|
|
for version in self:
|
|
if (version.children < 0 or version.disabled_children_number < 0 or
|
|
version.other_senior_dependent < 0 or version.other_disabled_senior_dependent < 0 or
|
|
version.other_juniors_dependent < 0 or version.other_disabled_juniors_dependent < 0 or
|
|
version.l10n_be_dependent_children_attachment < 0):
|
|
validation_error_message.append(_("Count of dependent people/children or disabled dependent people/children must be positive."))
|
|
if ((version.disabled_children_number > 0 and version.disabled_children_number > version.children) or
|
|
(version.other_disabled_senior_dependent > 0 and version.other_disabled_senior_dependent > version.other_senior_dependent) or
|
|
(version.other_disabled_juniors_dependent > 0 and version.other_disabled_juniors_dependent > version.other_juniors_dependent) or
|
|
(version.l10n_be_dependent_children_attachment > 0 and version.l10n_be_dependent_children_attachment > version.children)):
|
|
validation_error_message.append(_("Count of disabled dependent people/children must be less or equal to the number of dependent people/children."))
|
|
if validation_error_message:
|
|
raise ValidationError("\n".join(validation_error_message))
|
|
|
|
@api.constrains("l10n_be_group_insurance_rate")
|
|
def _check_l10n_be_group_insurance_rate(self):
|
|
for version in self:
|
|
if version.l10n_be_group_insurance_rate and (version.l10n_be_group_insurance_rate < 0 or version.l10n_be_group_insurance_rate > 100):
|
|
raise ValidationError(self.env._("Group insurance rate must be between 0 and 100."))
|
|
|
|
def _get_whitelist_fields_from_template(self):
|
|
whitelisted_fields = super()._get_whitelist_fields_from_template()
|
|
if self.env.company.country_id.code == "BE":
|
|
whitelisted_fields += [
|
|
"bike_id",
|
|
"car_atn",
|
|
"car_id",
|
|
"commission_on_target",
|
|
"contract_type_id",
|
|
"distance_home_work",
|
|
"distance_home_work_unit",
|
|
"eco_checks",
|
|
"employee_age",
|
|
"fiscal_voluntarism",
|
|
"fuel_card",
|
|
"has_hospital_insurance",
|
|
"has_laptop",
|
|
"hourly_wage",
|
|
"insurance_amount",
|
|
"internet",
|
|
"insured_relative_adults",
|
|
"insured_relative_children",
|
|
"insured_relative_spouse",
|
|
"ip",
|
|
"ip_wage_rate",
|
|
"l10n_be_ambulatory_insurance_amount",
|
|
"l10n_be_ambulatory_insurance_notes",
|
|
"l10n_be_ambulatory_insured_adults",
|
|
"l10n_be_ambulatory_insured_children",
|
|
"l10n_be_ambulatory_insured_spouse",
|
|
"l10n_be_canteen_cost",
|
|
"l10n_be_group_insurance_rate",
|
|
"l10n_be_has_ambulatory_insurance",
|
|
"l10n_be_hospital_insurance_notes",
|
|
"l10n_be_impulsion_plan",
|
|
"l10n_be_is_below_scale",
|
|
"l10n_be_is_below_scale_warning",
|
|
"l10n_be_mobility_budget",
|
|
"l10n_be_mobility_budget_amount",
|
|
"l10n_be_mobility_budget_amount_monthly",
|
|
"l10n_be_onss_restructuring",
|
|
"meal_voucher_amount",
|
|
"mobile",
|
|
"new_bike",
|
|
"new_bike_model_id",
|
|
"new_car",
|
|
"new_car_model_id",
|
|
"no_onss",
|
|
"no_withholding_taxes",
|
|
"private_car_reimbursed_amount",
|
|
"public_transport_employee_amount",
|
|
"public_transport_reimbursed_amount",
|
|
"rd_percentage",
|
|
"representation_fees",
|
|
"train_transport_employee_amount",
|
|
"train_transport_reimbursed_amount",
|
|
"transport_mode_bike",
|
|
"transport_mode_car",
|
|
"transport_mode_private_car",
|
|
"transport_mode_public",
|
|
"transport_mode_train",
|
|
"wage",
|
|
"wage_type",
|
|
]
|
|
return whitelisted_fields
|
|
|
|
def _get_marital_status_selection(self):
|
|
if self.env.company.country_id.code != "BE":
|
|
return super()._get_marital_status_selection()
|
|
return super()._get_marital_status_selection() + [
|
|
("separated", self.env._("Judicially Separated"))
|
|
]
|
|
|
|
def write(self, vals):
|
|
res = super().write(vals)
|
|
if vals.get('contract_date_start'):
|
|
self.employee_id.current_version_id.sudo().filtered(
|
|
lambda v: v._is_struct_from_country('BE') and v.contract_date_start
|
|
)._trigger_l10n_be_next_activities()
|
|
return res
|
|
|
|
@api.depends('disabled_children_bool', 'disabled_children_number', 'children')
|
|
def _compute_dependent_children(self):
|
|
for version in self:
|
|
if version.disabled_children_bool:
|
|
version.dependent_children = version.children + version.disabled_children_number
|
|
else:
|
|
version.dependent_children = version.children
|
|
|
|
@api.depends('other_dependent_people', 'other_senior_dependent',
|
|
'other_disabled_senior_dependent', 'other_juniors_dependent', 'other_disabled_juniors_dependent')
|
|
def _compute_dependent_people(self):
|
|
for version in self:
|
|
version.dependent_seniors = version.other_senior_dependent + version.other_disabled_senior_dependent
|
|
version.dependent_juniors = version.other_juniors_dependent + version.other_disabled_juniors_dependent
|
|
|
|
@api.depends('resource_calendar_id.attendance_ids.work_entry_type_id.l10n_be_is_time_credit')
|
|
def _compute_l10n_be_time_credit(self):
|
|
for version in self:
|
|
time_credit = any(attendance.work_entry_type_id.l10n_be_is_time_credit for attendance in version.resource_calendar_id.attendance_ids)
|
|
version.l10n_be_time_credit = time_credit
|
|
|
|
@api.depends(
|
|
'wage', 'contract_date_start', 'contract_date_end', 'employee_id.l10n_be_scale_seniority', 'job_id.l10n_be_scale_category',
|
|
'work_time_rate', 'l10n_be_time_credit', 'resource_calendar_id.work_time_rate')
|
|
def _compute_l10n_be_is_below_scale(self):
|
|
# Source: https://emploi.belgique.be/fr/themes/remuneration/salaires-minimums-par-sous-commission-paritaire/banque-de-donnees-salaires
|
|
student_stucture_type = self.env.ref('hr.structure_type_employee_cp200')
|
|
open_contracts = self.filtered(
|
|
lambda c: (c.contract_date_start and c.contract_date_start <= fields.Date.today())
|
|
and (not c.contract_date_end or c.contract_date_end >= fields.Date.today())
|
|
and c.company_id.country_id.code == 'BE'
|
|
and c.employee_id
|
|
and c._get_contract_wage()
|
|
and c.structure_type_id == student_stucture_type)
|
|
closed_contract = (self - open_contracts)
|
|
closed_contract.l10n_be_is_below_scale_warning = False
|
|
closed_contract.l10n_be_is_below_scale = False
|
|
category_mapping = {
|
|
'A': 0,
|
|
'B': 1,
|
|
'C': 2,
|
|
'D': 3,
|
|
}
|
|
for contract in open_contracts:
|
|
company_seniority = relativedelta(fields.Date.today(), contract.contract_date_start).years
|
|
if not company_seniority:
|
|
scales = self.env['hr.rule.parameter']._get_parameter_from_code('cp200_salary_scale_first_year', raise_if_not_found=False)
|
|
else:
|
|
scales = self.env['hr.rule.parameter']._get_parameter_from_code('cp200_salary_scale', raise_if_not_found=False)
|
|
if not scales:
|
|
# No existing scale (eg: contracts before 2021)
|
|
contract.l10n_be_is_below_scale = False
|
|
contract.l10n_be_is_below_scale_warning = False
|
|
continue
|
|
anterior_seniority = contract.employee_id.l10n_be_scale_seniority
|
|
seniority = anterior_seniority + company_seniority
|
|
category_index = category_mapping.get(contract.job_id.l10n_be_scale_category, 2)
|
|
seniority_scale = scales.get(seniority, scales[26])
|
|
min_wage = seniority_scale[category_index]
|
|
if contract.l10n_be_time_credit:
|
|
min_wage = min_wage * contract.work_time_rate
|
|
else:
|
|
min_wage = min_wage * contract.resource_calendar_id.work_time_rate / 100
|
|
if contract._get_contract_wage() < min_wage:
|
|
contract.l10n_be_is_below_scale = True
|
|
contract.l10n_be_is_below_scale_warning = _("The wage is under the minimum scale of %(amount)s€ for a seniority of %(years)s years.", amount=round(min_wage, 2), years=seniority)
|
|
else:
|
|
contract.l10n_be_is_below_scale = False
|
|
contract.l10n_be_is_below_scale_warning = False
|
|
|
|
@api.model
|
|
def _search_l10n_be_is_below_scale(self, operator, value):
|
|
if operator != 'in':
|
|
return NotImplemented
|
|
below_contracts = self.env['hr.version'].search(
|
|
[('state', 'in', ['draft', 'open'])]
|
|
).filtered(lambda c: c.company_id.country_id.code == 'BE' and c.l10n_be_is_below_scale)
|
|
return [('id', 'in', below_contracts.ids)]
|
|
|
|
@api.model
|
|
def _benefit_white_list(self):
|
|
return super()._benefit_white_list() + [
|
|
'insurance_amount',
|
|
'ip_value',
|
|
'l10n_be_ambulatory_insurance_amount',
|
|
'meal_voucher_paid_monthly_by_employer',
|
|
]
|
|
|
|
@api.onchange('has_hospital_insurance')
|
|
def _onchange_has_hospital_insurance(self):
|
|
if not self.has_hospital_insurance:
|
|
self.insured_relative_spouse = False
|
|
self.insured_relative_adults = 0
|
|
self.insured_relative_children = 0
|
|
|
|
@api.onchange('l10n_be_has_ambulatory_insurance')
|
|
def _onchange_l10n_be_has_ambulatory_insurance(self):
|
|
if not self.l10n_be_has_ambulatory_insurance:
|
|
self.l10n_be_ambulatory_insured_spouse = False
|
|
self.l10n_be_ambulatory_insured_adults = 0
|
|
self.l10n_be_ambulatory_insured_children = 0
|
|
|
|
@api.depends('has_hospital_insurance', 'insured_relative_adults', 'insured_relative_spouse')
|
|
def _compute_insured_relative_adults_total(self):
|
|
for version in self:
|
|
version.insured_relative_adults_total = (
|
|
int(version.has_hospital_insurance)
|
|
+ version.insured_relative_adults
|
|
+ int(version.insured_relative_spouse))
|
|
|
|
@api.model
|
|
def _get_insurance_amount(self, child_amount, child_count, adult_amount, adult_count):
|
|
return child_amount * child_count + adult_amount * adult_count
|
|
|
|
@api.depends(
|
|
'insured_relative_children', 'insured_relative_adults_total',
|
|
'hospital_insurance_amount_per_child', 'hospital_insurance_amount_per_adult')
|
|
def _compute_insurance_amount(self):
|
|
for version in self:
|
|
version.insurance_amount = version._get_insurance_amount(
|
|
version.hospital_insurance_amount_per_child,
|
|
version.insured_relative_children,
|
|
version.hospital_insurance_amount_per_adult,
|
|
version.insured_relative_adults_total)
|
|
|
|
@api.constrains('rd_percentage')
|
|
def _check_discount_percentage(self):
|
|
if self.filtered(lambda c: c.rd_percentage < 0 or c.rd_percentage > 100):
|
|
raise ValidationError(_('The time Percentage in R&D should be between 0-100'))
|
|
if self.env.context.get('salary_simulation'):
|
|
return
|
|
for version in self:
|
|
if version.rd_percentage and version.employee_id and version.employee_id.certificate not in ['civil_engineer', 'doctor', 'master', 'bachelor']:
|
|
raise ValidationError(_('Only employees with a Bachelor/Master/Doctor/Civil Engineer degree can benefit from the withholding taxes exemption.'))
|
|
|
|
@api.depends('ip', 'ip_wage_rate')
|
|
def _compute_ip_value(self):
|
|
for version in self:
|
|
version.ip_value = version.ip_wage_rate if version.ip else 0
|
|
|
|
@api.depends('commission_on_target')
|
|
def _compute_commission_cost(self):
|
|
for version in self:
|
|
version.warrants_cost = version.commission_on_target * 1.326 / 1.05
|
|
warrant_commission = version.warrants_cost * 3.0
|
|
cash_commission = version.commission_on_target * 9.0
|
|
version.yearly_commission_cost = warrant_commission + cash_commission * (1 + EMPLOYER_ONSS)
|
|
version.yearly_commission = warrant_commission + cash_commission
|
|
version.warrant_value_employee = version.commission_on_target * 1.326 * (1.00 - 0.535)
|
|
|
|
@api.depends('meal_voucher_amount')
|
|
def _compute_meal_voucher_info(self):
|
|
# The amount of the meal voucher is computed on the basis of the contribution
|
|
# of the employer and the employee. Indeed, the first can contribute up to a
|
|
# maximum of € 6.91 per check and per day provided, while the participation
|
|
# of the second must amount to a minimum of € 1.09.
|
|
for version in self:
|
|
version.meal_voucher_paid_by_employer = max(0, version.meal_voucher_amount - 1.09)
|
|
monthly_nb_meal_voucher = 220.0 / 12
|
|
version.meal_voucher_paid_monthly_by_employer = version.meal_voucher_paid_by_employer * monthly_nb_meal_voucher
|
|
version.meal_voucher_average_monthly_amount = version.meal_voucher_amount * monthly_nb_meal_voucher
|
|
|
|
@api.depends('train_transport_employee_amount')
|
|
def _compute_train_transport_reimbursed_amount(self):
|
|
for version in self:
|
|
version.train_transport_reimbursed_amount = version._get_train_transport_reimbursed_amount(version.train_transport_employee_amount)
|
|
|
|
def _get_train_transport_reimbursed_amount(self, amount):
|
|
return min(amount * 0.8, 311)
|
|
|
|
@api.depends('public_transport_employee_amount')
|
|
def _compute_public_transport_reimbursed_amount(self):
|
|
for version in self:
|
|
version.public_transport_reimbursed_amount = version._get_public_transport_reimbursed_amount(version.public_transport_employee_amount)
|
|
|
|
@api.depends('employee_id')
|
|
def _compute_employee_age(self):
|
|
for version in self:
|
|
if not version.employee_id or not version.employee_id.birthday:
|
|
version.employee_age = 0
|
|
else:
|
|
version.employee_age = relativedelta(fields.Date.today(), version.employee_id.birthday).years
|
|
|
|
def _get_public_transport_reimbursed_amount(self, amount):
|
|
# As of February 1st, 2020, reimbursement for non-train-based public transportation,
|
|
# when based on a flat fee, is computed as 71.8% of the actual cost, capped at the
|
|
# reimbursement for 7 km of train-based transportation (34.00 EUR)
|
|
# Source: http://www.cnt-nar.be/CCT-COORD/cct-019-09.pdf (Art. 4)
|
|
public_transport_max_amount = self.env['hr.rule.parameter'].sudo()._get_parameter_from_code(
|
|
'public_transport_max_amount', date=self.env.context.get('payslip_date') or date.today(), raise_if_not_found=False)
|
|
if not public_transport_max_amount:
|
|
public_transport_max_amount = 43
|
|
return min(amount * 0.718, public_transport_max_amount)
|
|
|
|
@api.depends('km_home_work', 'transport_mode_private_car')
|
|
def _compute_private_car_reimbursed_amount(self):
|
|
for version in self:
|
|
if version.transport_mode_private_car:
|
|
amount = self._get_private_car_reimbursed_amount(version.km_home_work)
|
|
else:
|
|
amount = 0.0
|
|
version.private_car_reimbursed_amount = amount
|
|
|
|
@api.onchange('transport_mode_car', 'transport_mode_train', 'transport_mode_public')
|
|
def _onchange_transport_mode(self):
|
|
if not self.transport_mode_car:
|
|
self.fuel_card = 0
|
|
self.company_car_total_depreciated_cost = 0
|
|
if not self.transport_mode_train:
|
|
self.train_transport_employee_amount = 0
|
|
self.train_transport_reimbursed_amount = 0
|
|
if not self.transport_mode_public:
|
|
self.public_transport_employee_amount = 0
|
|
self.public_transport_reimbursed_amount = 0
|
|
if self.transport_mode_car:
|
|
self.transport_mode_private_car = False
|
|
|
|
@api.onchange('transport_mode_private_car')
|
|
def _onchange_transport_mode_private_car(self):
|
|
if self.transport_mode_private_car:
|
|
self.transport_mode_car = False
|
|
self.fuel_card = 0
|
|
|
|
@api.depends('holidays', 'wage', 'final_yearly_costs', 'l10n_be_group_insurance_rate')
|
|
def _compute_wage_with_holidays(self):
|
|
super()._compute_wage_with_holidays()
|
|
|
|
@api.depends('wage', 'l10n_be_group_insurance_rate')
|
|
def _compute_l10n_be_group_insurance_amount(self):
|
|
for version in self:
|
|
rate = version.l10n_be_group_insurance_rate
|
|
insurance_amount = version.wage * rate / 100.0
|
|
version.l10n_be_group_insurance_amount = insurance_amount
|
|
# Example
|
|
# 5 % salary configurator
|
|
# 4.4 % insurance cost
|
|
# 8.86 % ONSS
|
|
# =-----------------------
|
|
# 13.26 % over the 5%
|
|
version.l10n_be_group_insurance_cost = insurance_amount * (1 + 13.26 / 100.0)
|
|
|
|
def _is_salary_sacrifice(self):
|
|
self.ensure_one()
|
|
return super()._is_salary_sacrifice() or self.l10n_be_group_insurance_rate
|
|
|
|
def _get_yearly_cost_sacrifice_fixed(self):
|
|
return super()._get_yearly_cost_sacrifice_fixed() + self._get_salary_costs_factor() * self.wage * self.l10n_be_group_insurance_rate / 100
|
|
|
|
@api.depends('schedule_pay')
|
|
def _compute_final_yearly_costs(self):
|
|
return super()._compute_final_yearly_costs()
|
|
|
|
def _get_salary_costs_factor(self):
|
|
self.ensure_one()
|
|
res = super()._get_salary_costs_factor()
|
|
if self.structure_type_id == self.env.ref('hr.structure_type_employee_cp200'):
|
|
res = 13.92
|
|
if not self.no_onss:
|
|
res += 13.0 * EMPLOYER_ONSS
|
|
if self.l10n_be_group_insurance_rate:
|
|
return res * (1.0 - self.l10n_be_group_insurance_rate / 100)
|
|
return res
|
|
|
|
@api.depends(
|
|
'l10n_be_has_ambulatory_insurance',
|
|
'l10n_be_ambulatory_insured_adults',
|
|
'l10n_be_ambulatory_insured_spouse')
|
|
def _compute_ambulatory_insured_adults_total(self):
|
|
for version in self:
|
|
version.l10n_be_ambulatory_insured_adults_total = (
|
|
int(version.l10n_be_has_ambulatory_insurance)
|
|
+ version.l10n_be_ambulatory_insured_adults
|
|
+ int(version.l10n_be_ambulatory_insured_spouse))
|
|
|
|
@api.model
|
|
def _get_ambulatory_insurance_amount(self, child_amount, child_count, adult_amount, adult_count):
|
|
return child_amount * child_count + adult_amount * adult_count
|
|
|
|
@api.depends(
|
|
'l10n_be_ambulatory_insured_children', 'l10n_be_ambulatory_insured_adults_total',
|
|
'l10n_be_ambulatory_amount_per_child', 'l10n_be_ambulatory_amount_per_adult')
|
|
def _compute_ambulatory_insurance_amount(self):
|
|
for version in self:
|
|
version.l10n_be_ambulatory_insurance_amount = version._get_ambulatory_insurance_amount(
|
|
version.l10n_be_ambulatory_amount_per_child,
|
|
version.l10n_be_ambulatory_insured_children,
|
|
version.l10n_be_ambulatory_amount_per_adult,
|
|
version.l10n_be_ambulatory_insured_adults_total)
|
|
|
|
@api.depends('l10n_be_mobility_budget_amount')
|
|
def _compute_l10n_be_mobility_budget_amount_monthly(self):
|
|
for version in self:
|
|
version.l10n_be_mobility_budget_amount_monthly = version.l10n_be_mobility_budget_amount / 12
|
|
|
|
@api.model
|
|
def _get_private_car_reimbursed_amount(self, distance):
|
|
# monthly train subscription amount => half is reimbursed
|
|
# Generally this is not mandatory
|
|
# See: https://emploi.belgique.be/fr/themes/remuneration/intervention-de-lemployeur-dans-les-frais-de-deplacement-domicile-lieu-de
|
|
# But this is the case for the CP200
|
|
# See: https://www.sfonds200.be/fonds-social/infos-sectorielles/frais-de-transport/prive-2020
|
|
private_car_reimbursement_scale = self.env['hr.rule.parameter'].sudo()._get_parameter_from_code(
|
|
'private_car_reimbursement_scale', date=self.env.context.get('payslip_date'), raise_if_not_found=False)
|
|
if not private_car_reimbursement_scale:
|
|
return 0
|
|
for distance_boundary, amount in private_car_reimbursement_scale:
|
|
if distance <= distance_boundary:
|
|
return amount / 2
|
|
return private_car_reimbursement_scale[-1][1] / 2
|
|
|
|
@api.model
|
|
def _generate_work_entries_postprocess_adapt_to_calendar(self, vals):
|
|
res = super()._generate_work_entries_postprocess_adapt_to_calendar(vals)
|
|
return res and not vals.get('l10n_be_is_time_credit')
|
|
|
|
def _preprocess_work_hours_data_split_half(self, work_data, date_from, date_to):
|
|
"""
|
|
Method is meant to be overriden, see l10n_be_hr_payroll_attendance
|
|
"""
|
|
return
|
|
|
|
def _get_work_hours_split_half(self, date_from, date_to, domain=None):
|
|
"""
|
|
Returns the amount (expressed in hours) of work
|
|
for a contract between two dates.
|
|
If called on multiple contracts, sum work amounts of each contract.
|
|
:param date_from: The start date
|
|
:param date_to: The end date
|
|
:returns: a dictionary {(half/full, work_entry_id_1): hours_1, (half/full, work_entry_id_2): hours_2}
|
|
"""
|
|
date_from = datetime.combine(date_from, datetime.min.time())
|
|
date_to = datetime.combine(date_to, datetime.max.time())
|
|
work_data = defaultdict(lambda: [0, 0]) # [days, hours]
|
|
|
|
# TODO DBE: Seems to return wrong value in case of flexible hours
|
|
attendances = self.resource_calendar_id.attendance_ids.filtered(lambda a: a.day_period != 'lunch')
|
|
mapped_data = defaultdict(lambda: 0)
|
|
for attendance in attendances:
|
|
mapped_data[attendance.week_type, attendance.dayofweek] += attendance.hour_to - attendance.hour_from
|
|
number_of_hours_full_day = max(mapped_data.values()) if attendances else 0
|
|
|
|
work_entries = self.env['hr.work.entry']._read_group(
|
|
self._get_work_hours_domain(date_from, date_to, domain=domain),
|
|
['date:day', 'work_entry_type_id'],
|
|
['duration:sum']
|
|
)
|
|
|
|
self._preprocess_work_hours_data_split_half(work_entries, date_from, date_to)
|
|
|
|
for _date_start_day, work_entry_type, duration_sum in work_entries:
|
|
work_entry_type_id = work_entry_type.id
|
|
if float_compare(duration_sum, number_of_hours_full_day, 2) != -1:
|
|
if number_of_hours_full_day:
|
|
number_of_days = float_round(duration_sum / number_of_hours_full_day, precision_rounding=1, rounding_method='HALF-UP')
|
|
else:
|
|
# If not supposed to work in calendar attendances, then there are not time offs
|
|
number_of_days = 1
|
|
work_data['full', work_entry_type_id][0] += number_of_days
|
|
work_data['full', work_entry_type_id][1] += duration_sum
|
|
else:
|
|
work_data['half', work_entry_type_id][0] += 0.5
|
|
work_data['half', work_entry_type_id][1] += duration_sum
|
|
return work_data
|
|
|
|
# override to add work_entry_type from leave
|
|
def _get_leave_work_entry_type_dates(self, leave, date_from, date_to, employee):
|
|
result = super()._get_leave_work_entry_type_dates(leave, date_from, date_to, employee)
|
|
if not self._is_struct_from_country('BE'):
|
|
return result
|
|
|
|
if result.code == "LEAVE500":
|
|
# The public holidays are paid only during the 14 first days of unemployment
|
|
unemployed_less_than_14_days_before = self.env['hr.leave'].search([
|
|
('employee_id', '=', self.employee_id.id),
|
|
('date_to', '>=', leave.date_from + relativedelta(days=-14)),
|
|
('date_from', '<=', leave.date_from),
|
|
('holiday_status_id.work_entry_type_id.code', 'in', ['LEAVE6666', 'LEAVE6665']),
|
|
('state', '=', 'validate'),
|
|
], order="date_from asc")
|
|
if unemployed_less_than_14_days_before:
|
|
is_unemployed = True
|
|
for offset in range(15):
|
|
day = leave.date_from + relativedelta(days=-offset)
|
|
if all(l.date_from > day or l.date_to < day for l in unemployed_less_than_14_days_before):
|
|
is_unemployed = False
|
|
if is_unemployed:
|
|
return unemployed_less_than_14_days_before[0].holiday_status_id.work_entry_type_id
|
|
|
|
# The public holidays are paid only during the period of 30 days following the start of the
|
|
# suspension of the employment contract due to illness or accident, work accident or
|
|
# occupational disease, pregnancy or childbirth leave, strike or lockout;
|
|
absent_less_than_X_days_before = self.env['hr.leave'].search([
|
|
('employee_id', '=', self.employee_id.id),
|
|
('date_to', '>=', leave.date_from + relativedelta(days=-30)),
|
|
('date_from', '<=', leave.date_from),
|
|
('holiday_status_id.work_entry_type_id.code', 'in', [
|
|
'LEAVE210', 'LEAVE220', 'LEAVE230', 'LEAVE115', 'LEAVE281', 'LEAVE280', 'LEAVE110', 'LEAVE214'
|
|
]),
|
|
('state', '=', 'validate'),
|
|
], order="date_from asc")
|
|
if absent_less_than_X_days_before:
|
|
unpaid_work_entry_type = absent_less_than_X_days_before[0].holiday_status_id.work_entry_type_id
|
|
if unpaid_work_entry_type.code == 'LEAVE110':
|
|
unpaid_work_entry_type = self.env.ref('hr_work_entry.l10n_be_work_entry_type_part_sick', False)
|
|
is_absent = True
|
|
# Special case for credit-times
|
|
# If time credit duration X is:
|
|
# X < 1 month -> Unpaid
|
|
# 1 <= X < 3 months -> Paid the first 14 days
|
|
# X >= 3 months -> Paid the first 30 days
|
|
# Always unpaid for full time credit time
|
|
paid_duration = 30
|
|
if self.l10n_be_time_credit:
|
|
if not self.work_time_rate:
|
|
return unpaid_work_entry_type
|
|
duration_start = self._get_occupation_dates()[0][1]
|
|
duration_stop = leave.date_from.date()
|
|
number_of_months = (duration_stop.year - duration_start.year) * 12 + (duration_stop.month - duration_start.month)
|
|
if number_of_months < 1:
|
|
return unpaid_work_entry_type
|
|
if number_of_months < 3:
|
|
paid_duration = 14
|
|
absent_less_than_X_days_before = absent_less_than_X_days_before.filtered_domain([
|
|
('date_to', '>=', leave.date_from + relativedelta(days=-paid_duration))])
|
|
for offset in range(paid_duration):
|
|
day = leave.date_from + relativedelta(days=-offset)
|
|
if all(l.date_from > day or l.date_to < day for l in absent_less_than_X_days_before):
|
|
is_absent = False
|
|
if is_absent:
|
|
return unpaid_work_entry_type
|
|
|
|
# The salary is not guaranteed after 30 calendar days of sick leave (it means from the 31th
|
|
# day of sick leave)
|
|
# LEAVE110 = classic sick leave
|
|
if result.code == "LEAVE110":
|
|
if not leave.holiday_id:
|
|
return result
|
|
|
|
sick_work_entry_type = self.env.ref("hr_work_entry.work_entry_type_sick_leave")
|
|
partial_sick_work_entry_type = self.env.ref("hr_work_entry.l10n_be_work_entry_type_part_sick")
|
|
long_sick_work_entry_type = self.env.ref("hr_work_entry.l10n_be_work_entry_type_long_sick")
|
|
sick_work_entry_types = sick_work_entry_type + partial_sick_work_entry_type + long_sick_work_entry_type
|
|
|
|
# In the following code, we will determine if the current day of the leave has
|
|
# a guaranteed salary or not. Days beyond the 30th calendar day of a sick leave
|
|
# do not have a guaranteed salary. A sickness can also span multiple sick leaves
|
|
# if the gap between the leaves is less than or equal to 14 days. To know if
|
|
# a sickness spans multiple leaves, it fetches all sick leaves prior to the current day.
|
|
# The code then loops through all the leaves, starting at the most recent one.
|
|
# The duration of the leaves is accumulated with the current leave until it either
|
|
# finds a gap bigger than 14 days, the sum exceeeds 30 days, or it finds a leave
|
|
# that is not a relapse.
|
|
|
|
# Example (all dates are inclusive):
|
|
# | Name | Date from | Date to | Relapse | Calendar Days | Work Days |
|
|
# | ---- | ----------- | ----------- | ------- | ------------- | ---------- |
|
|
# | 1st | 10/Jun/2024 | 12/Jun/2024 | False | 03 (c)days | 03 (w)days |
|
|
# | 2nd | 01/Jul/2024 | 08/Jul/2024 | False | 08 (c)days | 06 (w)days |
|
|
# | 3rd | 15/Jul/2024 | 30/Jul/2024 | True | 16 (c)days | 12 (w)days |
|
|
# | 4th | 05/Aug/2024 | 15/Aug/2024 | True | 11 (c)days | 09 (w)days |
|
|
|
|
# Note: 1st and 2nd leave will have a relapse value of True in practice because
|
|
# that is the default value for the field. In this example, the value is set to
|
|
# False to hopefully make the example clearer.
|
|
|
|
# The first leave is shorter than 30 (calendar) days.
|
|
# All (work) days within this leave have a guaranteed salary.
|
|
|
|
# The second leave is shorter than 30 (c) days.
|
|
# The gap between it and the first leave is greater than 14 days.
|
|
# All (w) days within this leave have a guaranteed salary.
|
|
|
|
# The third leave is shorter than 30 (c) days.
|
|
# The gap between it and the second leave is less than 14 days.
|
|
# It is a relapse, sum the duration of this and previous related leaves.
|
|
# The sum is 8 + 16 = 24 (c) days, which is less than 30 days.
|
|
# All (w) days within this leave have a guaranteed salary.
|
|
|
|
# The fourth leave is shorter than 30 (c) days.
|
|
# The gap between it and the third leave is less than 14 days.
|
|
# It is a relapse, sum the duration of this and previous related leaves.
|
|
# That sum is 8 + 16 + 11 = 35 (c) days, which is greater than 30 days.
|
|
# The first 6 (c) days have a guaranteed salary.
|
|
# (w) days 12/08 - 15/08 do not have a guaranteed salary.
|
|
|
|
# If the fourth leave was not a relapse, every day of that leave would have a
|
|
# guaranteed salary as the leave is shorter than 30 (c) days.
|
|
|
|
# Also note that public holidays do not impact this calculation.
|
|
# If 07/08 was a public holiday, 12/08-15/08 would still not be covered.
|
|
|
|
# Starting form 1/1/2026, the relapse period updated to 56 days
|
|
# So, needed to check the gap between the leaves is less than or equal to 56 days
|
|
|
|
current_leave_duration = (date_from - leave.holiday_id.date_from).days + 1
|
|
if current_leave_duration > 30:
|
|
return partial_sick_work_entry_type
|
|
|
|
if not leave.holiday_id.l10n_be_sickness_relapse:
|
|
return result
|
|
|
|
all_sick_leaves = self.env["hr.leave"].search(
|
|
[
|
|
("employee_id", "=", self.employee_id.id),
|
|
("date_from", "<", leave.date_from),
|
|
("holiday_status_id.work_entry_type_id", "in", sick_work_entry_types.ids),
|
|
("state", "=", "validate"),
|
|
],
|
|
order="date_from desc",
|
|
)
|
|
|
|
prev_leaves_sum = current_leave_duration
|
|
prev_leave_start = leave.holiday_id.date_from
|
|
relapse_period = 56 if prev_leave_start.year >= 2026 else 14
|
|
|
|
for leave in all_sick_leaves:
|
|
if (prev_leave_start - leave.date_to).days > relapse_period:
|
|
return result
|
|
prev_leaves_sum += (leave.date_to - leave.date_from).days + 1
|
|
if prev_leaves_sum > 30:
|
|
return partial_sick_work_entry_type
|
|
if leave.l10n_be_sickness_relapse is False:
|
|
return result
|
|
prev_leave_start = leave.date_from
|
|
|
|
return result
|
|
|
|
return result
|
|
|
|
def _get_bypassing_work_entry_type_codes(self):
|
|
return super()._get_bypassing_work_entry_type_codes() + [
|
|
'LEAVE280', # Long term sick
|
|
'LEAVE281', # Partial Incapacity
|
|
'LEAVE301', # Parental Time Off
|
|
'LEAVE300', # Time credit
|
|
# 'LEAVE110', # Sick Leave - Actually Sick Leave < Public Time Off
|
|
# If the employee does not have to work on a public
|
|
# holiday but falls ill when he could have benefited
|
|
# from a well-deserved day off, he is not entitled to
|
|
# a guaranteed salary but to remuneration in accordance
|
|
# with the days holidays. In fact, the employee is
|
|
# entitled to remuneration for each public holiday falling
|
|
# within 30 calendar days of the onset of his illness.
|
|
]
|
|
|
|
def _is_same_occupation(self, version):
|
|
self.ensure_one()
|
|
res = super()._is_same_occupation(version)
|
|
time_credit_type = self.resource_calendar_id.attendance_ids.work_entry_type_id.filtered('l10n_be_is_time_credit')
|
|
version_time_credit_type = version.resource_calendar_id.attendance_ids.work_entry_type_id.filtered('l10n_be_is_time_credit')
|
|
return res and self.l10n_be_time_credit == version.l10n_be_time_credit and time_credit_type == version_time_credit_type
|
|
|
|
def _create_credit_time_next_activity(self):
|
|
self.ensure_one()
|
|
part_time_link = "https://www.socialsecurity.be/site_fr/employer/applics/elo/index.htm"
|
|
part_time_link = '<a href="%s" target="_blank">%s</a>' % (part_time_link, part_time_link)
|
|
self.employee_id.activity_schedule(
|
|
'mail.mail_activity_data_todo',
|
|
note=_('Part Time of %(employee)s must be stated at %(link)s.',
|
|
employee=self.employee_id.name,
|
|
link=part_time_link),
|
|
user_id=self.hr_responsible_id.id or self.env.user.id,
|
|
summary='Part Time'
|
|
)
|
|
|
|
def _create_dimona_next_activity(self):
|
|
self.ensure_one()
|
|
dimona_link = "https://www.socialsecurity.be/site_fr/employer/applics/dimona/index.htm"
|
|
dimona_link = '<a href="%s" target="_blank">%s</a>' % (dimona_link, dimona_link)
|
|
self.employee_id.activity_schedule(
|
|
'mail.mail_activity_data_todo',
|
|
note=_('State the Dimona at %(link)s to declare the arrival of %(employee)s.',
|
|
link=dimona_link,
|
|
employee=self.employee_id.name),
|
|
user_id=self.hr_responsible_id.id or self.env.user.id,
|
|
summary='Dimona',
|
|
)
|
|
|
|
def _trigger_l10n_be_next_activities(self):
|
|
if self.env.context.get('pending_employee_creation'):
|
|
return
|
|
activities_by_summary = dict(
|
|
self.env['mail.activity']._read_group(
|
|
domain=[
|
|
('res_model', '=', 'hr.employee'),
|
|
('summary', 'in', ['Part Time', 'Dimona']),
|
|
('res_id', 'in', self.employee_id.ids),
|
|
],
|
|
groupby=['summary'],
|
|
aggregates=['id:recordset'],
|
|
)
|
|
)
|
|
dimona_activities = activities_by_summary.get('Dimona', self.env['mail.activity'])
|
|
part_time_activities = activities_by_summary.get('Part Time', self.env['mail.activity'])
|
|
for version in self:
|
|
if not version.employee_id.id:
|
|
continue
|
|
has_part_time_activity = any(activity.res_id == version.employee_id.id
|
|
and activity.date_deadline >= version.contract_date_start
|
|
for activity in part_time_activities)
|
|
has_dimona_activity = version.employee_id.id in dimona_activities.mapped('res_id')
|
|
if version.l10n_be_time_credit and not has_part_time_activity:
|
|
version._create_credit_time_next_activity()
|
|
if version.employee_id and not has_dimona_activity:
|
|
version._create_dimona_next_activity()
|
|
|
|
def _get_contract_insurance_amount(self, name):
|
|
self.ensure_one()
|
|
if name == 'hospital':
|
|
return self._get_hospital_insurance_amount()
|
|
if name == 'ambulatory':
|
|
return self.l10n_be_ambulatory_insurance_amount
|
|
if name == 'group':
|
|
return self.l10n_be_group_insurance_amount * (1 + 4.4 / 100.0)
|
|
return 0.0
|
|
|
|
def _get_hospital_insurance_amount(self):
|
|
self.ensure_one()
|
|
return self.insurance_amount
|
|
|
|
def _get_fields_that_recompute_we(self):
|
|
return super()._get_fields_that_recompute_we() + [
|
|
'l10n_be_time_credit',
|
|
'standard_calendar_id',
|
|
]
|
|
|
|
def _get_fields_that_recompute_payslip(self):
|
|
# Returns the fields that should recompute the payslip
|
|
return super()._get_fields_that_recompute_payslip() + [
|
|
'representation_fees',
|
|
'ip',
|
|
'ip_wage_rate',
|
|
'mobile',
|
|
'internet',
|
|
'transport_mode_car',
|
|
'transport_mode_private_car',
|
|
'transport_mode_train',
|
|
'transport_mode_public',
|
|
'train_transport_employee_amount',
|
|
'public_transport_employee_amount',
|
|
'distance_home_work',
|
|
'distance_home_work_unit',
|
|
'km_home_work',
|
|
'has_laptop',
|
|
'meal_voucher_amount'
|
|
'work_time_rate',
|
|
'no_onss',
|
|
'no_withholding_taxes',
|
|
]
|
|
|
|
def _l10n_be_is_isolated(self):
|
|
self.ensure_one()
|
|
return self.marital in ['divorced', 'single', 'widower', 'separated']
|
|
|
|
def _l10n_be_is_coupled(self):
|
|
self.ensure_one()
|
|
return self.marital in ['married', 'cohabitant']
|
|
|
|
def action_work_schedule_change_wizard(self):
|
|
if len(self) != 1:
|
|
raise UserError(_("This feature can only be used on a single contract."))
|
|
if not self.contract_date_start:
|
|
raise UserError(self.env._('This feature can only be used on versions that have a contract start date'))
|
|
if not self.is_current:
|
|
return False
|
|
action = self.env['ir.actions.actions']._for_xml_id('l10n_be_hr_payroll.schedule_change_wizard_action')
|
|
action['context'] = {'active_id': self.id}
|
|
return action
|