odoo18/addons_extensions/hr_resignation/models/hr_resignation.py

548 lines
26 KiB
Python

# -*- coding: utf-8 -*-
#############################################################################
# A part of Open HRMS Project <https://www.openhrms.com>
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>)
#
# 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 <http://www.gnu.org/licenses/>.
#
#############################################################################
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'},
}
}