odoo18/addons_extensions/hr_recruitment_extended/models/hr_applicant.py

302 lines
14 KiB
Python

from email.policy import default
from odoo import models, fields, api, _
from dateutil.relativedelta import relativedelta
from datetime import datetime
from odoo.exceptions import ValidationError
class HRApplicant(models.Model):
_inherit = 'hr.applicant'
_track_duration_field = 'recruitment_stage_id'
candidate_image = fields.Image(related='candidate_id.candidate_image', readonly=False, compute_sudo=True)
submitted_to_client = fields.Boolean(string="Submitted_to_client", default=False, readonly=True, tracking=True)
client_submission_date = fields.Datetime(string="Submission Date")
@api.model
def _read_group_recruitment_stage_ids(self, stages, domain):
# retrieve job_id from the context and write the domain: ids + contextual columns (job or default)
job_recruitment_id = self._context.get('default_hr_job_recruitment')
search_domain = []
if job_recruitment_id:
search_domain = [('job_recruitment_ids', '=', job_recruitment_id)] + search_domain
# if stages:
# search_domain = [('id', 'in', stages.ids)] + search_domain
stage_ids = stages.sudo()._search(search_domain, order=stages._order)
return stages.browse(stage_ids)
def write(self, vals):
# user_id change: update date_open
res = super().write(vals)
if vals.get('user_id'):
vals['date_open'] = fields.Datetime.now()
old_interviewers = self.interviewer_ids
# stage_id: track last stage before update
if 'recruitment_stage_id' in vals:
vals['date_last_stage_update'] = fields.Datetime.now()
if 'kanban_state' not in vals:
vals['kanban_state'] = 'normal'
for applicant in self:
vals['last_stage_id'] = applicant.recruitment_stage_id.id
return res
@api.depends('hr_job_recruitment')
def _compute_department(self):
for applicant in self:
applicant.department_id = applicant.hr_job_recruitment.department_id.id
@api.depends('hr_job_recruitment')
def _compute_stage(self):
for applicant in self:
if applicant.hr_job_recruitment:
if not applicant.recruitment_stage_id:
stage_ids = self.env['hr.recruitment.stage'].search([
'|',
('job_recruitment_ids', '=', False),
('job_recruitment_ids', '=', applicant.hr_job_recruitment.id),
('fold', '=', False)
], order='sequence asc', limit=1).ids
applicant.recruitment_stage_id = stage_ids[0] if stage_ids else False
else:
applicant.recruitment_stage_id = False
@api.depends('job_id')
def _compute_user(self):
for applicant in self:
applicant.user_id = applicant.hr_job_recruitment.user_id.id
def init(self):
super().init()
self.env.cr.execute("""
CREATE INDEX IF NOT EXISTS hr_applicant_job_id_recruitment_stage_id_idx
ON hr_applicant(job_id, recruitment_stage_id)
WHERE active IS TRUE
""")
refused_state = fields.Many2one('hr.recruitment.stage', readonly=True, force_save=True)
hr_job_recruitment = fields.Many2one('hr.job.recruitment')
job_id = fields.Many2one('hr.job', related='hr_job_recruitment.job_id', store=True)
recruitment_stage_id = fields.Many2one('hr.recruitment.stage', 'Stage', ondelete='restrict', tracking=True,
compute='_compute_recruitment_stage', store=True, readonly=False,
domain="[('job_recruitment_ids', '=', hr_job_recruitment)]",
copy=False, index=True,
group_expand='_read_group_recruitment_stage_ids')
stage_color = fields.Char(related="recruitment_stage_id.stage_color")
send_second_application_form = fields.Boolean(related='recruitment_stage_id.second_application_form')
second_application_form_status = fields.Selection([('draft','Draft'),('email_sent_to_candidate','Email Sent to Candidate'),('done','Done')], default='draft')
send_post_onboarding_form = fields.Boolean(related='recruitment_stage_id.post_onboarding_form')
post_onboarding_form_status = fields.Selection([('draft','Draft'),('email_sent_to_candidate','Email Sent to Candidate'),('done','Done')], default='draft')
legend_blocked = fields.Char(related='recruitment_stage_id.legend_blocked', string='Kanban Blocked')
legend_done = fields.Char(related='recruitment_stage_id.legend_done', string='Kanban Valid')
legend_normal = fields.Char(related='recruitment_stage_id.legend_normal', string='Kanban Ongoing')
# holding_offer = fields.HTML()
employee_code = fields.Char(related="employee_id.employee_id")
recruitment_attachments = fields.Many2many(
'recruitment.attachments',
string='Attachments Request')
joining_attachment_ids = fields.One2many('employee.recruitment.attachments','applicant_id',string="Attachments")
attachments_validation_status = fields.Selection([('pending', 'Pending'),
('validated', 'Validated')], default='pending')
approval_required = fields.Boolean(related='recruitment_stage_id.require_approval')
application_submitted = fields.Boolean(string="Application Submitted")
resume = fields.Binary(related='candidate_id.resume', readonly=False, compute_sudo=True)
def submit_to_client(self):
for rec in self:
submitted_count = len(self.sudo().search([('id','!=',rec.id),('submitted_to_client','=',True)]).ids)
if submitted_count >= rec.hr_job_recruitment.no_of_eligible_submissions:
raise ValidationError(_("Max no of submissions for this JD has been reached"))
rec.submitted_to_client = True
rec.client_submission_date = fields.Datetime.now()
def submit_for_approval(self):
for rec in self:
manager_id = self.env['ir.config_parameter'].sudo().get_param('requisitions.requisition_manager')
if not manager_id:
raise ValidationError(_("Recruitment Manager is not selected please go into the Configuration->Settings and add the Manager"))
mail_template = self.env.ref('hr_recruitment_extended.email_template_candidate_approval')
# menu_id = self.env.ref('hr_recruitment.menu_crm_case_categ0_act_job').id
manager_id = self.env['res.users'].sudo().browse(int(manager_id))
render_ctx = dict(recruitment_manager=manager_id)
mail_template.with_context(render_ctx).send_mail(
self.id,
force_send=True,
email_layout_xmlid='mail.mail_notification_light')
rec.application_submitted = True
def approve_applicant(self):
for rec in self:
manager_id = self.env['ir.config_parameter'].sudo().get_param('requisitions.requisition_manager')
if not manager_id:
raise ValidationError(
_("Recruitment Manager is not selected please go into the Configuration->Settings and add the Manager"))
mail_template = self.env.ref('hr_recruitment_extended.email_template_stage_approved')
# menu_id = self.env.ref('hr_recruitment.menu_crm_case_categ0_act_job').id
manager_id = self.env['res.users'].sudo().browse(int(manager_id))
render_ctx = dict(recruitment_manager=manager_id)
mail_template.with_context(render_ctx).send_mail(
self.id,
force_send=True,
email_layout_xmlid='mail.mail_notification_light')
rec.application_submitted = False
recruitment_stage_ids = rec.hr_job_recruitment.recruitment_stage_ids.ids
current_stage = self.env['hr.recruitment.stage'].browse(rec.recruitment_stage_id.id)
next_stage = self.env['hr.recruitment.stage'].search([
('id', 'in', recruitment_stage_ids),
('sequence', '>', current_stage.sequence)
], order='sequence asc', limit=1)
if next_stage:
rec.recruitment_stage_id = next_stage.id
def action_validate_attachments(self):
for rec in self:
if rec.employee_id and rec.joining_attachment_ids:
rec.joining_attachment_ids.write({'employee_id': rec.employee_id.id})
rec.attachments_validation_status = 'validated'
else:
raise ValidationError(_("No Data to Validate"))
def send_second_application_form_to_candidate(self):
"""Send the salary expectation and experience form to the candidate."""
template = self.env.ref('hr_recruitment_extended.email_template_second_application_form', raise_if_not_found=False)
for applicant in self:
if template and applicant.email_from:
template.send_mail(applicant.id, force_send=True)
applicant.second_application_form_status = 'email_sent_to_candidate'
def send_post_onboarding_form_to_candidate(self):
for rec in self:
if not rec.employee_id:
raise ValidationError(_('You must first create the employee before before Sending the Post Onboarding Form'))
elif not rec.employee_id.employee_id:
raise ValidationError(_('Employee Code for the Employee (%s) is missing')%(rec.employee_id.name))
return {
'type': 'ir.actions.act_window',
'name': 'Select Attachments',
'res_model': 'post.onboarding.attachment.wizard',
'view_mode': 'form',
'view_type': 'form',
'target': 'new',
'context': {'default_attachment_ids': []}
}
def _track_template(self, changes):
res = super(HRApplicant, self)._track_template(changes)
applicant = self[0]
# When applcant is unarchived, they are put back to the default stage automatically. In this case,
# don't post automated message related to the stage change.
if 'recruitment_stage_id' in changes and applicant.exists()\
and applicant.recruitment_stage_id.template_id\
and not applicant._context.get('just_moved')\
and not applicant._context.get('just_unarchived'):
res['recruitment_stage_id'] = (applicant.recruitment_stage_id.template_id, {
'auto_delete_keep_log': False,
'subtype_id': self.env['ir.model.data']._xmlid_to_res_id('mail.mt_note'),
'email_layout_xmlid': 'hr_recruitment.mail_notification_light_without_background'
})
return res
def _track_subtype(self, init_values):
record = self[0]
if 'recruitment_stage_id' in init_values and record.recruitment_stage_id:
return self.env.ref('hr_recruitment.mt_applicant_stage_changed')
return super(HRApplicant, self)._track_subtype(init_values)
def message_new(self, msg, custom_values=None):
stage = False
defaults = {}
if custom_values and 'hr_job_recruitment' in custom_values:
recruitment_stage_id = self.env['hr.job.recruitment'].browse(custom_values['hr_job_recruitment'])._get_first_stage()
if stage and stage.id:
defaults['recruitment_stage_id'] = recruitment_stage_id.id
res = super(HRApplicant, self).message_new(msg, custom_values=defaults)
return res
def reset_applicant(self):
""" Reinsert the applicant into the recruitment pipe in the first stage"""
default_stage = dict()
for hr_job_recruitment in self.mapped('hr_job_recruitment'):
default_stage[hr_job_recruitment.id] = self.env['hr.recruitment.stage'].search(
[
('job_recruitment_ids', '=', hr_job_recruitment.id),
('fold', '=', False)
], order='sequence asc', limit=1).id
for applicant in self:
applicant.write(
{'recruitment_stage_id': applicant.hr_job_recruitment.id and default_stage[applicant.hr_job_recruitment.id],
'refuse_reason_id': False})
@api.depends('recruitment_stage_id.hired_stage')
def _compute_date_closed(self):
for applicant in self:
if applicant.recruitment_stage_id and applicant.recruitment_stage_id.hired_stage and not applicant.date_closed:
applicant.date_closed = fields.datetime.now()
if not applicant.recruitment_stage_id.hired_stage:
applicant.date_closed = False
@api.depends('hr_job_recruitment')
def _compute_recruitment_stage(self):
for applicant in self:
if applicant.hr_job_recruitment:
if not applicant.recruitment_stage_id:
stage_ids = self.env['hr.recruitment.stage'].search([
'|',
('job_recruitment_ids', '=', False),
('job_recruitment_ids', '=', applicant.hr_job_recruitment.id),
('fold', '=', False)
], order='sequence asc', limit=1).ids
applicant.recruitment_stage_id = stage_ids[0] if stage_ids else False
else:
applicant.recruitment_stage_id = False
def _get_duration_from_tracking(self, trackings):
json = super()._get_duration_from_tracking(trackings)
now = datetime.now()
for applicant in self:
if applicant.refuse_reason_id and applicant.refuse_date:
json[applicant.recruitment_stage_id.id] -= (now - applicant.refuse_date).total_seconds()
return json
def create_employee_from_applicant(self):
self.ensure_one()
action = self.candidate_id.create_employee_from_candidate()
employee = self.env['hr.employee'].browse(action['res_id'])
employee.write({
'image_1920': self.candidate_image,
'job_id': self.job_id.id,
'job_title': self.job_id.name,
'department_id': self.department_id.id,
'work_email': self.department_id.company_id.email or self.email_from, # To have a valid email address by default
'work_phone': self.department_id.company_id.phone,
})
return action
class ApplicantGetRefuseReason(models.TransientModel):
_inherit = 'applicant.get.refuse.reason'
def action_refuse_reason_apply(self):
res = super(ApplicantGetRefuseReason, self).action_refuse_reason_apply()
refused_applications = self.applicant_ids
refused_applications.write({'refused_state': refused_applications.stage_id.id})
return res