# -*- coding: utf-8 -*- ############################################################################# # A part of Open HRMS Project # # Cybrosys Technologies Pvt. Ltd. # # Copyright (C) 2024-TODAY Cybrosys Technologies() # Author: Cybrosys Techno Solutions() # # You can modify it under the terms of the GNU LESSER # GENERAL PUBLIC LICENSE (LGPL v3), Version 3. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. # # You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE # (LGPL v3) along with this program. # If not, see . # ############################################################################# from datetime import timedelta from odoo import api, fields, models, _ from odoo.exceptions import ValidationError, UserError from odoo.tools.safe_eval import datetime date_format = "%Y-%m-%d" RESIGNATION_TYPE = [('resigned', 'Normal Resignation'), ('fired', 'Fired by the company'), ('abosconded','Absconded')] class HrResignation(models.Model): """ Model for HR Resignations. This model is used to track employee resignations.""" _name = 'hr.resignation' _description = 'HR Resignation' _inherit = 'mail.thread' _rec_name = 'employee_id' @api.model def default_get(self, default_fields): """If we're creating a new account through a many2one, there are chances that we typed the account code instead of its name. In that case, switch both fields values. """ context = {} if 'notice_period' in default_fields: notice_period = self.env['ir.config_parameter'].sudo().get_param('hr_resignation.notice_period') no_of_days = self.env['ir.config_parameter'].sudo().get_param('hr_resignation.no_of_days') defaults = super(HrResignation, self).default_get(default_fields) if notice_period == 'True': defaults['notice_period'] = int(no_of_days) defaults['emp_requested_lwd'] = str(fields.Datetime.now() + timedelta(days=int(no_of_days))) return defaults name = fields.Char(string='Order Reference', copy=False, readonly=True, index=True, default=lambda self: _('New'), tracking=True) employee_id = fields.Many2one('hr.employee', string="Employee", default=lambda self: self.env.user.employee_id.id, help='Name of the employee for ' 'whom the request is creating', tracking=True) department_id = fields.Many2one('hr.department', string="Department", related='employee_id.department_id', help='Department of the employee', tracking=True) resign_confirm_date = fields.Date(string="Confirmed Date", help='Date on which the request ' 'is confirmed by the employee.', track_visibility="always") approved_revealing_date = fields.Date( string="Finalized Last Day Of Employee", help='Date on which the request is confirmed by the HR.', track_visibility="always") joined_date = fields.Date(string="Join Date", help='Joining date of the employee.' 'i.e Start date of the first contract', tracking=True) emp_requested_lwd = fields.Date(string="Employee Request LWD", required=False, help='Employee requested date on ' 'which employee is revealing ' 'from the company.', tracking=True) reason = fields.Text(string="Reason", required=True, help='Specify reason for leaving the company', tracking=True) notice_period = fields.Char(string="Notice Period (day's)", help="Notice Period of the employee in Days.", tracking=True) state = fields.Selection( [('draft', 'Draft'), ('in_process','In Process'), ('approved','Done'), ('cancel_refuse','Canceled/Refused'), ('withdrawl', 'Withdrawal')], string='Status', default='draft', track_visibility="always") resignation_type = fields.Selection(selection=RESIGNATION_TYPE, default='resigned', help="Select the type of resignation: " "normal resignation or " "fired by the company", tracking=True) #normal resignaiton normal_resignation_status = fields.Selection([('sent_to_manager','Under Manager Review'),('meeting_with_emp','Meeting Scheduled'),('submit_hr','Under HR Review'),('hr_approve_and_request_asset_collection','Approve and process Collection'),('process_relieving_documents','Process Relieving Letters'),('final_clearance','Issue Final Clearance')], tracking=True) emp_meeting_points = fields.Html(tracking=True) agree_with_emp_lwd = fields.Boolean(tracking=True) manager_proposed_lwd = fields.Date(tracking=True) fired_status = fields.Selection([('warning_sent','Warning Issued'),('meeting_with_emp','Meeting Scheduled'),('hr_approve_and_request_asset_collection','Approve and Process Collection'),('process_relieving_documents','Process Relieving Letters'),('final_clearance','Issue Final Clearance')], tracking=True) meeting_sheduled = fields.Boolean(tracking=True) absconded_status = fields.Selection([('contact_emp','Contact Employee via phone, email'),('responed','Responed'),('meeting_with_emp','Meeting Scheduled'),('send_hr','Under HR Review'),('hr_approve_and_request_asset_collection','Process Checklist'),('process_relieving_documents','Process Relieving Letters'),('final_clearance','Issue Final Clearance')], tracking=True) emp_responded = fields.Boolean() change_employee = fields.Boolean(string="Change Employee", compute="_compute_change_employee", help="Checks , if the user has permission" " to change the employee", tracking=True) employee_contract = fields.Char(String="Contract",tracking=True) is_manager = fields.Boolean(compute="_compute_user_rights") is_finance_manager = fields.Boolean(compute="_compute_user_rights") is_admin = fields.Boolean(compute="_compute_user_rights") is_hr = fields.Boolean(compute="_compute_user_rights") is_it_manager = fields.Boolean(compute="_compute_user_rights") is_emp = fields.Boolean(compute="_compute_user_rights") emp_comments = fields.Text(tracking=True) manager_comments = fields.Text(tracking=True) finance_manager_comments = fields.Text(tracking=True) it_manager_comments = fields.Text(tracking=True) admin_comments = fields.Text(tracking=True) hr_comments = fields.Text(tracking=True) show_withdraw = fields.Boolean(tracking=True,default=True) checklist_line_ids = fields.One2many('resignation.checklist.line', 'resignation_id', string="All Checklist Items") it_checklist_ids = fields.One2many( 'resignation.checklist.line', 'resignation_id', domain=[('team', '=', 'it')], string="IT Checklist" ) admin_checklist_ids = fields.One2many( 'resignation.checklist.line', 'resignation_id', domain=[('team', '=', 'admin')], string="Admin Checklist" ) manager_checklist_ids = fields.One2many( 'resignation.checklist.line', 'resignation_id', domain=[('team', '=', 'manager')], string="Manager Checklist" ) finance_checklist_ids = fields.One2many( 'resignation.checklist.line', 'resignation_id', domain=[('team', '=', 'finance')], string="Finance Checklist" ) hr_checklist_ids = fields.One2many( 'resignation.checklist.line', 'resignation_id', domain=[('team', '=', 'hr')], string="HR Checklist" ) manager_checklist_submitted = fields.Boolean(tracking=True) it_checklist_submitted = fields.Boolean(tracking=True) finance_checklist_submitted = fields.Boolean(tracking=True) admin_checklist_submitted = fields.Boolean(tracking=True) hr_checklist_submitted = fields.Boolean(tracking=True) relieving_documents = fields.Many2many('ir.attachment') @api.depends('employee_id') def _compute_user_rights(self): current_user_id = self.env.user.id for resignation in self: resignation.is_manager = True if resignation.employee_id.parent_id.user_id.id == current_user_id else False resignation.is_it_manager = True if int(self.env['ir.config_parameter'].sudo().get_param('hr_employee_extended.emp_it_manager')) == current_user_id else False resignation.is_hr = True if int(self.env['ir.config_parameter'].sudo().get_param('hr_employee_extended.emp_hr_id')) == current_user_id else False resignation.is_finance_manager = True if int(self.env['ir.config_parameter'].sudo().get_param('hr_employee_extended.emp_finance_manager')) == current_user_id else False resignation.is_admin = True if int(self.env['ir.config_parameter'].sudo().get_param('hr_employee_extended.emp_admin')) == current_user_id else False resignation.is_emp = True if resignation.employee_id.user_id.id == current_user_id else False @api.onchange('agree_with_emp_lwd') def onchange_agree_with_emp_lwd(self): for resingation in self: if resingation.emp_requested_lwd and resingation.agree_with_emp_lwd: resingation.approved_revealing_date = resingation.emp_requested_lwd @api.depends('employee_id') def _compute_change_employee(self): """ Check whether the user has the permission to change the employee""" res_user = self.env['res.users'].browse(self._uid) self.change_employee = res_user.has_group('hr.group_hr_user') @api.constrains('employee_id') def _check_employee_id(self): """ Constraint method to check if the current user has the permission to create a resignation request for the specified employee. """ for resignation in self: if not self.env.user.has_group('hr.group_hr_user'): if (resignation.employee_id.user_id.id and resignation.employee_id.user_id.id != self.env.uid): raise ValidationError( _('You cannot create a request for other employees')) @api.constrains('joined_date') def _check_joined_date(self): """ Check if there is an active resignation request for the same employee with a confirmed or approved state, based on the 'joined_date' of the current resignation.""" for resignation in self: resignation_request = self.env['hr.resignation'].search( [('employee_id', '=', resignation.employee_id.id), ('state', 'in', ['in_process', 'approved'])]) if resignation_request: raise ValidationError( _('There is a resignation request in confirmed or' ' approved state for this employee')) def action_abscond_emp_response(self): for resignation in self: resignation.absconded_status = 'responed' resignation.emp_responded = True def action_abscond_emp_no_response(self): for resignation in self: resignation.absconded_status = 'send_hr' def action_abscond_send_hr(self): for resignation in self: resignation.absconded_status = 'send_hr' def action_abscond_meeting_emp(self): for resignation in self: resignation.absconded_status = 'meeting_with_emp' def action_submit_manager_checklist(self): for rec in self: rec.manager_checklist_submitted=True def action_revert_manager_checklist(self): for rec in self: rec.manager_checklist_submitted=False # For IT Checklist def action_submit_it_checklist(self): for rec in self: rec.it_checklist_submitted = True def action_revert_it_checklist(self): for rec in self: rec.it_checklist_submitted = False # For Admin Checklist def action_submit_admin_checklist(self): for rec in self: rec.admin_checklist_submitted = True def action_revert_admin_checklist(self): for rec in self: rec.admin_checklist_submitted = False # For Finance Checklist def action_submit_finance_checklist(self): for rec in self: rec.finance_checklist_submitted = True def action_revert_finance_checklist(self): for rec in self: rec.finance_checklist_submitted = False def action_submit_hr_checklist(self): for rec in self: rec.hr_checklist_submitted = True def action_revert_hr_checklist(self): for rec in self: rec.hr_checklist_submitted = False def action_process_relieving_docs(self): for rec in self: if rec.resignation_type == 'resigned': rec.normal_resignation_status = 'process_relieving_documents' elif rec.resignation_type == 'abosconded': rec.absconded_status = 'process_relieving_documents' elif rec.resignation_type == 'fired': rec.fired_status = 'process_relieving_documents' @api.onchange('employee_id') def _onchange_employee_id(self): """ Method triggered when the 'employee_id' field is changed.""" self.joined_date = self.employee_id.doj if self.employee_id: resignation_request = self.env['hr.resignation'].search( [('employee_id', '=', self.employee_id.id), ('state', 'in', ['in_process', 'approved'])]) if resignation_request: raise ValidationError( _('There is a resignation request in confirmed or' ' approved state for this employee')) employee_contract = self.env['hr.contract'].search( [('employee_id', '=', self.employee_id.id)]) for contracts in employee_contract: if contracts.state == 'open': self.employee_contract = contracts.name self.notice_period = contracts.notice_days @api.model def create(self, vals): """Override of the create method to assign a sequence for the record.""" if vals.get('name', _('New')) == _('New'): vals['name'] = self.env['ir.sequence'].next_by_code( 'hr.resignation') or _('New') return super(HrResignation, self).create(vals) def action_confirm_resignation(self): """ Method triggered by the 'Confirm' button to confirm the resignation request.""" for resignation in self: if resignation.resignation_type == 'resigned': if resignation.joined_date: if (resignation.joined_date >= resignation.emp_requested_lwd): raise ValidationError( _('Last date of the Employee must ' 'be anterior to Joining date')) else: raise ValidationError( _('Please set a Joining Date for employee')) resignation.state = 'in_process' if resignation.resignation_type == 'resigned': resignation.normal_resignation_status = 'sent_to_manager' if resignation.resignation_type == 'abosconded': resignation.absconded_status = 'contact_emp' if resignation.resignation_type == 'fired': resignation.fired_status = 'warning_sent' resignation.resign_confirm_date = str(fields.Datetime.now()) if resignation.notice_period: resignation.approved_revealing_date = str(fields.Datetime.now() + timedelta(days=int(resignation.notice_period))) def action_schedule_Meeting(self): for resignation in self: resignation.meeting_sheduled = True if resignation.resignation_type == 'resigned': resignation.normal_resignation_status = 'meeting_with_emp' elif resignation.resignation_type == 'fired': resignation.fired_status = 'meeting_with_emp' def action_proceed_further(self): for resignation in self: resignation.show_withdraw = False resignation.normal_resignation_status = 'submit_hr' def action_proceed_further_fired(self): for resignation in self: resignation.show_withdraw = False resignation.checklist_line_ids.unlink() # Fetch all active checklists checklists = self.env['pre.resignation.requirements.proceedings'].search([('active', '=', True)]) # Create checklist lines for checklist in checklists: self.env['resignation.checklist.line'].create({ 'resignation_id': resignation.id, 'checklist_name': checklist.name, 'team': checklist.team, }) resignation.fired_status = 'hr_approve_and_request_asset_collection' def action_process_resignation(self): for resignation in self: # Clean previous lines if needed resignation.checklist_line_ids.unlink() # Fetch all active checklists checklists = self.env['pre.resignation.requirements.proceedings'].search([('active', '=', True)]) # Create checklist lines for checklist in checklists: self.env['resignation.checklist.line'].create({ 'resignation_id': resignation.id, 'checklist_name': checklist.name, 'team': checklist.team, }) if resignation.resignation_type == 'resigned': resignation.normal_resignation_status = 'hr_approve_and_request_asset_collection' elif resignation.resignation_type == 'abosconded': resignation.absconded_status = 'hr_approve_and_request_asset_collection' def action_withdraw_resignation(self): for resignation in self: resignation.state = 'withdrawl' def action_cancel_resignation(self): """ Method triggered by the 'Cancel' button to cancel the resignation request.""" for resignation in self: resignation.state = 'cancel_refuse' def action_reject_resignation(self): """ Method triggered by the 'Reject' button to reject the resignation request.""" for resignation in self: resignation.state = 'cancel_refuse' def action_reset_to_draft(self): """ Method triggered by the 'Set to Draft' button to reset the resignation request to the 'draft' state.""" for resignation in self: resignation.state = 'draft' resignation.employee_id.active = True resignation.employee_id.resigned = False resignation.employee_id.fired = False resignation.employee_id.user_id.active = True resignation.fired_status = False resignation.absconded_status = False resignation.normal_resignation_status = False def action_approve_resignation(self): """ Method triggered by the 'Approve' button to approve the resignation.""" for resignation in self: if (resignation.emp_requested_lwd and resignation.resign_confirm_date): employee_contract = self.env['hr.contract'].search( [('employee_id', '=', self.employee_id.id)]) if not employee_contract: raise ValidationError( _("There are no Contracts found for this employee")) for contract in employee_contract: if contract.state == 'open': resignation.employee_contract = contract.name resignation.state = 'approved' resignation.approved_revealing_date = ( resignation.resign_confirm_date + timedelta( days=contract.notice_days)) else: resignation.approved_revealing_date = ( resignation.emp_requested_lwd) # Cancelling contract contract.state = 'cancel' if contract.state == "open" else \ contract.state # Changing state of the employee if resigning today if (resignation.emp_requested_lwd <= fields.Date.today() and resignation.employee_id.active): resignation.employee_id.active = False # Changing fields in the employee table # with respect to resignation resignation.employee_id.resign_date = ( resignation.emp_requested_lwd) if resignation.resignation_type == 'resigned': resignation.employee_id.resigned = True departure_reason_id = self.env[ 'hr.departure.reason'].search( [('name', '=', 'Resigned')]) else: resignation.employee_id.fired = True departure_reason_id = self.env[ 'hr.departure.reason'].search( [('name', '=', 'Fired')]) running_contract_ids = self.env['hr.contract'].search([ ('employee_id', '=', resignation.employee_id.id), ('company_id', '=', resignation.employee_id.company_id.id), ('state', '=', 'open'), ]).filtered(lambda c: c.date_start <= fields.Date.today() and ( not c.date_end or c.date_end >= fields.Date.today())) running_contract_ids.state = 'close' resignation.employee_id.departure_reason_id = departure_reason_id resignation.employee_id.departure_date = resignation.approved_revealing_date # Removing and deactivating user if resignation.employee_id.user_id: resignation.employee_id.user_id.active = False resignation.employee_id.user_id = None else: raise ValidationError(_('Please Enter Valid Dates.')) def update_employee_status(self): pass def process_final_clearance(self): """Process final clearance and exit the employee""" for resignation in self: employee = resignation.employee_id # Check if employee exists and is active if not employee: raise UserError(_("Employee is already exited or doesn't exist.")) # Update employee status if resignation.resignation_type == 'resigned': departure_reason = self.env.ref("hr.departure_resigned").id if resignation.resignation_type == 'fired': departure_reason = self.env.ref("hr.departure_fired").id elif resignation.resignation_type == 'abosconded': departure_reason = self.env.ref("hr_resignation.departure_absconded").id employee.write({ 'active': False, 'departure_reason_id': departure_reason, 'departure_description': resignation.reason, 'departure_date': datetime.date.today(), }) # Update resignation status if resignation.resignation_type == 'resigned': resignation.write({ 'state': 'approved', 'normal_resignation_status': 'final_clearance' }) elif resignation.resignation_type == 'abosconded': resignation.write({ 'state': 'approved', 'absconded_status': 'final_clearance' }) elif resignation.resignation_type == 'fired': resignation.write({ 'state': 'approved', 'fired_status': 'final_clearance' }) # Archive related user if exists if employee.user_id: employee.sudo().user_id.write({'active': False}) # Log the activity message = _("Employee %s has been exited through final clearance process.") % employee.name resignation.message_post(body=message) return { 'type': 'ir.actions.client', 'tag': 'display_notification', 'params': { 'title': _('Success'), 'message': _('Final clearance processed successfully. Employee has been exited.'), 'sticky': False, 'next': {'type': 'ir.actions.act_window_close'}, } }