[IMP] hr_recruitment: improve flow (back2basics)

* Redesign views
    * New layout for the job kanban card + redesigned menu
* Create new reports
    * Candidate Sources: get a better view on where applicants are
      coming from;
    * Time in Stage: shows how long an applicant is in the current
      stage.
* Archiving a job archives applicants as well

odoo/enterprise#24816
odoo/upgrade#3395

closes odoo/odoo#85479

Taskid: 2738082
Signed-off-by: Yannick Tivisse (yti) <yti@odoo.com>
This commit is contained in:
Kevin Baptiste
2022-04-01 13:37:09 +02:00
parent 80c662f808
commit 6c5f38ae37
28 changed files with 670 additions and 275 deletions
+2 -18
View File
@@ -11,13 +11,14 @@ class Job(models.Model):
_inherit = ['mail.thread']
_order = 'sequence'
active = fields.Boolean(default=True)
name = fields.Char(string='Job Position', required=True, index='trigram', translate=True)
sequence = fields.Integer(default=10)
expected_employees = fields.Integer(compute='_compute_employees', string='Total Forecasted Employees', store=True,
help='Expected number of employees for this job position after new recruitment.')
no_of_employee = fields.Integer(compute='_compute_employees', string="Current Number of Employees", store=True,
help='Number of employees currently occupying this job position.')
no_of_recruitment = fields.Integer(string='Expected New Employees', copy=False,
no_of_recruitment = fields.Integer(string='Target', copy=False,
help='Number of new employees you expect to recruit.', default=1)
no_of_hired_employee = fields.Integer(string='Hired Employees', copy=False,
help='Number of hired employees for this job position during recruitment phase.')
@@ -26,10 +27,6 @@ class Job(models.Model):
requirements = fields.Text('Requirements')
department_id = fields.Many2one('hr.department', string='Department', domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]")
company_id = fields.Many2one('res.company', string='Company', default=lambda self: self.env.company)
state = fields.Selection([
('recruit', 'Recruitment in Progress'),
('open', 'Not Recruiting')
], string='Status', readonly=True, required=True, tracking=True, copy=False, default='recruit', help="Set whether the recruitment process is open or closed for this job position.")
_sql_constraints = [
('name_company_uniq', 'unique(name, company_id, department_id)', 'The name of the job position must be unique per department in company!'),
@@ -56,16 +53,3 @@ class Job(models.Model):
if 'name' not in default:
default['name'] = _("%s (copy)") % (self.name)
return super(Job, self).copy(default=default)
def set_recruit(self):
for record in self:
no_of_recruitment = 1 if record.no_of_recruitment == 0 else record.no_of_recruitment
record.write({'state': 'recruit', 'no_of_recruitment': no_of_recruitment})
return True
def set_open(self):
return self.write({
'state': 'open',
'no_of_recruitment': 0,
'no_of_hired_employee': 0
})
-1
View File
@@ -3,7 +3,6 @@
from . import test_hr_employee
from . import test_channel
from . import test_hr_flow
from . import test_self_user_access
from . import test_multi_company
from . import test_resource
-49
View File
@@ -1,49 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.addons.hr.tests.common import TestHrCommon
class TestHrFlow(TestHrCommon):
@classmethod
def setUpClass(cls):
super(TestHrFlow, cls).setUpClass()
cls.dep_rd = cls.env['hr.department'].create({
'name': 'Research & Development',
})
cls.job_developer = cls.env['hr.job'].create({
'name': 'Experienced Developer',
'department_id': cls.dep_rd.id,
'no_of_recruitment': 5,
})
cls.employee_niv = cls.env['hr.employee'].create({
'name': 'Sharlene Rhodes',
})
cls.job_developer = cls.job_developer.with_user(cls.res_users_hr_officer.id)
cls.employee_niv = cls.employee_niv.with_user(cls.res_users_hr_officer.id)
def test_open2recruit2close_job(self):
""" Opening the job position for "Developer" and checking the job status and recruitment count. """
self.job_developer.set_open()
self.assertEqual(self.job_developer.state, 'open', "Job position of 'Job Developer' is in 'open' state.")
self.assertEqual(self.job_developer.no_of_recruitment, 0,
"Wrong number of recruitment for the job 'Job Developer'(%s found instead of 0)."
% self.job_developer.no_of_recruitment)
""" Recruiting employee "NIV" for the job position "Developer" and checking the job status and recruitment count. """
self.job_developer.set_recruit()
self.assertEqual(self.job_developer.state, 'recruit', "Job position of 'Job Developer' is in 'recruit' state.")
self.assertEqual(self.job_developer.no_of_recruitment, 1,
"Wrong number of recruitment for the job 'Job Developer'(%s found instead of 1.0)."
% self.job_developer.no_of_recruitment)
self.employee_niv.write({'job_id': self.job_developer.id})
""" Closing the recruitment for the job position "Developer" by marking it as open. """
self.job_developer.set_open()
self.assertEqual(self.job_developer.state, 'open', "Job position of 'Job Developer' is in 'open' state.")
self.assertEqual(self.job_developer.no_of_recruitment, 0,
"Wrong number of recruitment for the job 'Job Developer'(%s found instead of 0)."
% self.job_developer.no_of_recruitment)
+15 -20
View File
@@ -7,11 +7,7 @@
<field name="model">hr.job</field>
<field name="arch" type="xml">
<form string="Job">
<header>
<button name="set_recruit" string="Start Recruitment" states="open" type="object" class="oe_highlight" groups="base.group_user"/>
<button name="set_open" string="Stop Recruitment" states="recruit" type="object" groups="base.group_user"/>
<field name="state" widget="statusbar" statusbar_visible="recruit,open"/>
</header>
<field name="active" invisible="1"/>
<sheet>
<div class="oe_button_box" name="button_box"/>
<div class="oe_title">
@@ -19,9 +15,6 @@
<h1><field name="name" placeholder="e.g. Sales Manager"/></h1>
</div>
<notebook>
<page string="Job Description">
<field name="description" options="{'collaborative': true}" attrs="{'invisible': [('state', '!=', 'recruit')]}"/>
</page>
<page string="Recruitment" name="recruitment_page">
<group>
<group name="recruitment">
@@ -29,10 +22,17 @@
<field name="department_id"/>
</group>
<group>
<field name="no_of_recruitment"/>
<label for="no_of_recruitment"/>
<div class="o_row" name="recruitment_target">
<field name="no_of_recruitment" class="col-3 pl-0"/>
<span class="pl-1">new Employees</span>
</div>
</group>
</group>
</page>
<page string="Job Summary" name="job_description_page">
<field name="description" options="{'collaborative': true}"/>
</page>
</notebook>
</sheet>
<div class="oe_chatter">
@@ -50,13 +50,12 @@
<field name="sequence" widget="handle"/>
<field name="name"/>
<field name="department_id"/>
<field name="no_of_employee"/>
<field name="no_of_recruitment"/>
<field name="expected_employees"/>
<field name="no_of_hired_employee"/>
<field name="state"/>
<field name="no_of_employee" optional="hide"/>
<field name="expected_employees" optional="hide"/>
<field name="no_of_hired_employee" optional="hide"/>
<field name="message_needaction" invisible="1"/>
<field name="company_id" groups="base.group_multi_company"/>
<field name="company_id" groups="base.group_multi_company" optional="hide"/>
</tree>
</field>
</record>
@@ -77,9 +76,6 @@
</div>
<div t-if="!selection_mode">
<span>Vacancies : <field name="expected_employees"/></span>
<span t-att-class="record.state.raw_value == 'recruit' and 'float-right badge badge-success' or 'float-right badge badge-danger'">
<field name="state"/>
</span>
</div>
</div>
</t>
@@ -95,13 +91,12 @@
<search string="Jobs">
<field name="name" string="Job Position"/>
<field name="department_id" operator="child_of"/>
<filter name="in_position" string="In Position" domain="[('state', '=', 'open')]"/>
<filter name="in_recruitment" string="In Recruitment" domain="[('state', '=', 'recruit')]"/>
<separator/>
<filter name="message_needaction" string="Unread Messages" domain="[('message_needaction', '=', True)]"/>
<separator/>
<filter name="archived" string="Archived" domain="[('active', '=', False)]"/>
<group expand="0" string="Group By">
<filter string="Department" name="department" domain="[]" context="{'group_by': 'department_id'}"/>
<filter string="Status" name="status" domain="[]" context="{'group_by': 'state'}"/>
<filter string="Company" name="company" domain="[]" context="{'group_by': 'company_id'}" groups="base.group_multi_company"/>
</group>
</search>
+1
View File
@@ -33,6 +33,7 @@
'views/mail_activity_views.xml',
'views/digest_views.xml',
'wizard/applicant_refuse_reason_views.xml',
'wizard/applicant_send_mail_views.xml',
],
'demo': [
'data/hr_recruitment_demo.xml',
@@ -1,42 +1,37 @@
<?xml version="1.0"?>
<odoo>
<data noupdate="1">
<odoo noupdate="1">
<record id="base.user_demo" model="res.users">
<field name="groups_id" eval="[(4, ref('hr_recruitment.group_hr_recruitment_user'))]"/>
</record>
<!--Manage the job_id to get in hr.applicant-->
<record id="hr.job_developer" model="hr.job">
<field name="state">recruit</field>
<field name="no_of_recruitment">4</field>
<field name="no_of_hired_employee">56</field>
<field name="user_id" ref="base.user_admin" />
</record>
<record id="hr.job_ceo" model="hr.job">
<field name="state">open</field>
<field name="no_of_hired_employee">1</field>
</record>
<record id="hr.job_cto" model="hr.job">
<field name="state">open</field>
<field name="no_of_hired_employee">1</field>
<field name="user_id" ref="base.user_admin" />
</record>
<record id="hr.job_consultant" model="hr.job">
<field name="state">recruit</field>
<field name="no_of_recruitment">1</field>
<field name="no_of_hired_employee">17</field>
<field name="user_id" ref="base.user_demo" />
</record>
<record id="hr.job_hrm" model="hr.job">
<field name="no_of_recruitment">1</field>
<field name="state">recruit</field>
<field name="no_of_hired_employee">5</field>
</record>
<record id="hr.job_marketing" model="hr.job">
<field name="state">recruit</field>
<field name="no_of_recruitment">3</field>
<field name="no_of_hired_employee">2</field>
<field name="user_id" ref="base.user_demo" />
</record>
<record id="hr.job_trainee" model="hr.job">
<field name="state">recruit</field>
<field name="no_of_recruitment">6</field>
</record>
@@ -85,8 +80,11 @@
<field name="user_id" ref="base.user_demo"/>
<field name="priority">1</field>
<field name="partner_name">Enrique Jones</field>
<field name="email_from">enrique.jones152@gmail.example.com</field>
<field name="partner_mobile">9963214587</field>
<field name="stage_id" ref="stage_job1"/>
<field name="stage_id" ref="stage_job2"/>
<field name="create_date" eval="DateTime.now() - relativedelta(days=29)"/>
<field name="date_last_stage_update" eval="(DateTime.today() - timedelta(days=27)).strftime('%Y-%m-%d')"/>
</record>
<record id="hr_case_salesman1" model="hr.applicant">
<field name="name">Sales</field>
@@ -97,6 +95,7 @@
<field name="user_id" ref="base.user_demo"/>
<field name="priority">1</field>
<field name="partner_name">Meldona Thang</field>
<field name="email_from">thing.thang.thong@gmail.example.com</field>
<field name="partner_mobile">998655451</field>
<field name="stage_id" ref="stage_job1"/>
</record>
@@ -110,6 +109,7 @@
<field name="user_id" ref="base.user_admin"/>
<field name="priority">3</field>
<field name="partner_name">Johan Duck</field>
<field name="email_from">coincoin@gmail.example.com</field>
<field name="partner_mobile">8955545</field>
<field name="stage_id" ref="stage_job1"/>
</record>
@@ -122,6 +122,7 @@
<field name="user_id" ref="base.user_admin"/>
<field name="priority">0</field>
<field name="partner_name">Kelly Wallant</field>
<field name="email_from">kelly@wallant.example.com</field>
<field name="partner_mobile">879895515</field>
<field name="stage_id" ref="stage_job1"/>
</record>
@@ -135,6 +136,7 @@
<field name="user_id" ref="base.user_admin"/>
<field name="priority">0</field>
<field name="partner_name">Cécile Donth</field>
<field name="email_from">c-cile72@msn.example.com</field>
<field name="partner_mobile">98765411</field>
<field name="stage_id" ref="stage_job1"/>
</record>
@@ -147,6 +149,7 @@
<field name="user_id" ref="base.user_admin"/>
<field name="priority">0</field>
<field name="partner_name">Ohen Rizome</field>
<field name="email_from">0h3n-rijaune@yahoo.example.com</field>
<field name="partner_mobile">654687987654</field>
<field name="stage_id" ref="stage_job1"/>
</record>
@@ -159,9 +162,12 @@
<field name="user_id" ref="base.user_demo"/>
<field name="priority">2</field>
<field name="partner_name">Marie Justine</field>
<field name="email_from">justinemarie@outlook.example.com</field>
<field name="partner_mobile">9988774455</field>
<field name="stage_id" ref="stage_job4"/>
<field name="partner_phone">6633225</field>
<field name="create_date" eval="DateTime.now() - relativedelta(days=17)"/>
<field name="date_last_stage_update" eval="(DateTime.today() - timedelta(days=7)).strftime('%Y-%m-%d')"/>
</record>
<record id="hr_case_fresher0" model="hr.applicant">
<field name="name">Fresher</field>
@@ -172,6 +178,7 @@
<field name="user_id" ref="base.user_demo"/>
<field name="priority">0</field>
<field name="partner_name">Jose</field>
<field name="email_from">the.jose@gmail.example.com</field>
<field name="stage_id" ref="stage_job3"/>
<field name="partner_phone">999666735</field>
</record>
@@ -183,6 +190,7 @@
<field name="categ_ids" eval="[(6,0,[ref('tag_applicant_manager')])]"/>
<field name="user_id" ref="base.user_admin"/>
<field name="partner_name">Yin Lee</field>
<field name="email_from">yin.lee@wechat.example.com</field>
<field name="stage_id" ref="stage_job1"/>
</record>
<record id="hr_case_mkt1" model="hr.applicant">
@@ -193,6 +201,7 @@
<field name="categ_ids" eval="[(6,0,[ref('tag_applicant_manager')])]"/>
<field name="user_id" ref="base.user_admin"/>
<field name="partner_name">Hubert Blank</field>
<field name="email_from">st-hubertus@gmail.example.com</field>
<field name="priority">3</field>
<field name="stage_id" ref="stage_job3"/>
</record>
@@ -205,7 +214,10 @@
<field name="categ_ids" eval="[(6,0,[ref('tag_applicant_manager')])]"/>
<field name="user_id" ref="base.user_admin"/>
<field name="partner_name">John Bruno</field>
<field name="email_from">johnnyboy@gmail.example.com</field>
<field name="stage_id" ref="stage_job5"/>
<field name="create_date" eval="DateTime.now() - relativedelta(days=61)"/>
<field name="date_last_stage_update" eval="(DateTime.today() - timedelta(days=37)).strftime('%Y-%m-%d')"/>
</record>
<record id="hr_case_marketingjob0" model="hr.applicant">
<field name="name">More than 5 yrs Experience in PHP</field>
@@ -216,7 +228,10 @@
<field name="categ_ids" eval="[(6,0,[ref('tag_applicant_reserve')])]"/>
<field name="user_id" ref="base.user_demo"/>
<field name="partner_name">Sandra Elvis</field>
<field name="email_from">sandra.elvis.the.king25@gmail.example.com</field>
<field name="stage_id" ref="stage_job5"/>
<field name="create_date" eval="DateTime.now() - relativedelta(days=34)"/>
<field name="date_last_stage_update" eval="(DateTime.today() - timedelta(days=7)).strftime('%Y-%m-%d')"/>
</record>
<record id="hr_case_financejob0" model="hr.applicant">
<field name="name">Finance Manager</field>
@@ -227,6 +242,7 @@
<field name="user_id" ref="base.user_admin"/>
<field name="priority">1</field>
<field name="partner_name">David Armstrong</field>
<field name="email_from">david.strongarm@gmail.example.com</field>
<field name="stage_id" ref="stage_job2"/>
<field name="partner_phone">33968745</field>
</record>
@@ -239,7 +255,10 @@
<field name="user_id" ref="base.user_admin"/>
<field name="priority">1</field>
<field name="partner_name">Joren Jacob</field>
<field name="email_from">joren.jacob@outlook.example.com</field>
<field name="stage_id" ref="stage_job2"/>
<field name="create_date" eval="DateTime.now() - relativedelta(days=7)"/>
<field name="date_last_stage_update" eval="(DateTime.today() - timedelta(days=3)).strftime('%Y-%m-%d')"/>
</record>
<record id="hr_case_traineemca1" model="hr.applicant">
<field name="name">Trainee - MCA</field>
@@ -248,10 +267,13 @@
<field name="type_id" ref="degree_licenced"/>
<field name="categ_ids" eval="[(6,0,[ref('tag_applicant_sales')])]"/>
<field name="partner_name">Tina Augustie</field>
<field name="email_from">tina.turner@gmail.example.com</field>
<field name="partner_mobile">9898745745</field>
<field name="stage_id" ref="stage_job4"/>
<field name="partner_phone">6630125</field>
<field name="user_id" ref="base.user_admin"/>
<field name="create_date" eval="DateTime.now() - relativedelta(days=67)"/>
<field name="date_last_stage_update" eval="(DateTime.today() - timedelta(days=45)).strftime('%Y-%m-%d')"/>
</record>
<record id="hr_case_programmer" model="hr.applicant">
<field name="name">Programmer</field>
@@ -261,10 +283,13 @@
<field name="categ_ids" eval="[(6,0,[ref('tag_applicant_it')])]"/>
<field name="user_id" ref="base.user_admin"/>
<field name="partner_name">Shane Williams</field>
<field name="email_from">the.real.shane@gmail.example.com</field>
<field name="partner_mobile">9812398524</field>
<field name="stage_id" ref="stage_job4"/>
<field name="partner_phone">6630125</field>
<field name="salary_expected">11000.0</field>
<field name="create_date" eval="DateTime.now() - relativedelta(days=13)"/>
<field name="date_last_stage_update" eval="(DateTime.today() - timedelta(days=4)).strftime('%Y-%m-%d')"/>
</record>
<record id="hr_case_advertisement" model="hr.applicant">
<field name="name">Advertisement</field>
@@ -274,9 +299,12 @@
<field name="categ_ids" eval="[(6,0,[ref('tag_applicant_it')])]"/>
<field name="user_id" ref="base.user_admin"/>
<field name="partner_name">David Billy</field>
<field name="email_from">billy.boy12@gmail.example.com</field>
<field name="partner_mobile">9988774455</field>
<field name="stage_id" ref="stage_job2"/>
<field name="salary_expected">11000.0</field>
<field name="create_date" eval="DateTime.now() - relativedelta(days=4)"/>
<field name="date_last_stage_update" eval="(DateTime.today() - timedelta(days=2)).strftime('%Y-%m-%d')"/>
</record>
<record id="hr_case_salesman0_cv" model="ir.attachment">
@@ -309,6 +337,7 @@
<field name="subject">Regarding reference</field>
<field name="model">hr.applicant</field>
<field name="res_id" ref="hr_case_advertisement"/>
<field name="date" eval="DateTime.now() - relativedelta(days=3)"/>
<field name="body" type="xml">
<p>Hello!<br />
I will surely refer to this application as it is by your reference and <br />
@@ -460,6 +489,4 @@
<field name="create_uid" ref="base.user_admin"/>
<field name="user_id" ref="base.user_admin"/>
</record>
</data>
</odoo>
+100 -10
View File
@@ -1,6 +1,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import ast
from collections import defaultdict
from odoo import api, fields, models, _
@@ -8,7 +9,7 @@ from odoo import api, fields, models, _
class Job(models.Model):
_name = "hr.job"
_inherit = ["mail.alias.mixin", "hr.job"]
_order = "sequence, state desc, name asc"
_order = "sequence, name asc"
@api.model
def _default_address_id(self):
@@ -29,10 +30,11 @@ class Job(models.Model):
help="Number of applications that are new in the flow (typically at first step of the flow)")
old_application_count = fields.Integer(
compute='_compute_old_application_count', string="Old Application")
applicant_hired = fields.Integer(compute='_compute_applicant_hired', string="Applicants Hired")
manager_id = fields.Many2one(
'hr.employee', related='department_id.manager_id', string="Department Manager",
readonly=True, store=True)
user_id = fields.Many2one('res.users', "Recruiter", tracking=True)
user_id = fields.Many2one('res.users', "Recruiter", domain="[('share', '=', False), ('company_ids', 'in', company_id)]", tracking=True)
hr_responsible_id = fields.Many2one(
'res.users', "HR Responsible", tracking=True,
help="Person responsible of validating the employee's contracts.")
@@ -47,6 +49,39 @@ class Job(models.Model):
interviewer_ids = fields.Many2many('res.users', string='Interviewers', domain="[('share', '=', False), ('company_ids', 'in', company_id)]")
extended_interviewer_ids = fields.Many2many('res.users', 'hr_job_extended_interviewer_res_users', compute='_compute_extended_interviewer_ids', store=True)
activities_overdue = fields.Integer(compute='_compute_activities')
activities_today = fields.Integer(compute='_compute_activities')
@api.depends_context('uid')
def _compute_activities(self):
self.env.cr.execute("""
SELECT
app.job_id,
COUNT(*) AS act_count,
CASE
WHEN %(today)s::date - act.date_deadline::date = 0 THEN 'today'
WHEN %(today)s::date - act.date_deadline::date > 0 THEN 'overdue'
END AS act_state
FROM mail_activity act
JOIN hr_applicant app ON app.id = act.res_id
JOIN hr_recruitment_stage sta ON app.stage_id = sta.id
WHERE act.user_id = %(user_id)s AND act.res_model = 'hr.applicant'
AND act.date_deadline <= %(today)s::date AND app.active
AND app.job_id IN %(job_ids)s
AND sta.hired_stage IS NOT TRUE
GROUP BY app.job_id, act_state
""", {
'today': fields.Date.context_today(self),
'user_id': self.env.uid,
'job_ids': tuple(self.ids),
})
job_activities = defaultdict(dict)
for activity in self.env.cr.dictfetchall():
job_activities[activity['job_id']][activity['act_state']] = activity['act_count']
for job in self:
job.activities_overdue = job_activities[job.id].get('overdue', 0)
job.activities_today = job_activities[job.id].get('today', 0)
@api.depends('application_ids.interviewer_id')
def _compute_extended_interviewer_ids(self):
for job in self:
@@ -85,7 +120,13 @@ class Job(models.Model):
job.documents_count = len(job.document_ids)
def _compute_all_application_count(self):
read_group_result = self.env['hr.applicant'].with_context(active_test=False)._read_group([('job_id', 'in', self.ids)], ['job_id'], ['job_id'])
read_group_result = self.env['hr.applicant'].with_context(active_test=False)._read_group([
('job_id', 'in', self.ids),
'|',
('active', '=', True),
'&',
('active', '=', False), ('refuse_reason_id', '!=', False),
], ['job_id'], ['job_id'])
result = dict((data['job_id'][0], data['job_id_count']) for data in read_group_result)
for job in self:
job.all_application_count = result.get(job.id, 0)
@@ -109,6 +150,16 @@ class Job(models.Model):
[("job_id", "=", job.id), ("stage_id", "=", job._get_first_stage().id)]
)
def _compute_applicant_hired(self):
hired_stages = self.env['hr.recruitment.stage'].search([('hired_stage', '=', True)])
hired_data = self.env['hr.applicant']._read_group([
('job_id', 'in', self.ids),
('stage_id', 'in', hired_stages.ids),
], ['job_id'], ['job_id'])
job_hires = {data['job_id'][0]: data['job_id_count'] for data in hired_data}
for job in self:
job.applicant_hired = job_hires.get(job.id, 0)
@api.depends('application_count', 'new_application_count')
def _compute_old_application_count(self):
for job in self:
@@ -144,6 +195,8 @@ class Job(models.Model):
def write(self, vals):
old_interviewers = self.interviewer_ids
if 'active' in vals and not vals['active']:
self.application_ids.active = False
res = super().write(vals)
if 'interviewer_ids' in vals:
interviewers_to_clean = old_interviewers - self.interviewer_ids
@@ -163,14 +216,51 @@ class Job(models.Model):
def _creation_subtype(self):
return self.env.ref('hr_recruitment.mt_job_new')
def action_get_attachment_tree_view(self):
action = self.env["ir.actions.actions"]._for_xml_id("base.action_attachment")
action['context'] = {
'default_res_model': self._name,
'default_res_id': self.ids[0]
def action_open_attachments(self):
return {
'type': 'ir.actions.act_window',
'res_model': 'ir.attachment',
'name': _('Documents'),
'context': {
'default_res_model': self._name,
'default_res_id': self.ids[0],
'show_partner_name': 1,
},
'view_mode': 'tree,form',
'views': [
(self.env.ref('hr_recruitment.ir_attachment_hr_recruitment_list_view').id, 'tree'),
(False, 'form'),
],
'search_view_id': self.env.ref('hr_recruitment.ir_attachment_view_search_inherit_hr_recruitment').ids,
'domain': ['|',
'&', ('res_model', '=', 'hr.job'), ('res_id', 'in', self.ids),
'&', ('res_model', '=', 'hr.applicant'), ('res_id', 'in', self.application_ids.ids),
],
}
def action_open_activities(self):
action = self.env["ir.actions.actions"]._for_xml_id("hr_recruitment.action_hr_job_applications")
views = ['activity'] + [view for view in action['view_mode'].split(',') if view != 'activity']
action['view_mode'] = ','.join(views)
action['views'] = [(False, view) for view in views]
return action
def action_open_late_activities(self):
action = self.action_open_activities()
action['context'] = {
'default_job_id': self.id,
'search_default_job_id': self.id,
'search_default_activities_overdue': True,
}
return action
def action_open_today_activities(self):
action = self.action_open_activities()
action['context'] = {
'default_job_id': self.id,
'search_default_job_id': self.id,
'search_default_activities_today': True,
}
action['search_view_id'] = (self.env.ref('hr_recruitment.ir_attachment_view_search_inherit_hr_recruitment').id, )
action['domain'] = ['|', '&', ('res_model', '=', 'hr.job'), ('res_id', 'in', self.ids), '&', ('res_model', '=', 'hr.applicant'), ('res_id', 'in', self.mapped('application_ids').ids)]
return action
def close_dialog(self):
+73 -10
View File
@@ -10,6 +10,8 @@ from odoo.exceptions import AccessError, UserError
from dateutil.relativedelta import relativedelta
from lxml import etree
AVAILABLE_PRIORITIES = [
('0', 'Normal'),
('1', 'Good'),
@@ -24,9 +26,13 @@ class RecruitmentSource(models.Model):
_inherit = ['utm.source.mixin']
email = fields.Char(related='alias_id.display_name', string="Email", readonly=True)
has_domain = fields.Char(compute='_compute_has_domain')
job_id = fields.Many2one('hr.job', "Job", ondelete='cascade')
alias_id = fields.Many2one('mail.alias', "Alias ID")
def _compute_has_domain(self):
self.has_domain = bool(self.env["ir.config_parameter"].sudo().get_param("mail.catchall.domain"))
def create_alias(self):
campaign = self.env.ref('hr_recruitment.utm_campaign_job')
medium = self.env.ref('utm.utm_medium_email')
@@ -44,7 +50,16 @@ class RecruitmentSource(models.Model):
},
}
source.alias_id = self.env['mail.alias'].create(vals)
source.name = source.source_id.name
@api.model
def fields_view_get(self, view_id=None, view_type='form', toolbar=False, submenu=False):
res = super().fields_view_get(view_id, view_type, toolbar, submenu)
if view_type == 'tree' and not bool(self.env["ir.config_parameter"].sudo().get_param("mail.catchall.domain")):
arch = etree.fromstring(res['arch'])
email = arch.xpath("//field[@name='email']")[0]
email.getparent().remove(email)
res['arch'] = etree.tostring(arch, encoding='unicode')
return res
class RecruitmentStage(models.Model):
_name = "hr.recruitment.stage"
@@ -111,7 +126,7 @@ class Applicant(models.Model):
_inherit = ['mail.thread.cc', 'mail.activity.mixin', 'utm.mixin']
_mailing_enabled = True
name = fields.Char("Subject / Application Name", required=True, help="Email subject for applications sent via email", index='trigram')
name = fields.Char("Subject / Application", required=True, help="Email subject for applications sent via email", index='trigram')
active = fields.Boolean("Active", default=True, help="If the active field is set to false, it will allow you to hide the case without removing it.")
description = fields.Html("Description")
email_from = fields.Char("Email", size=128, help="Applicant email", compute='_compute_partner_phone_email',
@@ -129,7 +144,7 @@ class Applicant(models.Model):
categ_ids = fields.Many2many('hr.applicant.category', string="Tags")
company_id = fields.Many2one('res.company', "Company", compute='_compute_company', store=True, readonly=False, tracking=True)
user_id = fields.Many2one(
'res.users', "Recruiter", compute='_compute_user',
'res.users', "Recruiter", compute='_compute_user', domain="[('share', '=', False), ('company_ids', 'in', company_id)]",
tracking=True, store=True, readonly=False)
date_closed = fields.Datetime("Hire Date", compute='_compute_date_closed', store=True, readonly=False, tracking=True)
date_open = fields.Datetime("Assigned", readonly=True)
@@ -178,6 +193,12 @@ class Applicant(models.Model):
source_id = fields.Many2one(ondelete='set null')
interviewer_id = fields.Many2one('res.users', string='Interviewer', domain="[('share', '=', False), ('company_ids', 'in', company_id)]")
@api.onchange('job_id')
def _onchange_job_id(self):
for applicant in self:
if applicant.job_id.name:
applicant.name = applicant.job_id.name
@api.depends('date_open', 'date_closed')
def _compute_day(self):
for applicant in self:
@@ -312,7 +333,7 @@ class Applicant(models.Model):
for applicant in self:
applicant.user_id = applicant.job_id.user_id.id or self.env.uid
@api.depends('partner_id')
@api.depends('partner_id', 'partner_id.email', 'partner_id.mobile', 'partner_id.phone')
def _compute_partner_phone_email(self):
for applicant in self:
applicant.partner_phone = applicant.partner_id.phone
@@ -448,12 +469,24 @@ class Applicant(models.Model):
}
return res
def action_get_attachment_tree_view(self):
action = self.env['ir.actions.act_window']._for_xml_id('base.action_attachment')
action['context'] = {'default_res_model': self._name, 'default_res_id': self.ids[0]}
action['domain'] = str(['&', ('res_model', '=', self._name), ('res_id', 'in', self.ids)])
action['search_view_id'] = (self.env.ref('hr_recruitment.ir_attachment_view_search_inherit_hr_recruitment').id, )
return action
def action_open_attachments(self):
return {
'type': 'ir.actions.act_window',
'res_model': 'ir.attachment',
'name': _('Documents'),
'context': {
'default_res_model': 'hr.job',
'default_res_id': self.ids[0],
'show_partner_name': 1,
},
'view_mode': 'tree,form',
'views': [
(self.env.ref('hr_recruitment.ir_attachment_hr_recruitment_list_view').id, 'tree'),
(False, 'form'),
],
'search_view_id': self.env.ref('hr_recruitment.ir_attachment_view_search_inherit_hr_recruitment').ids,
'domain': [('res_model', '=', 'hr.applicant'), ('res_id', 'in', self.ids), ],
}
def action_applications_email(self):
# Security rules will apply when loading the view, here we just fetch the ids
@@ -479,6 +512,16 @@ class Applicant(models.Model):
},
}
def action_open_employee(self):
self.ensure_one()
return {
'name': _('Employee'),
'type': 'ir.actions.act_window',
'res_model': 'hr.employee',
'view_mode': 'form',
'res_id': self.emp_id.id,
}
def _track_template(self, changes):
res = super(Applicant, self)._track_template(changes)
applicant = self[0]
@@ -520,6 +563,14 @@ class Applicant(models.Model):
applicant._message_add_suggested_recipient(recipients, email=email_from, reason=_('Contact Email'))
return recipients
def name_get(self):
if self.env.context.get('show_partner_name'):
return [
(applicant.id, applicant.partner_name or applicant.name)
for applicant in self
]
return super().name_get()
@api.model
def message_new(self, msg, custom_values=None):
""" Overrides mail_thread message_new that is called by the mailgateway
@@ -646,6 +697,18 @@ class Applicant(models.Model):
return applicant_inactive.archive_applicant()
return res
def action_send_email(self):
return {
'name': _('Send Email'),
'type': 'ir.actions.act_window',
'target': 'new',
'view_mode': 'form',
'res_model': 'applicant.send.mail',
'context': {
'default_applicant_ids': self.ids,
}
}
class ApplicantCategory(models.Model):
_name = "hr.applicant.category"
@@ -17,3 +17,5 @@ access_hr_applicant_category_manager,hr.applicant_category,model_hr_applicant_ca
access_calendar_event_type_hr_officer,calendar.event.type.officer,calendar.model_calendar_event_type,group_hr_recruitment_user,1,1,1,0
access_applicant_get_refuse_reason,access.applicant.get.refuse.reason,model_applicant_get_refuse_reason,hr_recruitment.group_hr_recruitment_user,1,1,1,0
access_applicant_get_refuse_reason_interviewer,access.applicant.get.refuse.reason.interviewer,model_applicant_get_refuse_reason,hr_recruitment.group_hr_recruitment_interviewer,1,1,1,0
access_applicant_send_mail,access.applicant.send.mail,model_applicant_send_mail,hr_recruitment.group_hr_recruitment_user,1,1,1,0
access_applicant_send_mail_interviewer,access.applicant.send.mail.interviewer,model_applicant_send_mail,hr_recruitment.group_hr_recruitment_interviewer,1,1,1,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
17 access_calendar_event_type_hr_officer calendar.event.type.officer calendar.model_calendar_event_type group_hr_recruitment_user 1 1 1 0
18 access_applicant_get_refuse_reason access.applicant.get.refuse.reason model_applicant_get_refuse_reason hr_recruitment.group_hr_recruitment_user 1 1 1 0
19 access_applicant_get_refuse_reason_interviewer access.applicant.get.refuse.reason.interviewer model_applicant_get_refuse_reason hr_recruitment.group_hr_recruitment_interviewer 1 1 1 0
20 access_applicant_send_mail access.applicant.send.mail model_applicant_send_mail hr_recruitment.group_hr_recruitment_user 1 1 1 0
21 access_applicant_send_mail_interviewer access.applicant.send.mail.interviewer model_applicant_send_mail hr_recruitment.group_hr_recruitment_interviewer 1 1 1 0
@@ -1,18 +1,56 @@
odoo.define('job.update_kanban', function (require) {
'use strict';
var KanbanRecord = require('web.KanbanRecord');
/** @odoo-module **/
import KanbanRecord from 'web.KanbanRecord';
KanbanRecord.include({
/**
* @override
* @private
*/
_openRecord: function () {
if (this.modelName === 'hr.job' && this.$(".o_hr_job_boxes a").length) {
this.$(".o_hr_job_boxes a").first().click();
} else {
this._super.apply(this, arguments);
}
}
});
import fieldRegistry from 'web.field_registry';
import { FieldChar, StateSelectionWidget } from 'web.basic_fields';
const RecruitmentStateSelectionWidget = StateSelectionWidget.extend({
init() {
this._super(...arguments);
this.mode = 'edit';
},
});
const ApplicantChar = FieldChar.extend({
events: _.extend({}, FieldChar.prototype.events, {
'click': '_onClick',
}),
_onClick(ev) {
if (this.recordData['res_id'] !== undefined && this.recordData['res_model'] === 'hr.applicant') {
ev.stopPropagation();
this.do_action({
type: 'ir.actions.act_window',
res_model: 'hr.applicant',
res_id: this.recordData['res_id'],
views: [[false, "form"]],
view_mode: "form",
target: "current",
});
}
},
_renderReadonly() {
this._super(...arguments);
this.$el.addClass('o_hr_applicant_widget');
}
});
fieldRegistry
.add('recruitment_state_selection', RecruitmentStateSelectionWidget)
.add('applicant_char', ApplicantChar);
KanbanRecord.include({
/**
* @override
* @private
*/
_openRecord: function () {
if (this.modelName === 'hr.job' && this.$(".o_hr_job_boxes a").length) {
this.$(".o_hr_job_boxes a").first().click();
} else {
this._super.apply(this, arguments);
}
}
});
@@ -5,3 +5,19 @@
display: none;
}
}
.o_applicant_form {
&.o_form_readonly {
.o_field_empty:empty {
min-height: unset;
}
}
}
.o_hr_applicant_widget {
color: $link-color;
&:hover {
color: $link-hover-color;
}
}
@@ -1,12 +1,9 @@
.o_kanban_view.o_kanban_dashboard {
&.o_hr_recruitment_kanban {
&.o_kanban_ungrouped .o_kanban_record {
width: 350px;
width: 450px;
&:not(.o_kanban_ghost) {
height: 160px;
@include media-breakpoint-down(sm) {
height: 150px;
}
min-height: 220px;
}
}
@@ -35,10 +32,30 @@
.text_top_padding{
padding-top: 0.375rem;
}
}
.o_kanban_record_subtitle {
height: 1em;
.o_primary {
display: flex !important;
align-items: center;
justify-content: space-between;
padding-right: 24px !important;
.fa {
margin-left: 0;
}
}
.o_job_activities {
list-style-type: none;
.to-recruit {
color: $o-enterprise-color;
font-weight: bold;
}
}
.o_kanban_card_header_title {
min-height: 3rem;
}
}
.o_recruitment_kanban_boxes {
@@ -69,6 +86,9 @@
.o_value {
font-weight: 800;
}
&.bottom_block {
background-color: unset !important;
}
> a {
font-weight: 500;
@@ -95,6 +115,12 @@
.o_kanban_view {
&.o_kanban_applicant {
.o_sample_data_disabled {
.ribbon {
display: none;
}
}
.ribbon {
&::before, &::after {
display: none;
@@ -42,5 +42,5 @@
</record>
<menuitem id="menu_hr_department" name="Departments"
parent="menu_hr_recruitment_configuration" action="action_hr_department"/>
parent="menu_hr_recruitment_config_employees" action="action_hr_department"/>
</odoo>
+92 -48
View File
@@ -11,7 +11,8 @@
<field name="name">hr.job.kanban</field>
<field name="model">hr.job</field>
<field name="arch" type="xml">
<kanban class="oe_background_grey o_kanban_dashboard o_hr_recruitment_kanban" on_create="hr_recruitment.create_job_simple" sample="1">
<kanban class="oe_background_grey o_kanban_dashboard o_hr_recruitment_kanban" on_create="hr_recruitment.create_job_simple" sample="1" limit="40">
<field name="active"/>
<field name="name"/>
<field name="alias_name"/>
<field name="alias_domain"/>
@@ -22,7 +23,6 @@
<field name="new_application_count"/>
<field name="no_of_hired_employee"/>
<field name="manager_id"/>
<field name="state"/>
<field name="user_id"/>
<field name="application_count"/>
<templates>
@@ -30,15 +30,18 @@
<div t-attf-class="#{!selection_mode ? kanban_color(record.color.raw_value) : ''}">
<div class="o_kanban_card_header">
<div class="o_kanban_card_header_title">
<field name="is_favorite" widget="boolean_favorite" nolabel="1"/>
<div class="o_primary col-12">
<span><t t-esc="record.name.value"/></span>
<a name="%(action_hr_job_applications)d" type="action"><t t-esc="record.name.value"/></a>
</div>
<div class="col-12 text-muted">
<field name="user_id" />
</div>
<field name="is_favorite" widget="boolean_favorite" nolabel="1"/>
<div t-if="record.alias_name.value and record.alias_domain.value and record.state.raw_value == 'recruit'" class="o_secondary o_job_alias">
<small> <i class="fa fa-envelope-o" role="img" aria-label="Alias" title="Alias"></i> <field name="alias_id"/> </small>
<div class="col-12 o_secondary" groups="base.group_multi_company">
<small><i class="fa fa-building-o" role="img" aria-label="Company" title="Company"></i> <field name="company_id"/></small>
</div>
<div t-if="record.alias_name.value and record.alias_domain.value" class="col-12 o_secondary o_job_alias">
<small><i class="fa fa-envelope-o" role="img" aria-label="Alias" title="Alias"></i> <field name="alias_id"/></small>
</div>
</div>
<div class="o_kanban_manage_button_section">
@@ -46,51 +49,73 @@
</div>
</div>
<div class="container o_kanban_card_content mt-0 mt-sm-3">
<t t-if="record.state.raw_value == 'recruit'">
<div class="row">
<div class="col-7">
<button class="btn btn-primary" name="%(action_hr_job_applications)d" type="action">
<field name="new_application_count"/> New Applications
</button>
</div>
<div class="col-5">
<field name="all_application_count"/> Applications<br/>
<field name="no_of_recruitment"/> To Recruit
</div>
<div class="row">
<div class="col-7">
<button class="btn btn-primary" name="%(action_hr_job_applications)d" type="action">
<field name="new_application_count"/> New Applications
</button>
</div>
</t>
<t t-if="record.state.raw_value == 'open'">
<div class="row">
<div class="col-7 o_kanban_primary_left">
<button class="btn btn-secondary" name="set_recruit" type="object">Start Recruitment</button>
</div>
<t t-if="record.old_application_count.raw_value != 0">
<div class="col-5 text_top_padding">
<field name="old_application_count"/> Applications<br/>
</div>
</t>
</div>
</t>
<div name="kanban_boxes" class="row o_recruitment_kanban_boxes" groups="hr_recruitment.group_hr_recruitment_user">
<div class="o_recruitment_kanban_box o_kanban_primary_bottom bottom_block" style="padding-left:8px;">
<div class="col-6"></div>
<div class="col-6 o_link_trackers">
<a role="button" name="%(hr_recruitment.action_hr_job_sources)d" type="action" class="btn btn-sm py-0">
<span title="Link Trackers"><i class="fa fa-lg fa-envelope" role="img" aria-label="Link Trackers"/></span>
<ul class="col-5 o_job_activities">
<li>
<a name="edit_job" type="edit" t-attf-class="{{ record.no_of_recruitment.raw_value > 0 ? 'to-recruit' : 'text-secondary' }}">
<field name="no_of_recruitment"/> To Recruit
</a>
</li>
<li t-if="record.application_count.raw_value > 0">
<field name="application_count"/> Applications
</li>
<li class="text-warning" t-if="record.activities_today.raw_value > 0">
<a name="action_open_today_activities" type="object" class="text-warning"><field name="activities_today"/> Activities Today</a>
</li>
<li t-if="record.activities_overdue.raw_value > 0">
<a name="action_open_late_activities" type="object" class="text-danger"><field name="activities_overdue"/> Late Activities</a>
</li>
</ul>
</div>
<div name="kanban_boxes" class="row o_recruitment_kanban_boxes" groups="hr_recruitment.group_hr_recruitment_user"></div>
</div>
<div class="container o_kanban_card_manage_pane dropdown-menu" role="menu">
<div class="row">
<div class="col-6 o_kanban_card_manage_section">
<div role="menuitem" class="o_kanban_card_manage_title">
<span>View</span>
</div>
<div role="menuitem" name="menu_view_applications">
<a name="%(action_hr_job_applications)d" type="action">Applications</a>
</div>
<div role="menuitem">
<a name="action_open_activities" type="object">Activities</a>
</div>
<div role="menuitem">
<a name="%(action_hr_job_sources)d" type="action" context="{'default_job_id': active_id}">Trackers</a>
</div>
</div>
<div class="col-6 o_kanban_card_manage_section">
<div role="menuitem" class="o_kanban_card_manage_title">
<span>New</span>
</div>
<div role="menuitem" name="menu_new_applications">
<a name="%(hr_recruitment.action_hr_applicant_new)d" type="action">Application</a>
</div>
</div>
<div class="col-6 o_kanban_card_manage_section">
<div role="menuitem" class="o_kanban_card_manage_title">
<span>Reporting</span>
</div>
<div role="menuitem" name="o_kanban_job_reporting">
<a name="%(hr_recruitment.action_hr_recruitment_report_filtered_job)d" type="action">Analysis</a>
</div>
</div>
</div>
</div>
<div class="o_kanban_card_manage_pane dropdown-menu" role="menu">
<div class="o_kanban_card_manage_section">
<div role="menuitem"><a t-if="record.state.raw_value == 'recruit'" name="set_open" type="object">Recruitment Done</a></div>
<div role="menuitem"><a t-if="record.state.raw_value == 'open'" name="set_recruit" type="object">Start recruitment</a></div>
<div role="menuitem"><a t-if="widget.editable" name="edit_job" type="edit">Edit</a></div>
<div role="menuitem" name="applications_action"><a t-if="record.state.raw_value == 'open' and record.application_count.raw_value != 0" name="%(action_hr_job_applications)d" type="action">Applications</a></div>
</div>
<div role="menuitem" aria-haspopup="true">
<ul class="oe_kanban_colorpicker" data-field="color" role="menu"/>
<div class="o_kanban_card_manage_settings row">
<div class="col-6" role="menuitem" aria-haspopup="true">
<ul class="oe_kanban_colorpicker" data-field="color" role="popup"/>
</div>
<div class="col-6" role="menuitem">
<a class="dropdown-item" t-if="widget.editable" name="edit_job" type="edit">Configuration</a>
<a class="dropdown-item" t-if="record.active" name="toggle_active" type="object">Archive</a>
<a class="dropdown-item" t-if="!record.active" name="toggle_active" type="object">Unarchive</a>
</div>
</div>
</div>
<div class="o_hr_job_boxes">
@@ -108,18 +133,37 @@
<field name="model">hr.job</field>
<field name="inherit_id" ref="hr.view_job_filter" />
<field name="arch" type="xml">
<xpath expr="//field[@name='department_id']" position="after">
<xpath expr="//filter[@name='my_favorite_jobs']" position="after">
<filter string="My Job Positions" name="my_positions" domain="[('user_id', '=', uid)]"/>
</xpath>
</field>
</record>
<record id="hr_job_view_tree_inherit" model="ir.ui.view">
<field name="model">hr.job</field>
<field name="inherit_id" ref="hr.view_hr_job_tree"/>
<field name="arch" type="xml">
<field name="department_id" position="after">
<field name="application_count" string="Applications"/>
</field>
<field name="no_of_recruitment" position="after">
<field name="alias_name" invisible="1"/>
<field name="alias_id" attrs="{'invisible': [('alias_name', '=', False)]}" optional="hide"/>
<field name="user_id" widget="many2one_avatar_user" optional="hide"/>
</field>
</field>
</record>
<!-- hr related job position menu action -->
<record model="ir.actions.act_window" id="action_hr_job">
<field name="name">Job Positions</field>
<field name="res_model">hr.job</field>
<field name="view_mode">kanban,form</field>
<field name="view_mode">kanban,tree,form</field>
<field name="view_id" ref="hr_recruitment.view_hr_job_kanban"/>
<field name="view_ids" eval="[(5, 0, 0),
(0, 0, {'view_mode': 'kanban', 'view_id': ref('hr_recruitment.view_hr_job_kanban')}),
(0, 0, {'view_mode': 'tree', 'view_id': ref('hr_recruitment.hr_job_view_tree_inherit')}),
(0, 0, {'view_mode': 'form', 'view_id': ref('hr.view_hr_job_form')})]"/>
<field name="context">{}</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
@@ -29,13 +29,15 @@
<field name="last_stage_id" invisible="1"/>
<field name="date_last_stage_update" invisible="1"/>
<field name="partner_name" readonly="1"/>
<field name="type_id" invisible="1"/>
<field name="job_id"/>
<field name="name" readonly="1"/>
<field name="stage_id"/>
<field name="priority" widget="priority" optional="show"/>
<field name="partner_mobile" widget="phone" readonly="1" optional="show"/>
<field name="partner_mobile" widget="phone" readonly="1" optional="show" class="text-right"/>
<field name="categ_ids" widget="many2many_tags" options="{'color_field': 'color'}" optional="show"/>
<field name="user_id" widget="many2one_avatar_user" optional="show"/>
<field name="interviewer_id" widget="many2one_avatar_user" optional="hide"/>
<field name="create_date" readonly="1" widget="date" optional="show"/>
<field name="partner_phone" widget="phone" readonly="1" optional="hide"/>
<field name="email_from" readonly="1" optional="hide"/>
@@ -43,7 +45,6 @@
<field name="source_id" readonly="1" optional="hide"/>
<field name="salary_expected" optional="hide"/>
<field name="salary_proposed" optional="hide"/>
<field name="type_id" invisible="1"/>
<field name="availability" optional="hide"/>
<field name="department_id" invisible="context.get('invisible_department', True)" readonly="1"/>
<field name="company_id" groups="base.group_multi_company" readonly="1" optional="hide"/>
@@ -73,15 +74,28 @@
<field name="arch" type="xml">
<form string="Jobs - Recruitment Form" class="o_applicant_form">
<field name="emp_id" invisible="1"/>
<field name="meeting_ids" invisible="1"/>
<field name="refuse_reason_id" invisible="1"/>
<header>
<button string="Create Employee" name="create_employee_from_applicant" type="object" data-hotkey="v" groups="hr_recruitment.group_hr_recruitment_user"
class="oe_highlight o_create_employee" attrs="{'invisible': ['|',('emp_id', '!=', False),('active', '=', False)]}"/>
class="o_create_employee" attrs="{'invisible': ['|',('emp_id', '!=', False),('active', '=', False)]}"/>
<button string="Refuse" name="archive_applicant" type="object" attrs="{'invisible': [('active', '=', False)]}" data-hotkey="x"/>
<button string="Restore" name="toggle_active" type="object" attrs="{'invisible': [('active', '=', True)]}" data-hotkey="z"/>
<field name="stage_id" widget="statusbar" options="{'clickable': '1', 'fold_field': 'fold'}" attrs="{'invisible': [('active', '=', False),('emp_id', '=', False)]}"/>
</header>
<sheet>
<div class="oe_button_box" name="button_box">
<button name="action_open_employee"
type="object"
class="oe_stat_button"
icon="fa-id-card-o"
groups="hr.group_hr_user"
attrs="{'invisible': [('emp_id', '=', False)]}">
<div class="o_field_widget o_stat_info">
<span class="o_stat_text"><field name="employee_name"/></span>
<span class="o_stat_value">Employee</span>
</div>
</button>
<button name="action_applications_email"
class="oe_stat_button"
icon="fa-pencil"
@@ -90,15 +104,14 @@
attrs="{'invisible': [('application_count', '=' , 0)]}">
<field name="application_count" widget="statinfo" string="Other applications"/>
</button>
<button name="action_makeMeeting" class="oe_stat_button" icon="fa-calendar" type="object"
>
<button name="action_makeMeeting" class="oe_stat_button" icon="fa-calendar" type="object">
<div class="o_field_widget o_stat_info">
<span class="o_stat_value"><field name="meeting_display_text" /></span>
<span class="o_stat_text"><field name="meeting_display_date" readonly="1"/></span>
</div>
</button>
</div>
<widget name="web_ribbon" title="Refused" bg_color="bg-danger" attrs="{'invisible': [('active', '=', True)]}"/>
<widget name="web_ribbon" title="Refused" bg_color="bg-danger" attrs="{'invisible': ['|', ('active', '=', True), ('refuse_reason_id', '=', False)]}"/>
<widget name="web_ribbon" title="Hired" attrs="{'invisible': [('date_closed', '=', False)]}" />
<field name="kanban_state" widget="kanban_state_selection"/>
<field name="active" invisible="1"/>
@@ -121,14 +134,14 @@
<field name="refuse_reason_id" attrs="{'invisible': [('active', '=', True)]}"/>
<field name="email_from" widget="email"/>
<field name="email_cc" groups="base.group_no_one"/>
<field name="partner_phone" widget="phone"/>
<field name="partner_phone" widget="phone" options="{'enable_sms': 0}"/>
<field name="partner_mobile" widget="phone"/>
<field name="type_id" placeholder="Degree"/>
</group>
<group>
<field name="categ_ids" placeholder="Tags" widget="many2many_tags" options="{'color_field': 'color', 'no_create_edit': True}"/>
<field name="interviewer_id" options="{'no_create': True, 'no_create_edit': True}" />
<field name="user_id" domain="[('share', '=', False)]"/>
<field name="interviewer_id" options="{'no_create': True, 'no_create_edit': True}" widget="many2one_avatar_user" />
<field name="user_id" widget="many2one_avatar_user"/>
<field name="date_closed" attrs="{'invisible': [('date_closed', '=', False)]}" />
<field name="priority" widget="priority"/>
<field name="medium_id" groups="base.group_no_one" />
@@ -231,7 +244,8 @@
<separator/>
<filter string="Unread Messages" name="message_needaction" domain="[('message_needaction', '=', True)]"/>
<separator/>
<filter string="Archived / Refused" name="inactive" domain="[('active', '=', False)]"/>
<filter string="Archived" name="inactive" domain="[('active', '=', False), ('refuse_reason_id', '=', False)]"/>
<filter string="Refused" name="refused" domain="[('active', '=', False), ('refuse_reason_id', '!=', False)]"/>
<separator/>
<filter invisible="1" string="Late Activities" name="activities_overdue"
domain="[('my_activity_date_deadline', '&lt;', context_today().strftime('%Y-%m-%d'))]"
@@ -318,6 +332,7 @@
<field name="active"/>
<field name="activity_ids" />
<field name="activity_state" />
<field name="refuse_reason_id" />
<progressbar field="activity_state" colors='{"planned": "success", "overdue": "danger", "today": "warning"}'/>
<templates>
<t t-name="kanban-box">
@@ -326,7 +341,7 @@
<div class="ribbon ribbon-top-right" attrs="{'invisible': [('date_closed', '=', False)]}">
<span class="bg-success">Hired</span>
</div>
<span class="badge badge-pill badge-danger float-end mr-4" attrs="{'invisible': [('active', '=', True)]}">Refused</span>
<span class="badge badge-pill badge-danger float-end mr-4" attrs="{'invisible': ['|', ('active', '=', True), ('refuse_reason_id', '=', False)]}">Refused</span>
<div class="o_dropdown_kanban dropdown">
<a class="dropdown-toggle o-no-caret btn" role="button" data-toggle="dropdown" href="#" aria-label="Dropdown menu" title="Dropdown menu" data-display="static">
<span class="fa fa-ellipsis-v"/>
@@ -363,7 +378,7 @@
</div>
</div>
<div class="oe_kanban_bottom_right">
<a name="action_get_attachment_tree_view" type="object">
<a name="action_open_attachments" type="object">
<span title='Documents'><i class='fa fa-paperclip' role="img" aria-label="Documents"/>
<t t-esc="record.attachment_number.raw_value"/>
</span>
@@ -439,13 +454,19 @@
</field>
</record>
<record model="ir.actions.act_window" id="action_hr_applicant_new">
<field name="res_model">hr.applicant</field>
<field name="view_mode">form</field>
<field name="context">{'form_view_initial_mode': 'edit', 'default_job_id': active_id}</field>
</record>
<!-- Jobs -->
<record id="view_job_filter_recruitment" model="ir.ui.view">
<field name="name">Job</field>
<field name="model">hr.job</field>
<field name="inherit_id" ref="hr.view_job_filter"/>
<field name="arch" type="xml">
<xpath expr="//filter[@name='in_position']" position="before">
<xpath expr="//field[@name='department_id']" position="after">
<filter string="My Favorites" name="my_favorite_jobs" domain="[('favorite_user_ids', 'in', uid)]"/>
<separator/>
</xpath>
@@ -492,8 +513,15 @@
<field name="model">hr.job</field>
<field name="inherit_id" ref="hr.view_hr_job_form"/>
<field name="arch" type="xml">
<field name="department_id" position="before">
<field name="user_id" widget="many2one_avatar_user"/>
</field>
<xpath expr="//field[@name='department_id']" position="after">
<field name="address_id" context="{'show_address': 1}" domain= "[('is_company', '=', True )]" options="{'always_reload': True}"/>
<label for="address_id"/>
<div class="o_row">
<span attrs="{'invisible': [('address_id', '!=', False)]}" class="oe_read_only">Remote</span>
<field name="address_id" context="{'show_address': 1}" domain= "[('is_company', '=', True )]" options="{'always_reload': True}" placeholder="Remote"/>
</div>
<label for="alias_name" string="Email Alias" attrs="{'invisible': [('alias_domain', '=', False)]}" help="Define a specific contact address for this job position. If you keep it empty, the default email address will be used which is in human resources settings"/>
<div name="alias_def" attrs="{'invisible': [('alias_domain', '=', False)]}">
<field name="alias_id" class="oe_read_only" string="Email Alias" required="0"/>
@@ -502,10 +530,9 @@
</div>
</div>
</xpath>
<field name="no_of_recruitment" position="after">
<xpath expr="//div[@name='recruitment_target']" position="after">
<field name="interviewer_ids" widget="many2many_tags" options="{'no_create': True, 'no_create_edit': True}" />
<field name="user_id" domain="[('share', '=', False)]"/>
</field>
</xpath>
<div name="button_box" position="inside">
<button class="oe_stat_button"
icon="fa-pencil"
@@ -516,7 +543,7 @@
</button>
<button class="oe_stat_button"
icon="fa-file-text-o"
name="action_get_attachment_tree_view"
name="action_open_attachments"
type="object">
<field name="documents_count" widget="statinfo" string="Documents"/>
</button>
@@ -662,12 +689,18 @@
groups="group_hr_recruitment_user" sequence="100"/>
<!-- ALL JOBS REQUESTS -->
<menuitem parent="menu_hr_recruitment_configuration" id="menu_hr_job_position_config" action="action_hr_job_config" sequence="10"/>
<menuitem id="menu_hr_recruitment_config_jobs" name="Job Positions" parent="menu_hr_recruitment_configuration" sequence="10" />
<menuitem id="menu_hr_recruitment_config_applications" name="Applications" parent="menu_hr_recruitment_configuration" sequence="20" />
<menuitem id="menu_hr_recruitment_config_employees" name="Employees" parent="menu_hr_recruitment_configuration" sequence="30" />
<menuitem id="menu_hr_recruitment_config_activities" name="Activities" parent="menu_hr_recruitment_configuration" sequence="40" />
<menuitem
id="menu_hr_applicant_refuse_reason"
action="hr_applicant_refuse_reason_action"
parent="menu_hr_recruitment_configuration"
parent="menu_hr_recruitment_config_applications"
sequence="10"/>
<menuitem
@@ -702,18 +735,21 @@
</field>
</record>
<record model="ir.actions.server" id="hr_applicant_resumes_server">
<field name="name">hr.applicant.resumes.server</field>
<field name="model_id" ref="hr_recruitment.model_hr_applicant"/>
<field name="state">code</field>
<field name="code">
act = env.ref('hr_recruitment.hr_applicant_resumes').read()[0]
act['domain'] = [('res_model', '=', 'hr.applicant'), '|', ('company_id', '=', False), ('company_id', '=', env.user.company_id.id)]
action = act
<record id="ir_attachment_hr_recruitment_list_view" model="ir.ui.view">
<field name="model">ir.attachment</field>
<field name="mode">primary</field>
<field name="arch" type="xml">
<tree>
<field name="name" invisible="1"/>
<field name="res_id" invisible="1"/>
<field name="res_model" invisible="1"/>
<field name="datas" widget="binary" filename="name" string="File"/>
<field name="res_name" widget="applicant_char" string="Applicant" attrs="{'invisible': [('res_model', '!=', 'hr.applicant')]}"/>
<field name="create_date"/>
</tree>
</field>
</record>
<!-- Stage Tree View -->
<record model="ir.ui.view" id="hr_recruitment_stage_tree">
<field name="name">hr.recruitment.stage.tree</field>
@@ -819,7 +855,7 @@ action = act
<menuitem
id="menu_hr_recruitment_stage"
name="Stages"
parent="menu_hr_recruitment_configuration"
parent="menu_hr_recruitment_config_jobs"
action="hr_recruitment_stage_act"
groups="base.group_no_one"
sequence="1"/>
@@ -864,9 +900,9 @@ action = act
<menuitem
id="hr_applicant_category_menu"
parent="menu_hr_recruitment_configuration"
parent="menu_hr_recruitment_config_applications"
action="hr_applicant_category_action"
sequence="2" groups="base.group_no_one"/>
sequence="20" groups="base.group_no_one"/>
<!-- Degree Tree View -->
<record model="ir.ui.view" id="hr_recruitment_degree_tree">
@@ -906,9 +942,9 @@ action = act
<menuitem
id="menu_hr_recruitment_degree"
name="Degrees"
parent="menu_hr_recruitment_configuration"
parent="menu_hr_recruitment_config_applications"
action="hr_recruitment_degree_action"
sequence="5" groups="base.group_no_one"/>
sequence="1" groups="base.group_no_one"/>
<!-- Source Kanban View -->
<record id="hr_recruitment_source_kanban" model="ir.ui.view">
@@ -918,6 +954,7 @@ action = act
<kanban class="o_kanban_mobile" create="0" sample="1">
<field name="job_id"/>
<field name="email"/>
<field name="has_domain"/>
<templates>
<t t-name="kanban-box">
<div class="oe_kanban_card oe_kanban_global_click">
@@ -934,8 +971,8 @@ action = act
</div>
<div class="o_kanban_record_body mt-3">
<div class="text-right">
<field name="email" attrs="{'invisible': [('email', '=', False)]}" widget="email"/>
<button name="create_alias" class="btn btn-primary" type="object" attrs="{'invisible': [('email', '!=', False)]}">Generate Email</button>
<field name="email" attrs="{'invisible': [('has_domain', '=', False)]}" widget="email"/>
<button name="create_alias" class="btn btn-primary mb-2" type="object" attrs="{'invisible': ['|', ('has_domain', '=', False), ('email', '!=', False)]}">Generate Email</button>
</div>
</div>
</div>
@@ -952,10 +989,11 @@ action = act
<field name="model">hr.recruitment.source</field>
<field name="arch" type="xml">
<tree string="Sources of Applicants" editable="top" class="o_recruitment_list" sample="1">
<field name="has_domain" invisible="1"/>
<field name="source_id" placeholder="e.g. LinkedIn" decoration-bf="1" attrs="{'readonly': [('id', '!=', False)]}"/>
<field name="job_id" attrs="{'readonly': [('id', '!=', False)]}"/>
<field name="email" attrs="{'invisible': [('email', '=', False)]}" widget="email"/>
<button name="create_alias" string="Generate Email" class="btn btn-primary" type="object" attrs="{'invisible': [('email', '!=', False)]}"/>
<button name="create_alias" string="Generate Email" class="btn btn-primary" type="object" attrs="{'invisible': ['|', ('has_domain', '=', False), ('email', '!=', False)]}"/>
</tree>
</field>
</record>
@@ -966,10 +1004,10 @@ action = act
<menuitem
id="menu_hr_recruitment_source"
parent="menu_hr_recruitment_configuration"
parent="menu_hr_recruitment_config_applications"
action="hr_recruitment_source_action"
groups="base.group_no_one"
sequence="10"/>
sequence="15"/>
<record id="hr_applicant_action_from_department" model="ir.actions.act_window">
<field name="name">New Applications</field>
@@ -1111,6 +1149,7 @@ action = act
<field name="view_mode">graph,pivot</field>
<field name="search_view_id" ref="hr_applicant_view_search"/>
<field name="context">{
'search_default_creation_month': 1,
'search_default_job_id': [active_id],
'default_job_id': active_id}
</field>
@@ -1121,6 +1160,15 @@ action = act
</field>
</record>
<record id="action_applicant_send_mail" model="ir.actions.server">
<field name="name">Send Email</field>
<field name="model_id" ref="hr_recruitment.model_hr_applicant"/>
<field name="binding_model_id" ref="hr_recruitment.model_hr_applicant"/>
<field name="binding_view_types">list</field>
<field name="state">code</field>
<field name="code">action = records.action_send_email()</field>
</record>
<!-- Custom reports (aka filters) -->
<record id="hr_applicant_filter_recruiter" model="ir.filters">
<field name="name">By Recruiter</field>
@@ -10,5 +10,5 @@
</record>
<menuitem id="hr_recruitment_menu_config_activity_type"
action="mail_activity_type_action_config_hr_applicant"
parent="menu_hr_recruitment_configuration"/>
parent="menu_hr_recruitment_config_activities"/>
</odoo>
@@ -40,6 +40,18 @@
<div class="content-group" id="interview_forms"/>
</div>
</div>
<div class="col-xs-12 col-md-6 o_setting_box" id="sms">
<div class="o_setting_right_pane" id="sms_settings">
<div class="o_form_label">
Send SMS
<a href="https://www.odoo.com/documentation/master/applications/marketing/sms_marketing/pricing/pricing_and_faq.html" title="Documentation" class="ml-1 o_doc_link" target="_blank"></a>
</div>
<div class="text-muted">
Send texts to your contacts
</div>
<widget name="iap_buy_more_credits" service_name="sms" hide_service="1"/>
</div>
</div>
</div>
</div>
</xpath>
+1
View File
@@ -2,3 +2,4 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import applicant_refuse_reason
from . import applicant_send_mail
@@ -8,7 +8,7 @@ class ApplicantGetRefuseReason(models.TransientModel):
_name = 'applicant.get.refuse.reason'
_description = 'Get Refuse Reason'
refuse_reason_id = fields.Many2one('hr.applicant.refuse.reason', 'Refuse Reason')
refuse_reason_id = fields.Many2one('hr.applicant.refuse.reason', 'Refuse Reason', required=True)
applicant_ids = fields.Many2many('hr.applicant')
send_mail = fields.Boolean("Send Email", compute='_compute_send_mail', store=True, readonly=False)
template_id = fields.Many2one('mail.template', string='Email Template',
@@ -0,0 +1,50 @@
# -*- coding: utf-8 -*-
from odoo import api, fields, models, _
class ApplicantSendMail(models.TransientModel):
_name = 'applicant.send.mail'
_inherit = 'mail.composer.mixin'
_description = 'Send mails to applicants'
applicant_ids = fields.Many2many('hr.applicant', string='Applications', required=True)
author_id = fields.Many2one('res.partner', 'Author', required=True, default=lambda self: self.env.user.partner_id.id)
@api.depends('subject')
def _compute_render_model(self):
self.render_model = 'hr.applicant'
def action_send(self):
self.ensure_one()
without_emails = self.applicant_ids.filtered(lambda a: not a.email_from or (a.partner_id and not a.partner_id.email))
if without_emails:
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'type': 'danger',
'message': _("The following applicants are missing an email address: %s.", ', '.join(without_emails.mapped(lambda a: a.partner_name or a.name))),
}
}
for applicant in self.applicant_ids:
if not applicant.partner_id:
applicant.partner_id = self.env['res.partner'].create({
'is_company': False,
'type': 'private',
'name': applicant.partner_name,
'email': applicant.email_from,
'phone': applicant.partner_phone,
'mobile': applicant.partner_mobile,
})
applicant.message_post(
subject=self.subject,
body=self.body,
message_type='comment',
email_from=self.author_id.email,
email_layout_xmlid='mail.mail_notification_light',
partner_ids=applicant.partner_id.ids,
)
@@ -0,0 +1,24 @@
<?xml version="1.0"?>
<odoo>
<record id="applicant_send_mail_view_form" model="ir.ui.view">
<field name="model">applicant.send.mail</field>
<field name="arch" type="xml">
<form>
<field name="author_id" invisible="1"/>
<group>
<field name="subject" required="1"/>
<field name="applicant_ids" widget="many2many_tags" context="{'show_partner_name': 1}"/>
</group>
<group>
<field name="body" nolabel="1" class="oe-bordered-editor"
placeholder="Write your message here..."
options="{'style-inline': true}" force_save="1"/>
</group>
<footer>
<button name="action_send" string="Send" type="object" class="btn-primary" data-hotkey="q"/>
<button string="Cancel" class="btn-secondary" special="cancel" data-hotkey="z"/>
</footer>
</form>
</field>
</record>
</odoo>
@@ -10,8 +10,9 @@ class Job(models.Model):
'survey.survey', "Interview Form",
help="Choose an interview form for this job position and you will be able to print/answer this interview from all applicants who apply for this job")
def action_print_survey(self):
return self.survey_id.action_print_survey()
def action_test_survey(self):
self.ensure_one()
return self.survey_id.action_test_survey()
def action_new_survey(self):
self.ensure_one()
@@ -18,7 +18,7 @@
<field name="inherit_id" ref="hr_recruitment.hr_applicant_view_form"/>
<field name="arch" type="xml">
<xpath expr="//button[@name='archive_applicant']" position="before">
<button name="action_send_survey" string="SEND INTERVIEW" type="object"/>
<button name="action_send_survey" string="SEND INTERVIEW" type="object" attrs="{'invisible': ['|', ('active', '=', False), ('survey_id', '=', False)]}"/>
</xpath>
<xpath expr="//button[@name='action_makeMeeting']" position="after">
<button name="action_print_survey"
@@ -5,14 +5,14 @@
<field name="model">hr.job</field>
<field name="inherit_id" ref="hr_recruitment.hr_job_survey"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='user_id']" position="before">
<field name="interviewer_ids" position="after">
<label for="survey_id" groups="survey.group_survey_user"/>
<div groups="survey.group_survey_user" class="o_row">
<field name="survey_id"
context="{'default_access_mode': 'token'}"/>
<button string="Display Interview Form" name="action_print_survey" type="object" attrs="{'invisible':[('survey_id','=',False)]}" class="oe_link"/>
<button string="Display Interview Form" name="action_test_survey" type="object" attrs="{'invisible':[('survey_id','=',False)]}" class="oe_link pb-1"/>
</div>
</xpath>
</field>
</field>
</record>
<record id="view_hr_job_kanban_inherit" model="ir.ui.view">
@@ -23,10 +23,14 @@
<xpath expr="//field[@name='manager_id']" position="after">
<field name="survey_id"/>
</xpath>
<xpath expr='//div[@name="applications_action"]' position="after">
<div role="menuitem">
<a t-if="record.survey_id.raw_value" name="action_print_survey" type="object" title="Display Interview Form">Preview Interview</a>
<a t-if="!record.survey_id.raw_value" name="action_new_survey" type="object" title="Create Interview Form">Create Interview Form</a>
<xpath expr="//div[@name='menu_view_applications']" position="after">
<div role="menuitem" t-if="record.survey_id.raw_value">
<a name="action_test_survey" type="object" title="Display Interview Form">Interviews</a>
</div>
</xpath>
<xpath expr="//div[@name='menu_new_applications']" position="after">
<div role="menuitem" t-if="!record.survey_id.raw_value">
<a name="action_new_survey" type="object" title="Display Interview Form">Interview Form</a>
</div>
</xpath>
</field>
@@ -68,3 +68,8 @@ class Job(models.Model):
def get_backend_menu_id(self):
return self.env.ref('hr_recruitment.menu_hr_recruitment_root').id
def open_website_url(self):
action = super().open_website_url()
action['target'] = 'new'
return action
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="hr_job_website_inherit" model="ir.ui.view">
<field name="name">hr.job.form.inherit</field>
<field name="name">hr.job.kanban.inherit</field>
<field name="model">hr.job</field>
<field name="inherit_id" ref="hr_recruitment.view_hr_job_kanban"/>
<field name="arch" type="xml">
@@ -11,24 +11,19 @@
<span class="o_recruitment_purple">Published</span>
</div>
</xpath>
<xpath expr="//div[hasclass('o_primary')]" position="replace">
<xpath expr="//div[@name='kanban_boxes']" position="inside">
<field name="website_url" invisible="1"/>
<div class="o_primary col-11">
<span><a t-attf-href="#{record.website_url.raw_value}" class="mr-2">
<t t-esc="record.name.value"/></a>
</span>
<div class="o_recruitment_kanban_box o_kanban_primary_bottom bottom_block">
<div class="col-6 pl-0">
<field name="is_published" widget='boolean_toggle'/>
<span class="mr-2">
<t t-if="record.is_published.raw_value">Published</t>
<t t-else="">Not published</t>
</span>
</div>
<div class="col-6" name="bottom_right"></div>
</div>
</xpath>
<xpath expr="//div[@name='kanban_boxes']/div/div[1]" position="inside">
<field name="website_url" invisible="1"/>
<span><t t-if="record.no_of_recruitment.raw_value != 0">
<span class="mr-2">
<t t-if="record.is_published.raw_value">Published</t>
<t t-else="">Not published</t>
</span>
<field name="is_published" widget='boolean_toggle'/></t>
</span>
</xpath>
</field>
</record>
@@ -37,13 +32,29 @@
<field name="name">hr.job.view.kanban</field>
<field name="inherit_id" ref="hr_recruitment.view_hr_job_kanban"/>
<field name="arch" type="xml">
<xpath expr="//div[hasclass('o_link_trackers')]" position="replace">
<xpath expr="//div[@name='bottom_right']" position="replace">
<field name="website_url" invisible="1"/>
<div class="o_link_trackers col-6">
<a role="button" name="%(hr_recruitment.action_hr_job_sources)d" type="action" class="btn btn-sm py-0">
<span title='Link Trackers'><i class='fa fa-lg fa-link' role="img" aria-label="Link Trackers"/></span>
<a t-att-href="record.website_url.raw_value" target="_blank">
<i class="fa fa-fw fa-external-link" role="img"></i>
Job Page
</a>
</div>
</xpath>
</field>
</record>
<record id="hr_job_search_view_inherit" model="ir.ui.view">
<field name="model">hr.job</field>
<field name="inherit_id" ref="hr.view_job_filter"/>
<field name="arch" type="xml">
<xpath expr="//filter[@name='message_needaction']" position="before">
<filter name="published" string="Published" domain="[('is_published', '=', True)]"/>
<separator/>
</xpath>
<xpath expr="//group" position="inside">
<filter string="Published" name="groupby_published" domain="[]" context="{'group_by': 'is_published'}"/>
</xpath>
</field>
</record>
</odoo>
@@ -16,8 +16,8 @@
<field name="model">hr.recruitment.source</field>
<field name="inherit_id" ref="hr_recruitment.hr_recruitment_source_kanban"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='email']" position="after">
<field name="url"/>
<xpath expr="//button[@name='create_alias']" position="after">
<field name="url" widget="CopyClipboardURL"/>
</xpath>
<xpath expr="//div[hasclass('o_kanban_record_body')]/div" position="before">
<div class="float-start">
@@ -35,7 +35,7 @@
<div name="button_box" position="inside">
<field name="is_published" widget="website_redirect_button"/>
</div>
<xpath expr="//field[@name='no_of_recruitment']" position="after">
<xpath expr="//div[@name='recruitment_target']" position="after">
<field name="website_published" string="Is Published"/>
</xpath>
</field>
@@ -49,19 +49,22 @@
<field name="company_id" position="after">
<field name="website_id" options="{'no_create': True}" domain="['|', ('company_id', '=', company_id), ('company_id', '=', False)]" groups="website.group_multi_website"/>
</field>
<field name="description" position="attributes">
<attribute name="placeholder">You can write here a short description of your Job Description that will be displayed on the main Jobs' list page.</attribute>
</field>
</field>
</record>
<record id="view_hr_job_tree_inherit_website" model="ir.ui.view">
<field name="name">hr.job.tree</field>
<field name="model">hr.job</field>
<field name="inherit_id" ref="hr.view_hr_job_tree"/>
<field name="inherit_id" ref="hr_recruitment.hr_job_view_tree_inherit"/>
<field name="arch" type="xml">
<field name="department_id" position="after">
<field name="website_id" groups="website.group_multi_website"/>
<field name="no_of_employee" position="before">
<field name="website_id" groups="website.group_multi_website" optional="hide"/>
</field>
<xpath expr="//field[@name='state']" position="after">
<field name="website_published" string="Published"/>
<xpath expr="//field[@name='alias_id']" position="before">
<field name="website_published" string="Published" widget="toggle_button"/>
</xpath>
</field>
</record>
@@ -467,7 +467,7 @@
<div id="wrap">
<div class="oe_structure">
<div class="container">
<div class="row">
<div class="row pt40">
<div class="col-lg-12">
<h1 class="text-center">Congratulations!</h1>
<p class="text-center">
@@ -495,7 +495,7 @@
<br/><i class="fa fa-phone" role="img" aria-label="Phone" title="Phone"></i> <span t-field="responsible.phone"/>
</t>
</div>
<div class="col-lg-12 mt32 text-center">
<div class="col-lg-12 mt32 mb32 text-center">
<span>
We usually reply between one and three days.<br/>
Feel free to contact him/her if you have further questions.
@@ -504,7 +504,7 @@
</t>
</div>
<div class="row" id="o_recruitment_thank_cta">
<div class="col-lg-12 text-center mt32 mb32">
<div class="col-lg-12 text-center mb32">
In the meantime,
<h3 class="mt8 mb32">Look around on our website:</h3>
<a role="button" href="/" class="btn btn-primary btn-lg">Continue To Our Website</a>