recruitment Changes and fixes
This commit is contained in:
parent
c10892c62a
commit
6e1ce6a1ed
|
|
@ -42,6 +42,7 @@
|
|||
'views/requisitions.xml',
|
||||
'views/skills.xml',
|
||||
'wizards/post_onboarding_attachment_wizard.xml',
|
||||
'wizards/applicant_refuse_reason.xml',
|
||||
# 'views/resume_pearser.xml',
|
||||
],
|
||||
'assets': {
|
||||
|
|
|
|||
|
|
@ -8,5 +8,14 @@
|
|||
<field name="padding">5</field>
|
||||
<field name="number_next_actual">1</field>
|
||||
</record>
|
||||
|
||||
<record id="seq_hr_candidate" model="ir.sequence">
|
||||
<field name="name">HR Job Candidate Sequence</field>
|
||||
<field name="code">hr.job.candidate.sequence</field>
|
||||
<field name="prefix">C</field>
|
||||
<field name="padding">5</field>
|
||||
<field name="number_next_actual">1</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
|
|
|
|||
|
|
@ -16,6 +16,9 @@ class HRApplicant(models.Model):
|
|||
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")
|
||||
submitted_stage = fields.Many2one('hr.recruitment.stage')
|
||||
refused_stage = fields.Many2one('hr.recruitment.stage')
|
||||
refused_comments = fields.Text()
|
||||
|
||||
@api.model
|
||||
def _read_group_recruitment_stage_ids(self, stages, domain):
|
||||
|
|
@ -130,6 +133,7 @@ class HRApplicant(models.Model):
|
|||
)
|
||||
rec.submitted_to_client = True
|
||||
rec.client_submission_date = fields.Datetime.now()
|
||||
rec.submitted_stage = rec.recruitment_stage_id.id
|
||||
|
||||
def submit_for_approval(self):
|
||||
for rec in self:
|
||||
|
|
|
|||
|
|
@ -18,17 +18,20 @@ class HRJobRecruitment(models.Model):
|
|||
]
|
||||
|
||||
def _get_first_stage(self):
|
||||
"""This function is used to fetch the starting stage"""
|
||||
self.ensure_one()
|
||||
return self.env['hr.recruitment.stage'].search([
|
||||
('job_recruitment_ids', '=', self.id)], order='sequence asc', limit=1)
|
||||
|
||||
def _compute_application_count(self):
|
||||
"""this function is used to compute the application count"""
|
||||
read_group_result = self.env['hr.applicant']._read_group([('hr_job_recruitment', 'in', self.ids)], ['hr_job_recruitment'], ['__count'])
|
||||
result = {job.id: count for job, count in read_group_result}
|
||||
for job in self:
|
||||
job.application_count = result.get(job.id, 0)
|
||||
|
||||
def _compute_all_application_count(self):
|
||||
"this function is used to compute all the applicants count including inactive applicants"
|
||||
read_group_result = self.env['hr.applicant'].with_context(active_test=False)._read_group([
|
||||
('hr_job_recruitment', 'in', self.ids),
|
||||
'|',
|
||||
|
|
@ -41,6 +44,7 @@ class HRJobRecruitment(models.Model):
|
|||
job.all_application_count = result.get(job.id, 0)
|
||||
|
||||
def _compute_applicant_hired(self):
|
||||
"""this function is used to compute the hired applicants count"""
|
||||
hired_stages = self.env['hr.recruitment.stage'].search([('hired_stage', '=', True)])
|
||||
hired_data = self.env['hr.applicant']._read_group([
|
||||
('hr_job_recruitment', 'in', self.ids),
|
||||
|
|
@ -51,6 +55,7 @@ class HRJobRecruitment(models.Model):
|
|||
job.applicant_hired = job_hires.get(job.id, 0)
|
||||
|
||||
def _compute_new_application_count(self):
|
||||
"""sthis function is used to fetch the count of applicants those who are in starting stage"""
|
||||
self.env.cr.execute(
|
||||
"""
|
||||
WITH job_stage AS (
|
||||
|
|
@ -78,19 +83,20 @@ class HRJobRecruitment(models.Model):
|
|||
for job in self:
|
||||
job.new_application_count = new_applicant_count.get(job.id, 0)
|
||||
|
||||
|
||||
# # display_name = fields.Char(string='Name', compute='_compute_display_name', store=True)
|
||||
application_count = fields.Integer(compute='_compute_application_count', string="Application Count")
|
||||
all_application_count = fields.Integer(compute='_compute_all_application_count', string="All Application Count")
|
||||
new_application_count = fields.Integer(
|
||||
compute='_compute_new_application_count', string="New Application",
|
||||
help="Number of applications that are new in the flow (typically at first step of the flow)")
|
||||
applicant_hired = fields.Integer(compute='_compute_applicant_hired', string="Applicants Hired")
|
||||
|
||||
def _get_default_favorite_user_ids(self):
|
||||
"""this function is used to set the default users i.e current user"""
|
||||
return [(6, 0, [self.env.uid])]
|
||||
|
||||
@api.model
|
||||
def _default_address_id(self):
|
||||
"""this function is used to set the sefault company id """
|
||||
last_used_address = self.env['hr.job.recruitment'].search([('company_id', 'in', self.env.companies.ids)], order='id desc',
|
||||
limit=1)
|
||||
if last_used_address:
|
||||
|
|
@ -115,9 +121,11 @@ class HRJobRecruitment(models.Model):
|
|||
no_of_eligible_submissions = fields.Integer(string='Eligible Submissions', copy=False,
|
||||
help='Number of Submissions you expected to send.', default=1)
|
||||
|
||||
submission_status = fields.Selection([('zero','Zero Submissions'),('partial','Partial Submissions'),('filled','Filled Submissions')],default='zero', compute="_compute_submission_status", store=True)
|
||||
|
||||
@api.onchange("no_of_recruitment")
|
||||
def onchange_no_of_recruitments(self):
|
||||
"""this function is used to set the no_of_eligible submissions"""
|
||||
for rec in self:
|
||||
if rec.no_of_eligible_submissions <= 1:
|
||||
rec.no_of_eligible_submissions = rec.no_of_recruitment
|
||||
|
|
@ -186,7 +194,7 @@ class HRJobRecruitment(models.Model):
|
|||
store=True)
|
||||
no_of_submissions = fields.Integer(
|
||||
compute='_compute_no_of_submissions',
|
||||
string='Hired', copy=False,
|
||||
string='Submitted', copy=False, store=True,
|
||||
help='Number of Application submissions for this job position during recruitment phase.',
|
||||
)
|
||||
no_of_refused_submissions = fields.Integer(
|
||||
|
|
@ -213,6 +221,18 @@ class HRJobRecruitment(models.Model):
|
|||
if rec.job_category and rec.job_category.default_user:
|
||||
rec.user_id = rec.job_category.default_user.id
|
||||
|
||||
@api.depends('no_of_submissions','no_of_eligible_submissions')
|
||||
def _compute_submission_status(self):
|
||||
for rec in self:
|
||||
if rec.no_of_submissions == 0:
|
||||
rec.submission_status = 'zero'
|
||||
elif rec.no_of_submissions > 0 and rec.no_of_submissions < rec.no_of_eligible_submissions:
|
||||
rec.submission_status = 'partial'
|
||||
elif rec.no_of_submissions > 0 and rec.no_of_submissions >= rec.no_of_eligible_submissions:
|
||||
rec.submission_status = 'filled'
|
||||
else:
|
||||
rec.submission_status = 'zero'
|
||||
|
||||
|
||||
@api.depends('application_ids.submitted_to_client')
|
||||
def _compute_no_of_submissions(self):
|
||||
|
|
@ -318,6 +338,10 @@ class HRJobRecruitment(models.Model):
|
|||
result.append((record.id, name))
|
||||
return result
|
||||
|
||||
@api.depends('job_id', 'recruitment_sequence')
|
||||
def _compute_display_name(self):
|
||||
for rec in self:
|
||||
rec.display_name = False if not rec.recruitment_sequence else f"{rec.recruitment_sequence} ({rec.job_id.name})"
|
||||
|
||||
def buttion_view_applicants(self):
|
||||
if self.skill_ids:
|
||||
|
|
@ -334,11 +358,11 @@ class HRJobRecruitment(models.Model):
|
|||
action['context'] = dict(self._context)
|
||||
return action
|
||||
|
||||
def hr_job_recruitment_end_date_update(self):
|
||||
|
||||
def hr_job_recruitment_end_date_update(self):
|
||||
tomorrow_date = fields.Date.today() + timedelta(days=1)
|
||||
jobs_ending_tomorrow = self.sudo().search([('target_to', '=', tomorrow_date)])
|
||||
|
||||
jobs_unpublish_needed = self.sudo().search([('target_to','!=',False),('target_to', '<', fields.Date.today()),('website_published','=',True)])
|
||||
for job in jobs_ending_tomorrow:
|
||||
# Fetch recruiters (assuming job has a field recruiter_id or similar)
|
||||
recruiter = job.sudo().user_id # Replacne with the appropriate field name
|
||||
|
|
@ -348,13 +372,13 @@ class HRJobRecruitment(models.Model):
|
|||
'hr_recruitment_extended.template_recruitment_deadline_alert') # Replace with your email template XML ID
|
||||
if template:
|
||||
template.sudo().send_mail(recruiter.id, force_send=True)
|
||||
for job in jobs_unpublish_needed:
|
||||
job.sudo().write({'website_published': False})
|
||||
return True
|
||||
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
|
||||
|
||||
for vals in vals_list:
|
||||
vals["favorite_user_ids"] = vals.get("favorite_user_ids", [])
|
||||
if vals.get('recruitment_sequence', '/') == '/':
|
||||
|
|
@ -377,8 +401,6 @@ class HRJobRecruitment(models.Model):
|
|||
|
||||
return jobs
|
||||
|
||||
def buttion_view_applicants(self):
|
||||
pass
|
||||
|
||||
def action_open_attachments(self):
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from odoo import models, fields, api, _
|
||||
from odoo import models, fields, api, _, tools
|
||||
from odoo.exceptions import ValidationError, UserError
|
||||
from datetime import date
|
||||
from datetime import timedelta
|
||||
|
|
@ -14,7 +14,12 @@ import datetime
|
|||
class HrCandidate(models.Model):
|
||||
_inherit = "hr.candidate"
|
||||
|
||||
_sql_constraints = [
|
||||
('unique_candidate_sequence', 'UNIQUE(candidate_sequence)', 'Candidate sequence must be unique!'),
|
||||
]
|
||||
#personal Details
|
||||
candidate_sequence = fields.Char(string='Candidate Sequence', readonly=False, default='/', copy=False)
|
||||
|
||||
first_name = fields.Char(string='First Name',required=False, help="This is the person's first name, given at birth or during a naming ceremony. It’s the name people use to address you.")
|
||||
middle_name = fields.Char(string='Middle Name', help="This is an extra name that comes between the first name and last name. Not everyone has a middle name")
|
||||
last_name = fields.Char(string='Last Name',required=False, help="This is the family name, shared with other family members. It’s usually the last name.")
|
||||
|
|
@ -23,6 +28,62 @@ class HrCandidate(models.Model):
|
|||
employee_code = fields.Char(related="employee_id.employee_id")
|
||||
resume = fields.Binary()
|
||||
|
||||
applications_stages_stat = fields.Many2many('application.stage.status',string="Applications History", compute="_compute_applications_stages_stat")
|
||||
# availability_status = fields.Selection([('available','Available'),('not_available','Not Available'),('hired','Hired'),('abscond','Abscond')])
|
||||
|
||||
@api.depends('applicant_ids')
|
||||
def _compute_applications_stages_stat(self):
|
||||
for rec in self:
|
||||
if rec.applicant_ids:
|
||||
stage_status_records = self.env['application.stage.status'].with_context(active_test=False).search([
|
||||
('applicant_id', 'in', rec.applicant_ids.ids)
|
||||
])
|
||||
rec.applications_stages_stat = [(6, 0, stage_status_records.ids)]
|
||||
else:
|
||||
rec.applications_stages_stat = [(5,)]
|
||||
|
||||
@api.depends('partner_name', 'candidate_sequence')
|
||||
def _compute_display_name(self):
|
||||
for rec in self:
|
||||
rec.display_name = rec.partner_name if not rec.candidate_sequence else f"{rec.partner_name} ({rec.candidate_sequence})"
|
||||
|
||||
@api.constrains('email_from', 'partner_phone', 'alternate_phone')
|
||||
def _candidate_unique_constraints(self):
|
||||
for rec in self:
|
||||
# Check for unique email
|
||||
if rec.email_from:
|
||||
existing_email = self.sudo().search([('id', '!=', rec.id), ('email_from', '=', rec.email_from)],
|
||||
limit=1)
|
||||
if existing_email:
|
||||
raise ValidationError(_("A candidate with the email '%s' already exists, sourced by %s %s.") % (
|
||||
existing_email.email_from, existing_email.user_id.name, existing_email.candidate_sequence if existing_email.candidate_sequence else ''))
|
||||
|
||||
# Check for unique phone number (partner_phone or alternate_phone)
|
||||
if rec.partner_phone:
|
||||
existing_phone = self.sudo().search(
|
||||
[('id', '!=', rec.id), '|', ('partner_phone', '=', rec.partner_phone),
|
||||
('alternate_phone', '=', rec.partner_phone)], limit=1)
|
||||
if existing_phone:
|
||||
raise ValidationError(_("A candidate with the phone number '%s' already exists, sourced by %s %s.") % (
|
||||
existing_phone.partner_phone, existing_phone.user_id.name, existing_phone.candidate_sequence if existing_phone.candidate_sequence else ''))
|
||||
|
||||
if rec.alternate_phone:
|
||||
existing_al_phone = self.sudo().search(
|
||||
[('id', '!=', rec.id), '|', ('partner_phone', '=', rec.alternate_phone),
|
||||
('alternate_phone', '=', rec.alternate_phone)], limit=1)
|
||||
if existing_al_phone:
|
||||
raise ValidationError(
|
||||
_("A candidate with the alternate phone number '%s' already exists, sourced by %s %s.") % (
|
||||
existing_al_phone.alternate_phone, existing_al_phone.user_id.name, existing_al_phone.candidate_sequence if existing_al_phone.candidate_sequence else ''))
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
for vals in vals_list:
|
||||
if vals.get('candidate_sequence', '/') == '/':
|
||||
vals['candidate_sequence'] = self.env['ir.sequence'].next_by_code(
|
||||
'hr.job.candidate.sequence') or '/'
|
||||
return super(HrCandidate, self).create(vals_list)
|
||||
|
||||
|
||||
|
||||
def create_employee_from_candidate(self):
|
||||
|
|
@ -134,12 +195,12 @@ class HRApplicant(models.Model):
|
|||
|
||||
exp_type = fields.Selection([('fresher','Fresher'),('experienced','Experienced')], default='fresher', required=True)
|
||||
|
||||
total_exp = fields.Integer(string="Total Experience")
|
||||
relevant_exp = fields.Integer(string="Relevant Experience")
|
||||
total_exp_type = fields.Selection([('month',"Month's"),('year',"Year's")])
|
||||
relevant_exp_type = fields.Selection([('month',"Month's"),('year',"Year's")])
|
||||
total_exp = fields.Float(string="Total Experience")
|
||||
relevant_exp = fields.Float(string="Relevant Experience")
|
||||
total_exp_type = fields.Selection([('month',"Month's"),('year',"Year's")], default='year')
|
||||
relevant_exp_type = fields.Selection([('month',"Month's"),('year',"Year's")], default='year')
|
||||
notice_period = fields.Integer(string="Notice Period")
|
||||
notice_period_type = fields.Selection([('day',"Day's"),('month',"Month's"),('year',"Year's")], string='Type')
|
||||
notice_period_type = fields.Selection([('day',"Day's"),('month',"Month's"),('year',"Year's")], string='Type', default='day')
|
||||
|
||||
current_ctc = fields.Float(string="Current CTC", aggregator="avg", help="Applicant Current Salary", tracking=True, groups="hr_recruitment.group_hr_recruitment_user")
|
||||
salary_expected = fields.Float("Expected CTC", aggregator="avg", help="Salary Expected by Applicant", tracking=True, groups="hr_recruitment.group_hr_recruitment_user")
|
||||
|
|
@ -148,7 +209,7 @@ class HRApplicant(models.Model):
|
|||
holding_offer = fields.Char(string="Holding Offer")
|
||||
applicant_comments = fields.Text(string='Applicant Comments')
|
||||
recruiter_comments = fields.Text(string='Recruiter Comments')
|
||||
|
||||
medium_id = fields.Many2one(string='Mode')
|
||||
doj = fields.Date(tracking=True)
|
||||
gender = fields.Selection([
|
||||
('male', 'Male'),
|
||||
|
|
@ -423,3 +484,56 @@ class RecruitmentCategory(models.Model):
|
|||
|
||||
category_name = fields.Char(string="Category Name")
|
||||
default_user = fields.Many2one('res.users')
|
||||
|
||||
|
||||
class ApplicationsStageStatus(models.Model):
|
||||
_name = 'application.stage.status'
|
||||
_rec_name = 'applicant_id'
|
||||
_auto = False
|
||||
|
||||
applicant_id = fields.Many2one('hr.applicant')
|
||||
job_request = fields.Many2one('hr.job.recruitment', related='applicant_id.hr_job_recruitment')
|
||||
stage_id = fields.Many2one('hr.recruitment.stage', related="applicant_id.recruitment_stage_id")
|
||||
application_status = fields.Selection([
|
||||
('ongoing', 'Ongoing'),
|
||||
('hired', 'Hired'),
|
||||
('refused', 'Refused'),
|
||||
('archived', 'Archived'),
|
||||
], related='applicant_id.application_status')
|
||||
stage_color = fields.Char(related='applicant_id.stage_color', widget='color')
|
||||
stage_color_int = fields.Integer("Stage Color Int", compute="_compute_stage_color")
|
||||
|
||||
@api.depends("application_status")
|
||||
def _compute_stage_color(self):
|
||||
for record in self:
|
||||
if record.application_status == 'hired':
|
||||
record.stage_color_int = 10
|
||||
elif record.application_status == 'ongoing':
|
||||
record.stage_color_int = 3
|
||||
elif record.application_status == 'refused':
|
||||
record.stage_color_int = 1
|
||||
elif record.application_status == 'archived':
|
||||
record.stage_color_int = 9
|
||||
else:
|
||||
record.stage_color_int = 0
|
||||
|
||||
|
||||
@api.depends('applicant_id', 'stage_id', 'application_status', 'job_request', 'stage_color_int')
|
||||
def _compute_display_name(self):
|
||||
for rec in self:
|
||||
rec.display_name = rec.applicant_id.display_name if not rec.stage_id else f"({rec.job_request.recruitment_sequence}){rec.applicant_id.display_name} {rec.stage_color_int} - ({rec.stage_id.name}, {rec.application_status})"
|
||||
|
||||
def init(self):
|
||||
""" Create the SQL view for application.stage.status """
|
||||
tools.drop_view_if_exists(self.env.cr, self._table)
|
||||
self.env.cr.execute("""
|
||||
CREATE OR REPLACE VIEW %s AS (
|
||||
SELECT
|
||||
a.id AS id, -- Ensuring a unique primary key
|
||||
a.id AS applicant_id
|
||||
FROM
|
||||
hr_applicant a
|
||||
WHERE
|
||||
a.active = 't' or a.active = 'f'
|
||||
);
|
||||
""" % (self._table))
|
||||
|
|
@ -4,4 +4,4 @@ from odoo import models, fields, api, _
|
|||
class ResPartner(models.Model):
|
||||
_inherit = 'res.partner'
|
||||
|
||||
contact_type = fields.Selection([('internal','Internal'),('external','External')], required=True, default='internal')
|
||||
contact_type = fields.Selection([('internal','Internal'),('external','External')], required=True, default='external')
|
||||
|
|
@ -109,6 +109,6 @@ class Job(models.Model):
|
|||
def unlink(self):
|
||||
# Remove stage from all related jobs when stage is deleted
|
||||
for job in self:
|
||||
for stage in stage.recruitment_stage_ids:
|
||||
for stage in job.recruitment_stage_ids:
|
||||
stage.write({'job_recruitment_ids': [(3, job.id)]})
|
||||
return super(Job, self).unlink()
|
||||
|
|
|
|||
|
|
@ -20,3 +20,9 @@ access_recruitment_attachments_user,access.recruitment.attachments.user,model_re
|
|||
access_post_onboarding_attachment_wizard,access.post.onboarding.attachment.wizard,model_post_onboarding_attachment_wizard,base.group_user,1,1,1,1
|
||||
access_employee_recruitment_attachments,employee.recruitment.attachments,model_employee_recruitment_attachments,base.group_user,1,1,1,1
|
||||
|
||||
|
||||
hr_recruitment.access_hr_applicant_interviewer,hr.applicant.interviewer,hr_recruitment.model_hr_applicant,hr_recruitment.group_hr_recruitment_interviewer,1,1,1,0
|
||||
hr_recruitment.access_hr_recruitment_stage_user,hr.recruitment.stage.user,hr_recruitment.model_hr_recruitment_stage,hr_recruitment.group_hr_recruitment_user,1,1,1,0
|
||||
|
||||
|
||||
access_application_stage_status,application.stage.status,model_application_stage_status,base.group_user,1,1,1,1
|
||||
|
|
|
@ -1,6 +1,18 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
|
||||
<record id="hr_applicant_view_list_inherit" model="ir.ui.view">
|
||||
<field name="name">hr.applicant.view.list</field>
|
||||
<field name="inherit_id" ref="hr_recruitment.crm_case_tree_view_job"/>
|
||||
<field name="model">hr.applicant</field>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='stage_id']" position="attributes">
|
||||
<attribute name="column_invisible">1</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='stage_id']" position="after">
|
||||
<field name="recruitment_stage_id"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
<record id="hr_applicant_view_form_inherit" model="ir.ui.view">
|
||||
<field name="name">hr.applicant.view.form</field>
|
||||
<field name="model">hr.applicant</field>
|
||||
|
|
@ -106,6 +118,16 @@
|
|||
</group>
|
||||
</page>
|
||||
</xpath>
|
||||
<xpath expr="//notebook" position="after">
|
||||
<sheet invisible="application_status != 'refused'">
|
||||
<group invisible="application_status != 'refused'">
|
||||
<field name="refused_stage" readonly="1" force_save="1" invisible="application_status != 'refused'"/>
|
||||
<field name="refuse_date" string="Refused On" readonly="1" force_save="1" invisible="application_status != 'refused'"/>
|
||||
<field name="refused_comments" invisible="application_status != 'refused'"/>
|
||||
</group>
|
||||
</sheet>
|
||||
</xpath>
|
||||
|
||||
</field>
|
||||
</record>
|
||||
<record id="hr_applicant_view_search_bis_inherit" model="ir.ui.view">
|
||||
|
|
@ -128,7 +150,6 @@
|
|||
<filter string="Job Recruitment Stage" name="job_recruitment_stage" domain="[]"
|
||||
context="{'group_by': 'recruitment_stage_id'}"/>
|
||||
</xpath>
|
||||
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
|
@ -172,10 +193,36 @@
|
|||
invisible="application_status != 'refused'"/>
|
||||
<widget name="web_ribbon" title="Archived" bg_color="text-bg-secondary"
|
||||
invisible="application_status != 'archived'"/>
|
||||
<div class="d-flex align-items-baseline gap-1 ms-2">
|
||||
<field t-if="record.partner_name.raw_value" class="fw-bold fs-5" name="partner_name"/>
|
||||
<field name="job_id" invisible="context.get('search_default_job_id', False)"/>
|
||||
</div>
|
||||
<div class="row g-0 mt-0 mt-sm-3 ms-2">
|
||||
<div class="col-7">
|
||||
<!-- Categories and applicant properties -->
|
||||
<field name="categ_ids" widget="many2many_tags" options="{'color_field': 'color'}"/>
|
||||
<field name="applicant_properties" widget="properties"/>
|
||||
</div>
|
||||
<div class="col-5">
|
||||
<!-- Refused information, visible when status is 'refused' -->
|
||||
<div invisible="application_status != 'refused'">
|
||||
<sheet>
|
||||
<!-- Refused Stage -->
|
||||
<div class="form-group">
|
||||
<label for="refuse_reason_id"><strong>Refuse Details</strong></label>
|
||||
<field name="refuse_reason_id" readonly="1" force_save="1"
|
||||
invisible="application_status != 'refused'"/>
|
||||
</div>
|
||||
|
||||
<!-- Refuse Date -->
|
||||
<div class="form-group">
|
||||
<field name="refuse_date" string="Refused On" readonly="1" force_save="1"
|
||||
invisible="application_status != 'refused'" widget="date"/>
|
||||
</div>
|
||||
</sheet>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<footer>
|
||||
<field name="priority" widget="priority"/>
|
||||
<field class="ms-1 align-items-center" name="activity_ids" widget="kanban_activity"/>
|
||||
|
|
@ -251,7 +298,7 @@
|
|||
<record id="hr_recruitment.crm_case_categ0_act_job" model="ir.actions.act_window">
|
||||
<field name="search_view_id" ref="hr_applicant_view_search_bis_inherit"/>
|
||||
<field name="context">
|
||||
{"search_default_job_recruitment_stage":1,"search_default_job_recruitment":1,"search_default_my_applications":1}
|
||||
{"search_default_job_recruitment_stage":0,"search_default_job_recruitment":1,"search_default_my_applications":1}
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@
|
|||
<field name="alias_id" invisible="not alias_name" column_invisible="True" optional="hide"/>
|
||||
<field name="user_id" widget="many2one_avatar_user" optional="hide"/>
|
||||
<field name="no_of_employee"/>
|
||||
<field name="submission_status" optional="hide"/>
|
||||
</list>
|
||||
|
||||
</field>
|
||||
|
|
@ -121,7 +122,7 @@
|
|||
<field name="requested_by" can_create="True" can_write="True"/>
|
||||
|
||||
<field name="department_id" can_create="True" can_write="True" invisible="1"/>
|
||||
<label for="address_id"/>
|
||||
<label for="address_id" string="Requested By Company"/>
|
||||
<div class="o_row">
|
||||
<span invisible="address_id" class="oe_read_only">Remote</span>
|
||||
<field name="address_id" context="{'show_address': 1}" placeholder="Remote"
|
||||
|
|
@ -184,6 +185,7 @@
|
|||
</group>
|
||||
</group>
|
||||
<field name="job_properties" columns="2"/>
|
||||
<field name="submission_status" invisible="1" force_save="1"/>
|
||||
</page>
|
||||
<page string="Job Summary" name="job_description_page" invisible="0">
|
||||
<field name="description" options="{'collaborative': true}"
|
||||
|
|
@ -210,8 +212,25 @@
|
|||
<field name="department_id" operator="child_of"/>
|
||||
<field name="user_id" string="Primary Recruiter"/>
|
||||
<field name="interviewer_ids" string="Secondary Recruiters"/>
|
||||
<field name="no_of_submissions"/>
|
||||
<field name="no_of_eligible_submissions"/>
|
||||
<field name="submission_status"/>
|
||||
<separator/>
|
||||
<filter string="Published Records" name="published_records" domain="[('website_published','=',True)]"/>
|
||||
<separator/>
|
||||
<filter string="My Assignments" name="my_assignments" domain="['|',('user_id', '=', uid), ('interviewer_ids','in',uid)]"/>
|
||||
<separator/>
|
||||
<filter string="Zero Submissions" name="zero_submissions"
|
||||
domain="[('submission_status', '=', 'zero')]"/>
|
||||
|
||||
<!-- Partial Submissions -->
|
||||
<filter string="Partial Submissions" name="partial_submissions"
|
||||
domain="[('submission_status', '=', 'partial')]"/>
|
||||
|
||||
<!-- Filled Submissions -->
|
||||
<filter string="Filled Submissions" name="filled_submissions"
|
||||
domain="[('submission_status', '=', 'filled')]"/>
|
||||
<separator/>
|
||||
<filter string="My Assignments" name="my_assignments" domain="['|',('user_id', '=', uid),('interviewer_ids','in',uid)]"/>
|
||||
<filter name="message_needaction" string="Unread Messages"
|
||||
domain="[('message_needaction', '=', True)]"
|
||||
groups="mail.group_mail_notification_type_inbox"/>
|
||||
|
|
@ -224,6 +243,9 @@
|
|||
groups="base.group_multi_company"/>
|
||||
<filter string="Employment Type" name="employment_type" domain="[]"
|
||||
context="{'group_by': 'contract_type_id'}"/>
|
||||
<filter string="Submission Status" name="submission_status" domain="[]"
|
||||
context="{'group_by': 'submission_status'}"/>
|
||||
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
|
|
@ -238,9 +260,9 @@
|
|||
<field name="active"/>
|
||||
<field name="alias_email" invisible="1"/>
|
||||
<templates>
|
||||
<t t-name="menu" groups="hr_recruitment.group_hr_recruitment_user">
|
||||
<t t-name="menu">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="row" groups="hr_recruitment.group_hr_recruitment_user">
|
||||
<div class="col-6">
|
||||
<h5 role="menuitem" class="o_kanban_card_manage_title">
|
||||
<span>View</span>
|
||||
|
|
@ -278,15 +300,19 @@
|
|||
</div>
|
||||
<div class="col-6" role="menuitem">
|
||||
<a class="dropdown-item" t-if="widget.editable" name="edit_job" type="open">Configuration</a>
|
||||
<a class="dropdown-item" t-if="record.active.raw_value" type="archive">Archive</a>
|
||||
<a class="dropdown-item" t-if="!record.active.raw_value" name="toggle_active" type="object">Unarchive</a>
|
||||
<a class="dropdown-item" t-if="record.active.raw_value" type="archive" groups="hr_recruitment.group_hr_recruitment_user">Archive</a>
|
||||
<a class="dropdown-item" t-if="!record.active.raw_value" name="toggle_active" type="object" groups="hr_recruitment.group_hr_recruitment_user">Unarchive</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
<t t-name="card">
|
||||
<div class="d-flex align-items-baseline gap-1 ms-2">
|
||||
<field name="is_favorite" widget="boolean_favorite" nolabel="1"/>
|
||||
<div class="d-flex align-items-baseline gap-1 ms-2"
|
||||
t-att-style="
|
||||
record.submission_status.raw_value == 'zero' ? 'background-color: #f8d7da; padding: 5px; border-radius: 4px;' :
|
||||
record.submission_status.raw_value == 'partial' ? 'background-color: #fff3cd; padding: 5px; border-radius: 4px;' :
|
||||
record.submission_status.raw_value == 'filled' ? 'background-color: #d4edda; padding: 5px; border-radius: 4px;' : 'padding: 5px; border-radius: 4px;'">
|
||||
<field name="is_favorite" widget="boolean_favorite" style="color:red" nolabel="1"/>
|
||||
<div class="o_kanban_card_header_title d-flex flex-column">
|
||||
<div class="oe_row">
|
||||
<field name="recruitment_sequence" class="fw-bold fs-4"/> -
|
||||
|
|
@ -306,6 +332,12 @@
|
|||
<button class="btn btn-primary" name="%(action_hr_job_recruitment_applications)d" type="action">
|
||||
<field name="new_application_count"/> New Applications
|
||||
</button>
|
||||
<br/>
|
||||
<div t-if="widget.editable" style="padding-top: 5px">
|
||||
<button class="btn btn-primary" name="edit_job" type="open">
|
||||
Open
|
||||
</button>
|
||||
</div>
|
||||
<br/><br/>
|
||||
<div t-if="record.budget.value">
|
||||
<strong>Budget : </strong> <field name="budget"/>
|
||||
|
|
@ -318,6 +350,9 @@
|
|||
<span t-attf-class="{{ record.no_of_recruitment.raw_value > 0 ? 'text-primary fw-bolder' : 'text-secondary' }}" groups="!hr_recruitment.group_hr_recruitment_user">
|
||||
<field name="no_of_recruitment"/> To Recruit
|
||||
</span>
|
||||
<div t-if="record.no_of_eligible_submissions.raw_value > 0">
|
||||
<field name="no_of_eligible_submissions"/> To Submit
|
||||
</div>
|
||||
<div t-if="record.application_count.raw_value > 0">
|
||||
<field name="application_count"/> Applications
|
||||
</div>
|
||||
|
|
@ -329,7 +364,8 @@
|
|||
<field name="no_of_submissions"/>
|
||||
Submissions
|
||||
</div>
|
||||
|
||||
<field name="no_of_submissions" force_save="1" invisible="1"/>
|
||||
<field name="submission_status" invisible="1" force_save="1"/>
|
||||
<div t-if="record.no_of_refused_submissions.raw_value > 0">
|
||||
<field name="no_of_refused_submissions"/>
|
||||
Refused Submissions
|
||||
|
|
@ -354,7 +390,7 @@
|
|||
<field name="res_model">hr.job.recruitment</field>
|
||||
<field name="view_mode">kanban,list,form,search</field>
|
||||
<field name="search_view_id" ref="view_job_recruitment_filter"/>
|
||||
<field name="context">{"search_default_Current":1,"search_default_my_assignments":1}</field>
|
||||
<field name="context">{"search_default_Current":1,"search_default_my_assignments":1,"search_default_published_records":1,'no_of_eligible_submissions': 0}</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Ready to recruit more efficiently?
|
||||
|
|
@ -367,21 +403,27 @@
|
|||
|
||||
|
||||
|
||||
<menuitem
|
||||
name="Applications"
|
||||
parent="hr_recruitment.menu_hr_recruitment_root"
|
||||
id="hr_recruitment.menu_crm_case_categ0_act_job"
|
||||
active="0"
|
||||
sequence="2"/>
|
||||
|
||||
<menuitem
|
||||
name="Job Positions Recruitment"
|
||||
name="Job Description"
|
||||
id="menu_hr_job_recruitment_interviewer"
|
||||
parent="hr_recruitment.menu_crm_case_categ0_act_job"
|
||||
parent="hr_recruitment.menu_hr_recruitment_root"
|
||||
action="action_hr_job_recruitment"
|
||||
sequence="1"
|
||||
groups="base.group_user"/>
|
||||
|
||||
<menuitem
|
||||
name="By Job Positions"
|
||||
name="Job Positions"
|
||||
id="hr_recruitment.menu_hr_job_position"
|
||||
parent="hr_recruitment.menu_crm_case_categ0_act_job"
|
||||
parent="hr_recruitment.menu_hr_recruitment_config_jobs"
|
||||
action="hr_recruitment.action_hr_job"
|
||||
sequence="30"
|
||||
sequence="0"
|
||||
groups="hr_recruitment.group_hr_recruitment_user"/>
|
||||
|
||||
<!-- <menuitem-->
|
||||
|
|
|
|||
|
|
@ -176,8 +176,9 @@
|
|||
<xpath expr="//field[@name='linkedin_profile']" position="after">
|
||||
<field name="exp_type"/>
|
||||
<field name="resume" force_save="1"/>
|
||||
<field name="submitted_to_client" force_save="1" readonly="1"/>
|
||||
<field name="client_submission_date" force_save="1" readonly="1"/>
|
||||
<field name="submitted_to_client" force_save="1" readonly="1" invisible="not submitted_to_client"/>
|
||||
<field name="client_submission_date" force_save="1" readonly="1" invisible="not submitted_to_client"/>
|
||||
<field name="submitted_stage" force_save="1" readonly="1" invisible="not submitted_to_client"/>
|
||||
</xpath>
|
||||
<xpath expr="//group[@name='recruitment_contract']/label[@for='salary_expected']" position="before">
|
||||
<field name="current_ctc"/>
|
||||
|
|
@ -234,12 +235,20 @@
|
|||
<!-- <xpath expr="//field[@name='partner_name']" position="attributes">-->
|
||||
<!-- <attribute name="readonly">1</attribute>-->
|
||||
<!-- </xpath>-->
|
||||
<!-- <xpath expr="" position="before">-->
|
||||
<!-- <field name="candidate_sequence"/>-->
|
||||
<!-- </xpath>-->
|
||||
<xpath expr="//widget[@name='web_ribbon']" position="after">
|
||||
<div class="o_employee_avatar m-0 p-0">
|
||||
<field name="candidate_image" widget="image" class="oe_avatar m-0"
|
||||
options="{"zoom": true, "preview_image":"candidate_image"}"/>
|
||||
|
||||
</div>
|
||||
<div class="oe_title mw-75 ps-0 pe-2">
|
||||
<h1>
|
||||
<field name="candidate_sequence"/>
|
||||
</h1>
|
||||
</div>
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//form/sheet/group" position="before">
|
||||
|
|
@ -261,6 +270,75 @@
|
|||
</field>
|
||||
</record>
|
||||
|
||||
<record id="hr_candidate_view_search_inherit" model="ir.ui.view">
|
||||
<field name="name">hr.candidate.view.search.inherit</field>
|
||||
<field name="model">hr.candidate</field>
|
||||
<field name="inherit_id" ref="hr_recruitment.hr_candidate_view_search"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='partner_name']" position="after">
|
||||
<field name="candidate_sequence"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
<record id="hr_candidate_view_kanban_inherit" model="ir.ui.view">
|
||||
<field name="name">hr.candidate.view.kanban.inherit</field>
|
||||
<field name="model">hr.candidate</field>
|
||||
<field name="inherit_id" ref="hr_recruitment.hr_candidate_view_kanban"/>
|
||||
<field name="arch" type="xml">
|
||||
<!-- Ensure applicant_ids is included in the kanban field list -->
|
||||
<xpath expr="//kanban" position="inside">
|
||||
<field name="applicant_ids" context="{'active_test': False}"/>
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//kanban" position="attributes">
|
||||
<attribute name="context">{'active_test': False, 'kanban': True}</attribute>
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//field[@name='partner_name']" position="before">
|
||||
<field t-if="record.candidate_sequence.raw_value" name="candidate_sequence" class="fw-bold fs-4"/>
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//t[@t-name='card']" position="inside">
|
||||
<t t-if="record.applicant_ids and record.applicant_ids.value">
|
||||
<div class="mt-2">
|
||||
<strong>Application History:</strong>
|
||||
<div class="o_kanban_application_history">
|
||||
|
||||
<field name="applications_stages_stat" widget="many2many_tags" context="{'active_test': False}"
|
||||
options="{'color_field':'stage_color_int'}"/>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</xpath>
|
||||
<!-- <t t-if="record.applicant_ids and record.applicant_ids.value">-->
|
||||
<!-- <field name="applicant_ids" widget="kanban_one2many" options="{'display_field': ['job_id']}" />-->
|
||||
|
||||
<!--<!– <t t-foreach="record.applicant_ids.value" t-as="application">–>-->
|
||||
<!--<!– <div class="d-flex align-items-center">–>-->
|
||||
<!--<!– <span class="badge bg-info">–>-->
|
||||
<!--<!– <t t-if="application.job_id">–>-->
|
||||
<!--<!– <t t-esc="application.job_id.value"/>–>-->
|
||||
<!--<!– </t>–>-->
|
||||
<!--<!– <t t-else="">No Job</t>–>-->
|
||||
<!--<!– </span>–>-->
|
||||
<!--<!– <span class="ms-2 text-muted">–>-->
|
||||
<!--<!– <t t-if="application.stage_id">–>-->
|
||||
<!--<!– <t t-esc="application.stage_id.value"/>–>-->
|
||||
<!--<!– </t>–>-->
|
||||
<!--<!– <t t-else="">No Stage</t>–>-->
|
||||
<!--<!– </span>–>-->
|
||||
<!--<!– </div>–>-->
|
||||
<!--<!– </t>–>-->
|
||||
<!-- </t>-->
|
||||
<!-- <t t-else="">-->
|
||||
<!-- <span class="text-muted">No application history</span>-->
|
||||
<!-- </t>-->
|
||||
<!-- </div>-->
|
||||
<!-- </div>-->
|
||||
<!-- </xpath>-->
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- explicit list view definition -->
|
||||
<!--
|
||||
<record model="ir.ui.view" id="hr_recruitment_extended.list">
|
||||
|
|
@ -340,9 +418,23 @@
|
|||
parent="hr_recruitment.menu_crm_case_categ0_act_job"
|
||||
action="hr_recruitment.action_hr_job_interviewer"
|
||||
sequence="31"
|
||||
active="0"
|
||||
groups="base.group_no_one"/>
|
||||
|
||||
|
||||
|
||||
<menuitem
|
||||
name="Applications"
|
||||
parent="hr_recruitment.menu_hr_recruitment_root"
|
||||
id="hr_recruitment.menu_crm_case_categ_all_app"
|
||||
action="hr_recruitment.crm_case_categ0_act_job"
|
||||
sequence="2"/>
|
||||
|
||||
<menuitem
|
||||
name="Candidates"
|
||||
parent="hr_recruitment.menu_hr_recruitment_root"
|
||||
id="hr_recruitment.menu_hr_candidate"
|
||||
action="hr_recruitment.action_hr_candidate"
|
||||
sequence="3"/>
|
||||
</data>
|
||||
</odoo>
|
||||
|
|
|
|||
|
|
@ -1 +1,2 @@
|
|||
from . import post_onboarding_attachment_wizard
|
||||
from . import applicant_refuse_reason
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
|
||||
from datetime import datetime
|
||||
from markupsafe import Markup
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.osv import expression
|
||||
|
||||
|
||||
class ApplicantGetRefuseReason(models.TransientModel):
|
||||
_inherit = 'applicant.get.refuse.reason'
|
||||
|
||||
refused_comments = fields.Text()
|
||||
|
||||
def action_refuse_reason_apply(self):
|
||||
if self.send_mail:
|
||||
if not self.template_id:
|
||||
raise UserError(_("Email template must be selected to send a mail"))
|
||||
if any(not (applicant.email_from or applicant.partner_id.email) for applicant in self.applicant_ids):
|
||||
raise UserError(_("At least one applicant doesn't have a email; you can't use send email option."))
|
||||
|
||||
refused_applications = self.applicant_ids
|
||||
# duplicates_count can be true only if only one application is selected
|
||||
if self.duplicates_count and self.duplicates:
|
||||
applicant_id = self.applicant_ids[0]
|
||||
duplicate_domain = applicant_id.candidate_id._get_similar_candidates_domain()
|
||||
duplicates = self.env['hr.candidate'].search(duplicate_domain).applicant_ids
|
||||
refused_applications |= duplicates
|
||||
url = applicant_id._get_html_link()
|
||||
message = _(
|
||||
"Refused automatically because this application has been identified as a duplicate of %(link)s",
|
||||
link=url)
|
||||
duplicates._message_log_batch(bodies={duplicate.id: message for duplicate in duplicates})
|
||||
|
||||
refused_applications.write({'refuse_reason_id': self.refuse_reason_id.id, 'active': False, 'refuse_date': datetime.now(),'refused_comments': self.refused_comments})
|
||||
for applicant in refused_applications:
|
||||
applicant.write({'refused_stage': applicant.recruitment_stage_id.id})
|
||||
|
||||
if self.send_mail:
|
||||
# TDE note: keeping 16.0 behavior, clean me please
|
||||
message_values = {
|
||||
'email_layout_xmlid' : 'hr_recruitment.mail_notification_light_without_background',
|
||||
}
|
||||
if len(self.applicant_ids) > 1:
|
||||
self.applicant_ids.with_context(active_test=True).message_mail_with_source(
|
||||
self.template_id,
|
||||
auto_delete_keep_log=True,
|
||||
**message_values
|
||||
)
|
||||
else:
|
||||
self.applicant_ids.with_context(active_test=True).message_post_with_source(
|
||||
self.template_id,
|
||||
subtype_xmlid='mail.mt_note',
|
||||
**message_values
|
||||
)
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
<record id="applicant_get_refuse_reason_view_form_inherit" model="ir.ui.view">
|
||||
<field name="name">applicant.get.refuse.reason.form.inherit</field>
|
||||
<field name="model">applicant.get.refuse.reason</field>
|
||||
<field name="inherit_id" ref="hr_recruitment.applicant_get_refuse_reason_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='refuse_reason_id']" position="after">
|
||||
<field name="refused_comments" placeholder="Comments if any..."/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
|
@ -468,6 +468,8 @@ class WebsiteJobHrRecruitment(WebsiteHrRecruitment):
|
|||
# candidate_skills_list.append(skill.id)
|
||||
if candidate.candidate_skill_ids and candidate_skills['skill_id'] not in candidate.candidate_skill_ids.skill_id.ids:
|
||||
candidate.write({'candidate_skill_ids':[(0,4,candidate_skills)]})
|
||||
else:
|
||||
candidate.write({'candidate_skill_ids':[(0,4,candidate_skills)]})
|
||||
|
||||
|
||||
else:
|
||||
|
|
|
|||
Loading…
Reference in New Issue