[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:
@@ -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
|
||||
})
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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,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>
|
||||
|
||||
@@ -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', '<', 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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user