# -*- coding:utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. from collections import defaultdict from functools import reduce from odoo import api, fields, models, _ from odoo.exceptions import ValidationError, AccessError class HrEmployee(models.Model): _inherit = 'hr.employee' niss = fields.Char( 'NISS Number', compute="_compute_niss", store=True, readonly=False, groups="hr.group_hr_user", tracking=True) spouse_fiscal_status = fields.Selection([ ('without_income', 'Without Income'), ('high_income', 'With High income'), ('low_income', 'With Low Income'), ('low_pension', 'With Low Pensions'), ('high_pension', 'With High Pensions') ], string='Tax status for spouse', groups="hr.group_hr_user", default='without_income', required=False, tracking=True) spouse_fiscal_status_explanation = fields.Char(compute='_compute_spouse_fiscal_status_explanation') disabled = fields.Boolean(string="Disabled", help="If the employee is declared disabled by law", groups="hr.group_hr_user", 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", tracking=True) 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 (>=65)', 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 (>=65)', groups="hr.group_hr_user", tracking=True) other_juniors_dependent = fields.Integer('# people (<65)', 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 (<65)', 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") start_notice_period = fields.Date("Start notice period", groups="hr.group_hr_user", copy=False, tracking=True) end_notice_period = fields.Date("End notice period", groups="hr.group_hr_user", copy=False, tracking=True) first_contract_in_company = fields.Date("First contract in company", groups="hr.group_hr_user", copy=False) certificate = fields.Selection(selection_add=[('civil_engineer', 'Master: Civil Engineering')]) l10n_be_scale_seniority = fields.Integer(string="Seniority at Hiring", groups="hr.group_hr_user", tracking=True) # The attestation for the year of the first contract date first_contract_year_n = fields.Char(compute='_compute_first_contract_year') first_contract_year_n_plus_1 = fields.Char(compute='_compute_first_contract_year') l10n_be_holiday_pay_to_recover_n = fields.Float( string="Simple Holiday Pay to Recover (N)", tracking=True, groups="hr_payroll.group_hr_payroll_user", help="Amount of the holiday pay paid by the previous employer to recover.") l10n_be_holiday_pay_number_of_days_n = fields.Float( string="Number of days to recover (N)", tracking=True, groups="hr_payroll.group_hr_payroll_user", help="Number of days on which you should recover the holiday pay.") l10n_be_holiday_pay_recovered_n = fields.Float( string="Recovered Simple Holiday Pay (N)", tracking=True, compute='_compute_l10n_be_holiday_pay_recovered', groups="hr_payroll.group_hr_payroll_user", help="Amount of the holiday pay paid by the previous employer already recovered.") double_pay_line_n_ids = fields.Many2many( 'l10n.be.double.pay.recovery.line', 'double_pay_n_rel' 'employee_id', 'double_pay_line_n_ids', compute='_compute_from_double_pay_line_ids', readonly=False, inverse='_inverse_double_pay_line_n_ids', string='Previous Occupations (N)', groups="hr_payroll.group_hr_payroll_user") # The attestation for the previous year of the first contract date first_contract_year_n1 = fields.Char(compute='_compute_first_contract_year') l10n_be_holiday_pay_to_recover_n1 = fields.Float( string="Simple Holiday Pay to Recover (N-1)", tracking=True, groups="hr_payroll.group_hr_payroll_user", help="Amount of the holiday pay paid by the previous employer to recover.") l10n_be_holiday_pay_number_of_days_n1 = fields.Float( string="Number of days to recover (N-1)", tracking=True, groups="hr_payroll.group_hr_payroll_user", help="Number of days on which you should recover the holiday pay.") l10n_be_holiday_pay_recovered_n1 = fields.Float( string="Recovered Simple Holiday Pay (N-1)", tracking=True, compute='_compute_l10n_be_holiday_pay_recovered', groups="hr_payroll.group_hr_payroll_user", help="Amount of the holiday pay paid by the previous employer already recovered.") double_pay_line_n1_ids = fields.Many2many( 'l10n.be.double.pay.recovery.line', 'double_pay_n1_rel' 'employee_id', 'double_pay_line_n1_ids', compute='_compute_from_double_pay_line_ids', readonly=False, inverse='_inverse_double_pay_line_n1_ids', string='Previous Occupations (N-1)', groups="hr_payroll.group_hr_payroll_user") first_contract_year = fields.Integer(compute='_compute_first_contract_year') double_pay_line_ids = fields.One2many( 'l10n.be.double.pay.recovery.line', 'employee_id', string='Previous Occupations', groups="hr_payroll.group_hr_payroll_user") @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 employee in self: if (employee.children < 0 or employee.disabled_children_number < 0 or employee.other_senior_dependent < 0 or employee.other_disabled_senior_dependent < 0 or employee.other_juniors_dependent < 0 or employee.other_disabled_juniors_dependent < 0 or employee.l10n_be_dependent_children_attachment < 0): validation_error_message.append(_("Count of dependent people/children or disabled dependent people/children must be positive.")) if ((employee.disabled_children_number > 0 and employee.disabled_children_number > employee.children) or (employee.other_disabled_senior_dependent > 0 and employee.other_disabled_senior_dependent > employee.other_senior_dependent) or (employee.other_disabled_juniors_dependent > 0 and employee.other_disabled_juniors_dependent > employee.other_juniors_dependent) or (employee.l10n_be_dependent_children_attachment > 0 and employee.l10n_be_dependent_children_attachment > employee.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.depends('first_contract_date') def _compute_first_contract_year(self): current_year = fields.Datetime.today().date().year for employee in self: year = employee.first_contract_date.year if employee.first_contract_date else current_year employee.first_contract_year = year employee.first_contract_year_n = year employee.first_contract_year_n1 = year - 1 employee.first_contract_year_n_plus_1 = year + 1 def _compute_from_double_pay_line_ids(self): for employee in self: year = employee.first_contract_year employee.double_pay_line_n_ids = employee.double_pay_line_ids.filtered(lambda d: d.year == year) employee.double_pay_line_n1_ids = employee.double_pay_line_ids.filtered(lambda d: d.year == year - 1) def _inverse_double_pay_line_n_ids(self): for employee in self: year = employee.first_contract_year to_be_deleted = employee.double_pay_line_ids.filtered(lambda d: d.year == year) - employee.double_pay_line_n_ids employee.double_pay_line_ids.filtered(lambda d: d.id in to_be_deleted.ids).unlink() employee.double_pay_line_ids |= employee.double_pay_line_n_ids def _inverse_double_pay_line_n1_ids(self): for employee in self: year = employee.first_contract_year to_be_deleted = employee.double_pay_line_ids.filtered(lambda d: d.year == year - 1) - employee.double_pay_line_n1_ids employee.double_pay_line_ids.filtered(lambda d: d.id in to_be_deleted.ids).unlink() employee.double_pay_line_ids |= employee.double_pay_line_n1_ids @api.constrains('start_notice_period', 'end_notice_period') def _check_notice_period(self): for employee in self: if employee.start_notice_period and employee.end_notice_period and employee.start_notice_period > employee.end_notice_period: raise ValidationError(_('The employee start notice period should be set before the end notice period')) def _compute_l10n_be_holiday_pay_recovered(self): payslips = self.env['hr.payslip'].search([ ('employee_id', 'in', self.ids), ('struct_id', '=', self.env.ref('l10n_be_hr_payroll.hr_payroll_structure_cp200_employee_salary').id), ('company_id', '=', self.env.company.id), ('state', 'in', ['done', 'paid']), ]) line_values = payslips._get_line_values(['HolPayRecN', 'HolPayRecN1']) payslips_by_employee = defaultdict(lambda: self.env['hr.payslip']) for payslip in payslips: payslips_by_employee[payslip.employee_id] |= payslip for employee in self: employee_payslips = payslips_by_employee[employee] employee.l10n_be_holiday_pay_recovered_n = - sum(line_values['HolPayRecN'][p.id]['total'] for p in employee_payslips) employee.l10n_be_holiday_pay_recovered_n1 = - sum(line_values['HolPayRecN1'][p.id]['total'] for p in employee_payslips) def _compute_spouse_fiscal_status_explanation(self): low_income_threshold = self.env['hr.rule.parameter'].sudo()._get_parameter_from_code('spouse_low_income_threshold') other_income_threshold = self.env['hr.rule.parameter'].sudo()._get_parameter_from_code('spouse_other_income_threshold') for employee in self: employee.spouse_fiscal_status_explanation = _("""- Without Income: The spouse of the income recipient has no professional income.\n - High income: The spouse of the recipient of the income has professional income, other than pensions, annuities or similar income, which exceeds %s€ net per month.\n - Low Income: The spouse of the recipient of the income has professional income, other than pensions, annuities or similar income, which does not exceed %s€ net per month.\n - Low Pensions: The spouse of the beneficiary of the income has professional income which consists exclusively of pensions, annuities or similar income and which does not exceed %s€ net per month.\n - High Pensions: The spouse of the beneficiary of the income has professional income which consists exclusively of pensions, annuities or similar income and which exceeds %s€ net per month.""", low_income_threshold, low_income_threshold, other_income_threshold, other_income_threshold) @api.depends('identification_id') def _compute_niss(self): characters = dict.fromkeys([',', '.', '-', ' '], '') for employee in self: if employee.identification_id and not employee.niss and employee.company_country_code == 'BE': employee.niss = reduce(lambda a, kv: a.replace(*kv), characters.items(), employee.identification_id) @api.model def _validate_niss(self, niss): try: test = niss[:-2] if test[0] in ['0', '1', '2', '3', '4', '5']: # Should be good for several years test = '2%s' % test checksum = int(niss[-2:]) if checksum != (97 - int(test) % 97): raise Exception() return True except Exception: return False def _is_niss_valid(self): # The last 2 positions constitute the check digit. This check digit is # a sequence of 2 digits forming a number between 01 and 97. This number is equal to 97 # minus the remainder of the division by 97 of the number formed: # - either by the first 9 digits of the national number for people born before the 1st # January 2000. # - either by the number 2 followed by the first 9 digits of the national number for people # born after December 31, 1999. # (https://fr.wikipedia.org/wiki/Num%C3%A9ro_de_registre_national) self.ensure_one() niss = self.niss if not niss or len(niss) != 11: return False return self._validate_niss(niss) @api.onchange('disabled_children_bool') def _onchange_disabled_children_bool(self): self.disabled_children_number = 0 @api.onchange('other_dependent_people') def _onchange_other_dependent_people(self): self.other_senior_dependent = 0.0 self.other_disabled_senior_dependent = 0.0 self.other_juniors_dependent = 0.0 self.other_disabled_juniors_dependent = 0.0 @api.depends('disabled_children_bool', 'disabled_children_number', 'children') def _compute_dependent_children(self): for employee in self: if employee.disabled_children_bool: employee.dependent_children = employee.children + employee.disabled_children_number else: employee.dependent_children = employee.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 employee in self: employee.dependent_seniors = employee.other_senior_dependent + employee.other_disabled_senior_dependent employee.dependent_juniors = employee.other_juniors_dependent + employee.other_disabled_juniors_dependent @api.model def _get_invalid_niss_employee_ids(self): # as we do not store if the niss is valid or not it's not possible to fetch all the employees directly # use sql and manually filter the employees # return nothing if user has no right to either employee or bank partner try: self.check_access_rights('read') # niss field is for this group only if not self.user_has_groups('hr.group_hr_user'): raise AccessError() except AccessError: return [] self.env.cr.execute(''' SELECT emp.id, emp.niss FROM hr_employee emp JOIN hr_contract con ON con.id = emp.contract_id AND con.state in ('open', 'close') WHERE emp.company_id IN %s AND emp.employee_type IN ('employee', 'student') AND emp.active=TRUE ''', (tuple(c.id for c in self.env.companies if c.country_id.code == 'BE'),)) return [row['id'] for row in self.env.cr.dictfetchall() if not row['niss'] or not self._validate_niss(row['niss'])]