initial commit
This commit is contained in:
commit
cd7aff508a
39
README.md
Normal file
39
README.md
Normal file
@ -0,0 +1,39 @@
|
||||
Manage Recruitment and Job applications
|
||||
---------------------------------------
|
||||
|
||||
Publish, promote and organize your job offers with the Odoo
|
||||
<a href="https://www.odoo.com/app/recruitment">Open Source Recruitment Application</a>.
|
||||
|
||||
Organize your job board, promote your job announces and keep track of
|
||||
application submissions easily. Follow every applicant and build up a database
|
||||
of skills and profiles with indexed documents.
|
||||
|
||||
Post Your Jobs on Best Job Boards
|
||||
---------------------------------
|
||||
|
||||
Connect automatically to most famous job board websites; linkedIn, Monster,
|
||||
Craigslist, ... Every job position has a new email address automatically
|
||||
assigned to route applications automatically to the right job position.
|
||||
|
||||
Whether applicants contact you by email or using an online form, you get all
|
||||
the data indexed automatically (resumes, motivation letter) and you can answer
|
||||
in just a click, reusing templates of answers.
|
||||
|
||||
Customize Your Recruitment Process
|
||||
----------------------------------
|
||||
|
||||
Use the kanban view and customize the steps of your recruitments process;
|
||||
pre-qualification, first interview, second interview, negociaiton, ...
|
||||
|
||||
Get accurate statistics on your recruitment pipeline. Get reports to compare
|
||||
the performance of your different investments on external job boards.
|
||||
|
||||
Streamline Your Recruitment Process
|
||||
-----------------------------------
|
||||
|
||||
Follow applicants in your recruitment process with the smart kanban view. Save
|
||||
time by automating some communications with email templates.
|
||||
|
||||
Documents like resumes and motivation letters are indexed automatically,
|
||||
allowing you to easily find for specific skills and build up a database of
|
||||
profiles.
|
5
__init__.py
Normal file
5
__init__.py
Normal file
@ -0,0 +1,5 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import models
|
||||
from . import wizard
|
56
__manifest__.py
Normal file
56
__manifest__.py
Normal file
@ -0,0 +1,56 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
{
|
||||
'name': 'Recruitment',
|
||||
'version': '1.1',
|
||||
'category': 'Human Resources/Recruitment',
|
||||
'sequence': 90,
|
||||
'summary': 'Track your recruitment pipeline',
|
||||
'website': 'https://www.odoo.com/app/recruitment',
|
||||
'depends': [
|
||||
'hr',
|
||||
'calendar',
|
||||
'utm',
|
||||
'attachment_indexation',
|
||||
'web_tour',
|
||||
'digest',
|
||||
],
|
||||
'data': [
|
||||
'security/hr_recruitment_security.xml',
|
||||
'security/ir.model.access.csv',
|
||||
'data/digest_data.xml',
|
||||
'data/mail_message_subtype_data.xml',
|
||||
'data/mail_template_data.xml',
|
||||
'data/mail_templates.xml',
|
||||
'data/hr_recruitment_data.xml',
|
||||
'views/hr_recruitment_degree_views.xml',
|
||||
'views/hr_recruitment_source_views.xml',
|
||||
'views/hr_recruitment_stage_views.xml',
|
||||
'views/ir_attachment_views.xml',
|
||||
'views/hr_applicant_category_views.xml',
|
||||
'views/hr_applicant_refuse_reason_views.xml',
|
||||
'views/hr_applicant_views.xml',
|
||||
'views/res_config_settings_views.xml',
|
||||
'views/hr_department_views.xml',
|
||||
'views/hr_job_views.xml',
|
||||
'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',
|
||||
],
|
||||
'installable': True,
|
||||
'application': True,
|
||||
'assets': {
|
||||
'web.assets_backend': [
|
||||
'hr_recruitment/static/src/**/*.js',
|
||||
'hr_recruitment/static/src/**/*.scss',
|
||||
'hr_recruitment/static/src/**/*.xml',
|
||||
'hr_recruitment/static/src/js/tours/hr_recruitment.js',
|
||||
],
|
||||
},
|
||||
'license': 'LGPL-3',
|
||||
}
|
28
data/digest_data.xml
Normal file
28
data/digest_data.xml
Normal file
@ -0,0 +1,28 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<record id="digest.digest_digest_default" model="digest.digest">
|
||||
<field name="kpi_hr_recruitment_new_colleagues">True</field>
|
||||
</record>
|
||||
</data>
|
||||
|
||||
<data>
|
||||
<record id="digest_tip_hr_recruitment_0" model="digest.tip">
|
||||
<field name="name">Tip: Let candidates apply by email</field>
|
||||
<field name="sequence">1300</field>
|
||||
<field name="group_id" ref="hr_recruitment.group_hr_recruitment_manager" />
|
||||
<field name="tip_description" type="html">
|
||||
<div>
|
||||
<p class="tip_title">Tip: Let candidates apply by email</p>
|
||||
<p class="tip_content">
|
||||
By setting an alias to a job position, emails sent to this address create applications automatically. You can even use multiple trackers to get statistics according to the source of the application: LinkedIn, Monster, Indeed, etc.
|
||||
<t t-set="record" t-value="object.env['hr.job'].search([('alias_name', '!=', False)], limit=1)" />
|
||||
<t t-if="record.alias_email">
|
||||
<a t-attf-href="mailto:{{record.alias_email}}" target="_blank" style="color: #714B67; text-decoration: none;">Try sending an email</a>
|
||||
</t>
|
||||
</p>
|
||||
</div>
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
112
data/hr_recruitment_data.xml
Normal file
112
data/hr_recruitment_data.xml
Normal file
@ -0,0 +1,112 @@
|
||||
<?xml version="1.0"?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
|
||||
<!-- Meeting Types (for interview meetings) -->
|
||||
<record model="calendar.event.type" id="categ_meet_interview">
|
||||
<field name="name">Interview</field>
|
||||
</record>
|
||||
|
||||
<record model="hr.recruitment.degree" id="degree_graduate">
|
||||
<field name="name">Graduate</field>
|
||||
<field name="sequence">1</field>
|
||||
</record>
|
||||
<record model="hr.recruitment.degree" id="degree_bachelor">
|
||||
<field name="name">Bachelor Degree</field>
|
||||
<field name="sequence">2</field>
|
||||
</record>
|
||||
<record model="hr.recruitment.degree" id="degree_licenced">
|
||||
<field name="name">Master Degree</field>
|
||||
<field name="sequence">3</field>
|
||||
</record>
|
||||
<record model="hr.recruitment.degree" id="degree_bac5">
|
||||
<field name="name">Doctoral Degree</field>
|
||||
<field name="sequence">4</field>
|
||||
</record>
|
||||
|
||||
<!-- Applicant Categories(Tag) -->
|
||||
<record id="tag_applicant_reserve" model="hr.applicant.category">
|
||||
<field name="name">Reserve</field>
|
||||
</record>
|
||||
<record id="tag_applicant_manager" model="hr.applicant.category">
|
||||
<field name="name">Manager</field>
|
||||
</record>
|
||||
<record id="tag_applicant_it" model="hr.applicant.category">
|
||||
<field name="name">IT</field>
|
||||
</record>
|
||||
<record id="tag_applicant_sales" model="hr.applicant.category">
|
||||
<field name="name">Sales</field>
|
||||
</record>
|
||||
<record model="utm.campaign" id="utm_campaign_job">
|
||||
<field name="name">Job Campaign</field>
|
||||
</record>
|
||||
|
||||
<record model="hr.recruitment.stage" id="stage_job0">
|
||||
<field name="name">New</field>
|
||||
<field name="sequence">0</field>
|
||||
<field name="template_id" ref="email_template_data_applicant_congratulations"/>
|
||||
</record>
|
||||
<record model="hr.recruitment.stage" id="stage_job1">
|
||||
<field name="name">Initial Qualification</field>
|
||||
<field name="sequence">1</field>
|
||||
</record>
|
||||
<record model="hr.recruitment.stage" id="stage_job2">
|
||||
<field name="name">First Interview</field>
|
||||
<field name="sequence">2</field>
|
||||
</record>
|
||||
<record model="hr.recruitment.stage" id="stage_job3">
|
||||
<field name="name">Second Interview</field>
|
||||
<field name="sequence">3</field>
|
||||
</record>
|
||||
<record model="hr.recruitment.stage" id="stage_job4">
|
||||
<field name="name">Contract Proposal</field>
|
||||
<field name="sequence">4</field>
|
||||
</record>
|
||||
<record model="hr.recruitment.stage" id="stage_job5">
|
||||
<field name="name">Contract Signed</field>
|
||||
<field name="sequence">5</field>
|
||||
<field name="fold" eval="True"/>
|
||||
<field name="hired_stage">True</field>
|
||||
</record>
|
||||
|
||||
<!-- applicant refuse reason -->
|
||||
<record id="refuse_reason_1" model="hr.applicant.refuse.reason">
|
||||
<field name="name">Doesn't fit the job requirements</field>
|
||||
<field name="template_id" ref="email_template_data_applicant_refuse"/>
|
||||
</record>
|
||||
<record id="refuse_reason_2" model="hr.applicant.refuse.reason">
|
||||
<field name="name">Refused by Applicant: don't like job</field>
|
||||
<field name="template_id" ref="email_template_data_applicant_not_interested"/>
|
||||
</record>
|
||||
<record id="refuse_reason_3" model="hr.applicant.refuse.reason">
|
||||
<field name="name">Refused by Applicant: better offer</field>
|
||||
<field name="template_id" ref="email_template_data_applicant_not_interested"/>
|
||||
</record>
|
||||
<record id="refuse_reason_4" model="hr.applicant.refuse.reason">
|
||||
<field name="name">Language issues</field>
|
||||
<field name="template_id" ref="email_template_data_applicant_refuse"/>
|
||||
</record>
|
||||
<record id="refuse_reason_5" model="hr.applicant.refuse.reason">
|
||||
<field name="name">Role already fulfilled</field>
|
||||
<field name="template_id" ref="email_template_data_applicant_refuse"/>
|
||||
</record>
|
||||
<record id="refuse_reason_6" model="hr.applicant.refuse.reason">
|
||||
<field name="name">Duplicate</field>
|
||||
<field name="template_id" ref="email_template_data_applicant_refuse"/>
|
||||
</record>
|
||||
<record id="refuse_reason_7" model="hr.applicant.refuse.reason">
|
||||
<field name="name">Spam</field>
|
||||
<field name="template_id" ref="email_template_data_applicant_refuse"/>
|
||||
</record>
|
||||
<record id="refuse_reason_8" model="hr.applicant.refuse.reason">
|
||||
<field name="name">Refused by Applicant: salary</field>
|
||||
<field name="template_id" ref="email_template_data_applicant_not_interested"/>
|
||||
</record>
|
||||
|
||||
<record model="ir.config_parameter" id="hr_recruitment_blacklisted_emails" forcecreate="False">
|
||||
<field name="key">hr_recruitment.blacklisted_emails</field>
|
||||
<field name="value"> </field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</odoo>
|
654
data/hr_recruitment_demo.xml
Normal file
654
data/hr_recruitment_demo.xml
Normal file
@ -0,0 +1,654 @@
|
||||
<?xml version="1.0"?>
|
||||
<odoo noupdate="1">
|
||||
<record id="base.user_demo" model="res.users">
|
||||
<field name="groups_id" eval="[(3, ref('hr_recruitment.group_hr_recruitment_manager'))]"/>
|
||||
</record>
|
||||
|
||||
<!--Manage the job_id to get in hr.applicant-->
|
||||
<record id="hr.job_developer" model="hr.job">
|
||||
<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="no_of_hired_employee">1</field>
|
||||
</record>
|
||||
<record id="hr.job_cto" model="hr.job">
|
||||
<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="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="no_of_hired_employee">5</field>
|
||||
</record>
|
||||
<record id="hr.job_marketing" model="hr.job">
|
||||
<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="no_of_recruitment">6</field>
|
||||
</record>
|
||||
|
||||
<record id="hr_recruitment_linkedin_developer" model="hr.recruitment.source">
|
||||
<field name="source_id" ref="utm.utm_source_linkedin"/>
|
||||
<field name="job_id" ref="hr.job_developer"/>
|
||||
</record>
|
||||
|
||||
<record id="hr_recruitment_linkedin_ceo" model="hr.recruitment.source">
|
||||
<field name="source_id" ref="utm.utm_source_linkedin"/>
|
||||
<field name="job_id" ref="hr.job_ceo"/>
|
||||
</record>
|
||||
|
||||
<record id="hr_recruitment_linkedin_cto" model="hr.recruitment.source">
|
||||
<field name="source_id" ref="utm.utm_source_linkedin"/>
|
||||
<field name="job_id" ref="hr.job_cto"/>
|
||||
</record>
|
||||
|
||||
<record id="hr_recruitment_linkedin_consultant" model="hr.recruitment.source">
|
||||
<field name="source_id" ref="utm.utm_source_linkedin"/>
|
||||
<field name="job_id" ref="hr.job_consultant"/>
|
||||
</record>
|
||||
|
||||
<record id="hr_recruitment_linkedin_hrm" model="hr.recruitment.source">
|
||||
<field name="source_id" ref="utm.utm_source_linkedin"/>
|
||||
<field name="job_id" ref="hr.job_hrm"/>
|
||||
</record>
|
||||
|
||||
<record id="hr_recruitment_linkedin_marketing" model="hr.recruitment.source">
|
||||
<field name="source_id" ref="utm.utm_source_linkedin"/>
|
||||
<field name="job_id" ref="hr.job_marketing"/>
|
||||
</record>
|
||||
|
||||
<record id="hr_recruitment_linkedin_trainee" model="hr.recruitment.source">
|
||||
<field name="source_id" ref="utm.utm_source_linkedin"/>
|
||||
<field name="job_id" ref="hr.job_trainee"/>
|
||||
</record>
|
||||
|
||||
<record id="hr_case_salesman0" model="hr.applicant">
|
||||
<field name="name">Sales Manager</field>
|
||||
<field name="job_id" ref="hr.job_marketing"/>
|
||||
<field name="department_id" ref="hr.dep_sales"/>
|
||||
<field name="medium_id" ref="utm.utm_medium_direct"/>
|
||||
<field name="type_id" ref="degree_graduate"/>
|
||||
<field name="categ_ids" eval="[(6,0,[ref('tag_applicant_sales')])]"/>
|
||||
<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_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')"/>
|
||||
<field name="availability" eval="(DateTime.today() + relativedelta(months=3)).strftime('%Y-%m-%d')"/>
|
||||
</record>
|
||||
<record id="hr_case_salesman1" model="hr.applicant">
|
||||
<field name="name">Sales</field>
|
||||
<field name="job_id" ref="hr.job_marketing"/>
|
||||
<field name="department_id" ref="hr.dep_sales"/>
|
||||
<field name="type_id" ref="degree_graduate"/>
|
||||
<field name="categ_ids" eval="[(6,0,[ref('tag_applicant_sales')])]"/>
|
||||
<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"/>
|
||||
<field name="availability" eval="(DateTime.today() + relativedelta(months=3)).strftime('%Y-%m-%d')"/>
|
||||
</record>
|
||||
<record id="hr_case_dev0" model="hr.applicant">
|
||||
<field name="name">Developer PHP</field>
|
||||
<field name="job_id" ref="hr.job_developer"/>
|
||||
<field name="department_id" ref="hr.dep_rd"/>
|
||||
<field name="medium_id" ref="utm.utm_medium_email"/>
|
||||
<field name="type_id" ref="degree_graduate"/>
|
||||
<field name="categ_ids" eval="[(6,0,[ref('tag_applicant_it')])]"/>
|
||||
<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"/>
|
||||
<field name="availability" eval="(DateTime.today() + timedelta(days=15)).strftime('%Y-%m-%d')"/>
|
||||
</record>
|
||||
<record id="hr_case_dev1" model="hr.applicant">
|
||||
<field name="name">Developer Fullstack</field>
|
||||
<field name="job_id" ref="hr.job_developer"/>
|
||||
<field name="department_id" ref="hr.dep_rd"/>
|
||||
<field name="type_id" ref="degree_graduate"/>
|
||||
<field name="categ_ids" eval="[(6,0,[ref('tag_applicant_it')])]"/>
|
||||
<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>
|
||||
<record id="hr_case_dev2" model="hr.applicant">
|
||||
<field name="name">Developer Python</field>
|
||||
<field name="job_id" ref="hr.job_developer"/>
|
||||
<field name="department_id" ref="hr.dep_rd"/>
|
||||
<field name="medium_id" ref="utm.utm_medium_email"/>
|
||||
<field name="type_id" ref="degree_graduate"/>
|
||||
<field name="categ_ids" eval="[(6,0,[ref('tag_applicant_it')])]"/>
|
||||
<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>
|
||||
<record id="hr_case_dev3" model="hr.applicant">
|
||||
<field name="name">Developer C/C++</field>
|
||||
<field name="job_id" ref="hr.job_developer"/>
|
||||
<field name="department_id" ref="hr.dep_rd"/>
|
||||
<field name="type_id" ref="degree_graduate"/>
|
||||
<field name="categ_ids" eval="[(6,0,[ref('tag_applicant_it')])]"/>
|
||||
<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>
|
||||
<record id="hr_case_traineemca0" model="hr.applicant">
|
||||
<field name="name">Trainee - MCA</field>
|
||||
<field name="job_id" ref="hr.job_trainee"/>
|
||||
<field name="department_id" ref="hr.dep_rd"/>
|
||||
<field name="type_id" ref="degree_licenced"/>
|
||||
<field name="categ_ids" eval="[(6,0,[ref('tag_applicant_manager')])]"/>
|
||||
<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')"/>
|
||||
<field name="availability" eval="(DateTime.today() + timedelta(days=15)).strftime('%Y-%m-%d')"/>
|
||||
</record>
|
||||
<record id="hr_case_fresher0" model="hr.applicant">
|
||||
<field name="name">Fresher</field>
|
||||
<field name="job_id" ref="hr.job_trainee"/>
|
||||
<field name="department_id" ref="hr.dep_administration"/>
|
||||
<field name="type_id" ref="degree_bachelor"/>
|
||||
<field name="categ_ids" eval="[(6,0,[ref('tag_applicant_it')])]"/>
|
||||
<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>
|
||||
<record id="hr_case_mkt0" model="hr.applicant">
|
||||
<field name="name">Marketing</field>
|
||||
<field name="job_id" ref="hr.job_marketing"/>
|
||||
<field name="department_id" ref="hr.dep_sales"/>
|
||||
<field name="type_id" ref="degree_graduate"/>
|
||||
<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">
|
||||
<field name="name">Marketing 2 Year Experience</field>
|
||||
<field name="job_id" ref="hr.job_marketing"/>
|
||||
<field name="department_id" ref="hr.dep_sales"/>
|
||||
<field name="type_id" ref="degree_graduate"/>
|
||||
<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"/>
|
||||
<field name="availability" eval="(DateTime.today() + timedelta(days=15)).strftime('%Y-%m-%d')"/>
|
||||
</record>
|
||||
<record id="hr_case_yrsexperienceinphp0" model="hr.applicant">
|
||||
<field name="name">Marketing Job</field>
|
||||
<field eval="(datetime.now()+relativedelta(months=-2)).strftime('%Y-%m-03 01:00:00')" name="create_date"/>
|
||||
<field name="job_id" ref="hr.job_marketing"/>
|
||||
<field name="department_id" ref="hr.dep_sales"/>
|
||||
<field name="type_id" ref="degree_graduate"/>
|
||||
<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>
|
||||
<field eval="(datetime.now()+relativedelta(months=-1)).strftime('%Y-%m-08 01:00:00')" name="create_date"/>
|
||||
<field name="job_id" ref="hr.job_developer"/>
|
||||
<field name="department_id" ref="hr.dep_rd"/>
|
||||
<field name="type_id" ref="degree_licenced"/>
|
||||
<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>
|
||||
<field name="job_id" ref="hr.job_hrm"/>
|
||||
<field name="department_id" ref="hr.dep_administration"/>
|
||||
<field name="type_id" ref="degree_licenced"/>
|
||||
<field name="categ_ids" eval="[(6,0,[ref('tag_applicant_reserve')])]"/>
|
||||
<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>
|
||||
<field name="availability" eval="(DateTime.today() + relativedelta(months=3)).strftime('%Y-%m-%d')"/>
|
||||
</record>
|
||||
<record id="hr_case_financejob1" model="hr.applicant">
|
||||
<field name="name">Finance</field>
|
||||
<field name="job_id" ref="hr.job_hrm"/>
|
||||
<field name="department_id" ref="hr.dep_administration"/>
|
||||
<field name="type_id" ref="degree_licenced"/>
|
||||
<field name="categ_ids" eval="[(6,0,[ref('tag_applicant_reserve')])]"/>
|
||||
<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')"/>
|
||||
<field name="availability" eval="(DateTime.today() + timedelta(days=15)).strftime('%Y-%m-%d')"/>
|
||||
</record>
|
||||
<record id="hr_case_traineemca1" model="hr.applicant">
|
||||
<field name="name">Trainee - MCA</field>
|
||||
<field name="job_id" ref="hr.job_trainee"/>
|
||||
<field name="department_id" ref="hr.dep_rd"/>
|
||||
<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>
|
||||
<field name="job_id" ref="hr.job_developer"/>
|
||||
<field name="department_id" ref="hr.dep_rd"/>
|
||||
<field name="type_id" ref="degree_licenced"/>
|
||||
<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')"/>
|
||||
<field name="availability" eval="(DateTime.today() + relativedelta(months=3)).strftime('%Y-%m-%d')"/>
|
||||
</record>
|
||||
<record id="hr_case_advertisement" model="hr.applicant">
|
||||
<field name="name">Advertisement</field>
|
||||
<field name="job_id" ref="hr.job_consultant"/>
|
||||
<field name="department_id" ref="hr.dep_ps"/>
|
||||
<field name="type_id" ref="degree_licenced"/>
|
||||
<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')"/>
|
||||
<field name="availability" eval="(DateTime.today() + relativedelta(months=3)).strftime('%Y-%m-%d')"/>
|
||||
</record>
|
||||
|
||||
<record id="hr_case_dev2_cv" model="ir.attachment">
|
||||
<field name="name">Cecile_Donth_CV.pdf</field>
|
||||
<field name="datas" type="base64" file="hr_recruitment/static/applicant_cvs/Cecile_Donth_CV.pdf"></field>
|
||||
<field name="res_model">hr.applicant</field>
|
||||
<field name="res_id" ref="hr_recruitment.hr_case_dev2"/>
|
||||
</record>
|
||||
|
||||
<record id="hr_case_financejob0_cv" model="ir.attachment">
|
||||
<field name="name">David_Armstrong_CV.pdf</field>
|
||||
<field name="datas" type="base64" file="hr_recruitment/static/applicant_cvs/David_Armstrong_CV.pdf"></field>
|
||||
<field name="res_model">hr.applicant</field>
|
||||
<field name="res_id" ref="hr_recruitment.hr_case_financejob0"/>
|
||||
</record>
|
||||
|
||||
<record id="hr_case_advertisement_cv" model="ir.attachment">
|
||||
<field name="name">David_Billy_CV.pdf</field>
|
||||
<field name="datas" type="base64" file="hr_recruitment/static/applicant_cvs/David_Billy_CV.pdf"></field>
|
||||
<field name="res_model">hr.applicant</field>
|
||||
<field name="res_id" ref="hr_recruitment.hr_case_advertisement"/>
|
||||
</record>
|
||||
|
||||
<record id="hr_case_salesman0_cv" model="ir.attachment">
|
||||
<field name="name">Enrique_Jones_CV.pdf</field>
|
||||
<field name="datas" type="base64" file="hr_recruitment/static/applicant_cvs/Enrique_Jones_CV.pdf"></field>
|
||||
<field name="res_model">hr.applicant</field>
|
||||
<field name="res_id" ref="hr_recruitment.hr_case_salesman0"/>
|
||||
</record>
|
||||
|
||||
<record id="hr_case_mkt1_cv" model="ir.attachment">
|
||||
<field name="name">Hubert_Blank_CV.pdf</field>
|
||||
<field name="datas" type="base64" file="hr_recruitment/static/applicant_cvs/Hubert_Blank_CV.pdf"></field>
|
||||
<field name="res_model">hr.applicant</field>
|
||||
<field name="res_id" ref="hr_recruitment.hr_case_mkt1"/>
|
||||
</record>
|
||||
|
||||
<record id="hr_case_dev0_cv" model="ir.attachment">
|
||||
<field name="name">Johan_Duck_CV.pdf</field>
|
||||
<field name="datas" type="base64" file="hr_recruitment/static/applicant_cvs/Johan_Duck_CV.pdf"></field>
|
||||
<field name="res_model">hr.applicant</field>
|
||||
<field name="res_id" ref="hr_recruitment.hr_case_dev0"/>
|
||||
</record>
|
||||
|
||||
<record id="hr_case_yrsexperienceinphp0_cv" model="ir.attachment">
|
||||
<field name="name">John_Bruno_CV.pdf</field>
|
||||
<field name="datas" type="base64" file="hr_recruitment/static/applicant_cvs/John_Bruno_CV.pdf"></field>
|
||||
<field name="res_model">hr.applicant</field>
|
||||
<field name="res_id" ref="hr_recruitment.hr_case_yrsexperienceinphp0"/>
|
||||
</record>
|
||||
|
||||
<record id="hr_case_financejob1_cv" model="ir.attachment">
|
||||
<field name="name">Joren_Jacob_CV.pdf</field>
|
||||
<field name="datas" type="base64" file="hr_recruitment/static/applicant_cvs/Joren_Jacob_CV.pdf"></field>
|
||||
<field name="res_model">hr.applicant</field>
|
||||
<field name="res_id" ref="hr_recruitment.hr_case_financejob1"/>
|
||||
</record>
|
||||
|
||||
<record id="hr_case_fresher0_cv" model="ir.attachment">
|
||||
<field name="name">Jose_CV.pdf</field>
|
||||
<field name="datas" type="base64" file="hr_recruitment/static/applicant_cvs/Jose_CV.pdf"></field>
|
||||
<field name="res_model">hr.applicant</field>
|
||||
<field name="res_id" ref="hr_recruitment.hr_case_fresher0"/>
|
||||
</record>
|
||||
|
||||
<record id="hr_case_dev1_cv" model="ir.attachment">
|
||||
<field name="name">Kelly_Wallant_CV.pdf</field>
|
||||
<field name="datas" type="base64" file="hr_recruitment/static/applicant_cvs/Kelly_Wallant_CV.pdf"></field>
|
||||
<field name="res_model">hr.applicant</field>
|
||||
<field name="res_id" ref="hr_recruitment.hr_case_dev1"/>
|
||||
</record>
|
||||
|
||||
<record id="hr_case_traineemca0_cv" model="ir.attachment">
|
||||
<field name="name">Marie_Justine_CV.pdf</field>
|
||||
<field name="datas" type="base64" file="hr_recruitment/static/applicant_cvs/Marie_Justine_CV.pdf"></field>
|
||||
<field name="res_model">hr.applicant</field>
|
||||
<field name="res_id" ref="hr_recruitment.hr_case_traineemca0"/>
|
||||
</record>
|
||||
|
||||
<record id="hr_case_salesman1_cv" model="ir.attachment">
|
||||
<field name="name">Meldona_Thang_CV.pdf</field>
|
||||
<field name="datas" type="base64" file="hr_recruitment/static/applicant_cvs/Meldona_Thang_CV.pdf"></field>
|
||||
<field name="res_model">hr.applicant</field>
|
||||
<field name="res_id" ref="hr_recruitment.hr_case_salesman1"/>
|
||||
</record>
|
||||
|
||||
<record id="hr_case_dev3_cv" model="ir.attachment">
|
||||
<field name="name">Ohen_Rizome_CV.pdf</field>
|
||||
<field name="datas" type="base64" file="hr_recruitment/static/applicant_cvs/Ohen_Rizome_CV.pdf"></field>
|
||||
<field name="res_model">hr.applicant</field>
|
||||
<field name="res_id" ref="hr_recruitment.hr_case_dev3"/>
|
||||
</record>
|
||||
|
||||
<record id="hr_case_marketingjob0_cv" model="ir.attachment">
|
||||
<field name="name">Sandra_Elvis_CV.pdf</field>
|
||||
<field name="datas" type="base64" file="hr_recruitment/static/applicant_cvs/Sandra_Elvis_CV.pdf"></field>
|
||||
<field name="res_model">hr.applicant</field>
|
||||
<field name="res_id" ref="hr_recruitment.hr_case_marketingjob0"/>
|
||||
</record>
|
||||
|
||||
<record id="hr_case_programmer_cv" model="ir.attachment">
|
||||
<field name="name">Shane_Williams_CV.pdf</field>
|
||||
<field name="datas" type="base64" file="hr_recruitment/static/applicant_cvs/Shane_Williams_CV.pdf"></field>
|
||||
<field name="res_model">hr.applicant</field>
|
||||
<field name="res_id" ref="hr_recruitment.hr_case_programmer"/>
|
||||
</record>
|
||||
|
||||
<record id="hr_case_traineemca1_cv" model="ir.attachment">
|
||||
<field name="name">Tina_Augustie_CV.pdf</field>
|
||||
<field name="datas" type="base64" file="hr_recruitment/static/applicant_cvs/Tina_Augustie_CV.pdf"></field>
|
||||
<field name="res_model">hr.applicant</field>
|
||||
<field name="res_id" ref="hr_recruitment.hr_case_traineemca1"/>
|
||||
</record>
|
||||
|
||||
<record id="hr_case_mkt0_cv" model="ir.attachment">
|
||||
<field name="name">Yin_Lee_CV.pdf</field>
|
||||
<field name="datas" type="base64" file="hr_recruitment/static/applicant_cvs/Yin_Lee_CV.pdf"></field>
|
||||
<field name="res_model">hr.applicant</field>
|
||||
<field name="res_id" ref="hr_recruitment.hr_case_mkt0"/>
|
||||
</record>
|
||||
|
||||
<!-- Set the main attachment to avoid automatic sending to the OCR-->
|
||||
<record id="hr_case_salesman0" model="hr.applicant">
|
||||
<field name="message_main_attachment_id" ref="hr_recruitment.hr_case_salesman0_cv"/>
|
||||
</record>
|
||||
<record id="hr_case_salesman1" model="hr.applicant">
|
||||
<field name="message_main_attachment_id" ref="hr_recruitment.hr_case_salesman1_cv"/>
|
||||
</record>
|
||||
<record id="hr_case_dev0" model="hr.applicant">
|
||||
<field name="message_main_attachment_id" ref="hr_recruitment.hr_case_dev0_cv"/>
|
||||
</record>
|
||||
<record id="hr_case_dev1" model="hr.applicant">
|
||||
<field name="message_main_attachment_id" ref="hr_recruitment.hr_case_dev1_cv"/>
|
||||
</record>
|
||||
<record id="hr_case_dev2" model="hr.applicant">
|
||||
<field name="message_main_attachment_id" ref="hr_recruitment.hr_case_dev2_cv"/>
|
||||
</record>
|
||||
<record id="hr_case_dev3" model="hr.applicant">
|
||||
<field name="message_main_attachment_id" ref="hr_recruitment.hr_case_dev3_cv"/>
|
||||
</record>
|
||||
<record id="hr_case_traineemca0" model="hr.applicant">
|
||||
<field name="message_main_attachment_id" ref="hr_recruitment.hr_case_traineemca0_cv"/>
|
||||
</record>
|
||||
<record id="hr_case_fresher0" model="hr.applicant">
|
||||
<field name="message_main_attachment_id" ref="hr_recruitment.hr_case_fresher0_cv"/>
|
||||
</record>
|
||||
<record id="hr_case_mkt0" model="hr.applicant">
|
||||
<field name="message_main_attachment_id" ref="hr_recruitment.hr_case_mkt0_cv"/>
|
||||
</record>
|
||||
<record id="hr_case_mkt1" model="hr.applicant">
|
||||
<field name="message_main_attachment_id" ref="hr_recruitment.hr_case_mkt1_cv"/>
|
||||
</record>
|
||||
<record id="hr_case_yrsexperienceinphp0" model="hr.applicant">
|
||||
<field name="message_main_attachment_id" ref="hr_recruitment.hr_case_yrsexperienceinphp0_cv"/>
|
||||
</record>
|
||||
<record id="hr_case_marketingjob0" model="hr.applicant">
|
||||
<field name="message_main_attachment_id" ref="hr_recruitment.hr_case_marketingjob0_cv"/>
|
||||
</record>
|
||||
<record id="hr_case_financejob0" model="hr.applicant">
|
||||
<field name="message_main_attachment_id" ref="hr_recruitment.hr_case_financejob0_cv"/>
|
||||
</record>
|
||||
<record id="hr_case_financejob1" model="hr.applicant">
|
||||
<field name="message_main_attachment_id" ref="hr_recruitment.hr_case_financejob1_cv"/>
|
||||
</record>
|
||||
<record id="hr_case_traineemca1" model="hr.applicant">
|
||||
<field name="message_main_attachment_id" ref="hr_recruitment.hr_case_traineemca1_cv"/>
|
||||
</record>
|
||||
<record id="hr_case_programmer" model="hr.applicant">
|
||||
<field name="message_main_attachment_id" ref="hr_recruitment.hr_case_programmer_cv"/>
|
||||
</record>
|
||||
<record id="hr_case_advertisement" model="hr.applicant">
|
||||
<field name="message_main_attachment_id" ref="hr_recruitment.hr_case_advertisement_cv"/>
|
||||
</record>
|
||||
|
||||
<record id="message_application_demo" model="mail.message">
|
||||
<field name="model">hr.applicant</field>
|
||||
<field name="res_id" ref="hr_case_advertisement"/>
|
||||
<field name="body">Please do refer to this application for sure.</field>
|
||||
<field name="message_type">comment</field>
|
||||
<field name="author_id" ref="base.res_partner_2"/>
|
||||
</record>
|
||||
<record id="msg_case18_aplicant" model="mail.message">
|
||||
<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="html">
|
||||
<p>Hello!<br />
|
||||
I will surely refer to this application as it is by your reference and <br />
|
||||
will try to conduct an interview within a very short time<br />
|
||||
Thanks,</p>
|
||||
</field>
|
||||
<field name="message_type">comment</field>
|
||||
<field name="subtype_id" ref="mail.mt_comment"/>
|
||||
<field name="author_id" ref="base.partner_demo"/>
|
||||
</record>
|
||||
<function model="mail.message" name="toggle_message_starred"
|
||||
eval="[ref('msg_case18_aplicant')]"
|
||||
/>
|
||||
<record id="msg_case_salesman0_aplicant" model="mail.message">
|
||||
<field name="subject">Refuse Application</field>
|
||||
<field name="model">hr.applicant</field>
|
||||
<field name="res_id" ref="hr_case_salesman0"/>
|
||||
<field name="body" type="html">
|
||||
<p>Hello,</p>
|
||||
<p>I have checked this application but it does not match with our requirements. We don't need to proceed further and we should refuse this application.</p>
|
||||
<p>Kind regards,</p>
|
||||
</field>
|
||||
<field name="message_type">comment</field>
|
||||
<field name="subtype_id" ref="mail.mt_comment"/>
|
||||
<field name="author_id" ref="base.partner_demo"/>
|
||||
</record>
|
||||
<record id="msg_case_dev0_aplicant" model="mail.message">
|
||||
<field name="subject">Refuse Application</field>
|
||||
<field name="model">hr.applicant</field>
|
||||
<field name="res_id" ref="hr_case_dev0"/>
|
||||
<field name="body" type="html">
|
||||
<p>Hello,</p>
|
||||
<p>This applicant has excellent skills and would greatly fit in the RD Team!</p>
|
||||
<p>Kind regards,</p>
|
||||
</field>
|
||||
<field name="message_type">comment</field>
|
||||
<field name="subtype_id" ref="mail.mt_comment"/>
|
||||
<field name="author_id" ref="base.partner_demo"/>
|
||||
</record>
|
||||
<record id="msg_case_fresher0_aplicant" model="mail.message">
|
||||
<field name="model">hr.applicant</field>
|
||||
<field name="res_id" ref="hr_case_fresher0"/>
|
||||
<field name="body" type="html">
|
||||
<p>Hello,</p>
|
||||
<p>We should move further for this application as early as possible.</p>
|
||||
<p>Kind regards,</p>
|
||||
</field>
|
||||
<field name="message_type">comment</field>
|
||||
<field name="subtype_id" ref="mail.mt_comment"/>
|
||||
<field name="author_id" ref="base.partner_demo"/>
|
||||
</record>
|
||||
<record id="msg_case_advertisement_aplicant" model="mail.message">
|
||||
<field name="model">hr.applicant</field>
|
||||
<field name="res_id" ref="hr_case_advertisement"/>
|
||||
<field name="body" type="html">
|
||||
<p>Hello,</p>
|
||||
<p>The first interview was good. Skilled and open minded applicant.</p>
|
||||
<p>I think we should consider hiring him.</p>
|
||||
<p>Kind regards,</p>
|
||||
</field>
|
||||
<field name="message_type">comment</field>
|
||||
<field name="subtype_id" ref="mail.mt_comment"/>
|
||||
<field name="author_id" ref="base.partner_demo"/>
|
||||
</record>
|
||||
<record id="msg_case_mkt1_1" model="mail.message">
|
||||
<field name="model">hr.applicant</field>
|
||||
<field name="res_id" ref="hr_case_mkt1"/>
|
||||
<field name="body" type="html">
|
||||
<p>Hello,</p>
|
||||
<p>The first interview was good. I will propose a second interview</p>
|
||||
<p>Kind regards,</p>
|
||||
</field>
|
||||
<field name="message_type">comment</field>
|
||||
<field name="subtype_id" ref="mail.mt_comment"/>
|
||||
<field name="author_id" ref="base.partner_demo"/>
|
||||
</record>
|
||||
<record id="msg_case_mkt1_2" model="mail.message">
|
||||
<field name="model">hr.applicant</field>
|
||||
<field name="res_id" ref="hr_case_mkt1"/>
|
||||
<field name="body" type="html">
|
||||
<p>Hello,</p>
|
||||
<p>After the second interview, I think we should consider hiring him.</p>
|
||||
<p>Kind regards,</p>
|
||||
</field>
|
||||
<field name="message_type">comment</field>
|
||||
<field name="subtype_id" ref="mail.mt_comment"/>
|
||||
<field name="author_id" ref="base.partner_admin"/>
|
||||
</record>
|
||||
<record id="mail_activity_0" model="mail.activity">
|
||||
<field name="res_id" ref="hr_recruitment.hr_case_dev0" />
|
||||
<field name="res_model_id" ref="model_hr_applicant"/>
|
||||
<field name="activity_type_id" ref="mail.mail_activity_data_email" />
|
||||
<field name="date_deadline" eval="time.strftime('%Y-%m-27 18:15:00')"/>
|
||||
<field name="summary">Send mail regarding our interview</field>
|
||||
<field name="create_uid" ref="base.user_admin"/>
|
||||
<field name="user_id" ref="base.user_admin"/>
|
||||
</record>
|
||||
<record id="mail_activity_1" model="mail.activity">
|
||||
<field name="res_id" ref="hr_recruitment.hr_case_dev1" />
|
||||
<field name="res_model_id" ref="model_hr_applicant"/>
|
||||
<field name="activity_type_id" ref="mail.mail_activity_data_email" />
|
||||
<field name="date_deadline" eval="time.strftime('%Y-%m-%d')"/>
|
||||
<field name="summary">Send mail for first interview</field>
|
||||
<field name="create_uid" ref="base.user_admin"/>
|
||||
<field name="user_id" ref="base.user_admin"/>
|
||||
</record>
|
||||
<record id="mail_activity_2" model="mail.activity">
|
||||
<field name="res_id" ref="hr_recruitment.hr_case_salesman0" />
|
||||
<field name="res_model_id" ref="model_hr_applicant"/>
|
||||
<field name="activity_type_id" ref="mail.mail_activity_data_email" />
|
||||
<field name="date_deadline" eval="time.strftime('%Y-%m-15 18:15:00')"/>
|
||||
<field name="summary">Send mail regarding our interview</field>
|
||||
<field name="create_uid" ref="base.user_admin"/>
|
||||
<field name="user_id" ref="base.user_admin"/>
|
||||
</record>
|
||||
<record id="mail_activity_3" model="mail.activity">
|
||||
<field name="res_id" ref="hr_recruitment.hr_case_traineemca0" />
|
||||
<field name="res_model_id" ref="model_hr_applicant"/>
|
||||
<field name="activity_type_id" ref="mail.mail_activity_data_call" />
|
||||
<field name="date_deadline" eval="time.strftime('%Y-%m-10 18:15:00')"/>
|
||||
<field name="summary">Call to define real needs about training</field>
|
||||
<field name="create_uid" ref="base.user_admin"/>
|
||||
<field name="user_id" ref="base.user_admin"/>
|
||||
</record>
|
||||
<record id="mail_activity_4" model="mail.activity">
|
||||
<field name="res_id" ref="hr_recruitment.hr_case_yrsexperienceinphp0" />
|
||||
<field name="res_model_id" ref="model_hr_applicant"/>
|
||||
<field name="activity_type_id" ref="mail.mail_activity_data_call" />
|
||||
<field name="date_deadline" eval="time.strftime('%Y-%m-24 18:15:00')"/>
|
||||
<field name="summary">Call to define real needs about training</field>
|
||||
<field name="create_uid" ref="base.user_admin"/>
|
||||
<field name="user_id" ref="base.user_admin"/>
|
||||
</record>
|
||||
<record id="mail_activity_5" model="mail.activity">
|
||||
<field name="res_id" ref="hr_recruitment.hr_case_advertisement" />
|
||||
<field name="res_model_id" ref="model_hr_applicant"/>
|
||||
<field name="activity_type_id" ref="mail.mail_activity_data_call" />
|
||||
<field name="date_deadline" eval="time.strftime('%Y-%m-26 18:15:00')"/>
|
||||
<field name="summary">Call to schedule a second interview</field>
|
||||
<field name="create_uid" ref="base.user_admin"/>
|
||||
<field name="user_id" ref="base.user_admin"/>
|
||||
</record>
|
||||
<record id="mail_activity_6" model="mail.activity">
|
||||
<field name="res_id" ref="hr_recruitment.hr_case_mkt1" />
|
||||
<field name="res_model_id" ref="model_hr_applicant"/>
|
||||
<field name="activity_type_id" ref="mail.mail_activity_data_call" />
|
||||
<field name="date_deadline" eval="time.strftime('%Y-%m-18 17:15:00')"/>
|
||||
<field name="summary">Call to propose a contract</field>
|
||||
<field name="create_uid" ref="base.user_admin"/>
|
||||
<field name="user_id" ref="base.user_admin"/>
|
||||
</record>
|
||||
</odoo>
|
BIN
data/hr_recruitment_demo_jones_cv.pdf
Normal file
BIN
data/hr_recruitment_demo_jones_cv.pdf
Normal file
Binary file not shown.
9
data/hr_recruitment_demo_jose_cv.txt
Normal file
9
data/hr_recruitment_demo_jose_cv.txt
Normal file
@ -0,0 +1,9 @@
|
||||
Profile
|
||||
|
||||
Name : Jose
|
||||
Address : 93, Press Avenue
|
||||
: Le Bourget du Lac, 73377,
|
||||
: France
|
||||
Qualification : MCA
|
||||
Email : Jose@gmail.com
|
||||
Mobile : 9968513587
|
BIN
data/hr_recruitment_demo_williams_cv.doc
Normal file
BIN
data/hr_recruitment_demo_williams_cv.doc
Normal file
Binary file not shown.
62
data/mail_message_subtype_data.xml
Normal file
62
data/mail_message_subtype_data.xml
Normal file
@ -0,0 +1,62 @@
|
||||
<?xml version="1.0"?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<!-- Applicant-related subtypes for messaging / Chatter -->
|
||||
<record id="mt_applicant_new" model="mail.message.subtype">
|
||||
<field name="name">New Applicant</field>
|
||||
<field name="res_model">hr.applicant</field>
|
||||
<field name="default" eval="False"/>
|
||||
<field name="hidden" eval="True"/>
|
||||
<field name="description">Applicant created</field>
|
||||
</record>
|
||||
<record id="mt_applicant_stage_changed" model="mail.message.subtype">
|
||||
<field name="name">Stage Changed</field>
|
||||
<field name="res_model">hr.applicant</field>
|
||||
<field name="default" eval="False"/>
|
||||
<field name="description">Stage changed</field>
|
||||
</record>
|
||||
<record id="mt_applicant_hired" model="mail.message.subtype">
|
||||
<field name="name">Applicant Hired</field>
|
||||
<field name="res_model">hr.applicant</field>
|
||||
<field name="default" eval="True"/>
|
||||
</record>
|
||||
|
||||
<!-- Job-related subtypes for messaging / Chatter -->
|
||||
<record id="mt_job_new" model="mail.message.subtype">
|
||||
<field name="name">Job Position created</field>
|
||||
<field name="res_model">hr.job</field>
|
||||
<field name="default" eval="False"/>
|
||||
<field name="hidden" eval="True"/>
|
||||
</record>
|
||||
<record id="mt_job_applicant_stage_changed" model="mail.message.subtype">
|
||||
<field name="name">Applicant Stage Changed</field>
|
||||
<field name="res_model">hr.job</field>
|
||||
<field name="default" eval="False"/>
|
||||
<field name="parent_id" ref="mt_applicant_stage_changed"/>
|
||||
<field name="relation_field">job_id</field>
|
||||
</record>
|
||||
<record id="mt_job_applicant_hired" model="mail.message.subtype">
|
||||
<field name="name">Applicant Hired</field>
|
||||
<field name="res_model">hr.job</field>
|
||||
<field name="default" eval="True"/>
|
||||
<field name="parent_id" ref="mt_applicant_hired"/>
|
||||
<field name="relation_field">job_id</field>
|
||||
</record>
|
||||
<record id="mt_job_applicant_new" model="mail.message.subtype">
|
||||
<field name="name">New Applicant</field>
|
||||
<field name="res_model">hr.job</field>
|
||||
<field name="default" eval="False"/>
|
||||
<field name="parent_id" ref="mt_applicant_new" />
|
||||
<field name="relation_field">job_id</field>
|
||||
</record>
|
||||
|
||||
<!-- Department-related (parent) subtypes for messaging / Chatter -->
|
||||
<record id="mt_department_new" model="mail.message.subtype">
|
||||
<field name="name">Job Position Created</field>
|
||||
<field name="res_model">hr.department</field>
|
||||
<field name="sequence" eval="1"/>
|
||||
<field name="parent_id" ref="mt_job_new"/>
|
||||
<field name="relation_field">department_id</field>
|
||||
</record>
|
||||
|
||||
</data></odoo>
|
271
data/mail_template_data.xml
Normal file
271
data/mail_template_data.xml
Normal file
@ -0,0 +1,271 @@
|
||||
<?xml version="1.0"?>
|
||||
<odoo><data noupdate="1">
|
||||
|
||||
<!-- Templates for interest / refusing applicants -->
|
||||
<record id="email_template_data_applicant_refuse" model="mail.template">
|
||||
<field name="name">Recruitment: Refuse</field>
|
||||
<field name="model_id" ref="hr_recruitment.model_hr_applicant"/>
|
||||
<field name="subject">Your Job Application: {{ object.job_id.name }}</field>
|
||||
<field name="email_to">{{ (not object.partner_id and object.email_from or '') }}</field>
|
||||
<field name="partner_to">{{ object.partner_id.id or '' }}</field>
|
||||
<field name="description">When you refuse an application, you can choose this template</field>
|
||||
<field name="body_html" type="html">
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="590" style="min-width: 590px; background-color: white; padding: 0px 8px 0px 8px; border-collapse:separate;">
|
||||
<tr>
|
||||
<td valign="top">
|
||||
<div style="font-size: 13px; margin: 0px; padding: 0px;">
|
||||
Hello,<br/><br/>
|
||||
Thank you for your interest in joining the
|
||||
<b><t t-out="object.company_id.name or ''">YourCompany</t></b> team. We
|
||||
wanted to let you know that, although your resume is
|
||||
competitive, our hiring team reviewed your application
|
||||
and <b>did not select it for further consideration</b>.
|
||||
<br/><br/>
|
||||
Please note that recruiting is hard, and we can make
|
||||
mistakes. Do not hesitate to reply to this email if you
|
||||
think we made a mistake, or if you want more information
|
||||
about our decision.
|
||||
<br/><br/>
|
||||
We will, however, keep your resume on record and get in
|
||||
touch with you about future opportunities that may be a
|
||||
better fit for your skills and experience.
|
||||
<br/><br/>
|
||||
We wish you all the best in your job search and hope we
|
||||
will have the chance to consider you for another role
|
||||
in the future.
|
||||
<br/><br/>
|
||||
Thank you,
|
||||
<div style="font-size: 11px; color: grey;">
|
||||
<t t-if="object.user_id">
|
||||
-- <br/>
|
||||
<strong t-out="object.user_id.name or ''">Mitchell Admin</strong><br/>
|
||||
Email: <t t-out="object.user_id.email or ''">admin@yourcompany.example.com</t><br/>
|
||||
Phone: <t t-out="object.user_id.phone or ''">+1 650-123-4567</t>
|
||||
</t>
|
||||
<t t-else="">
|
||||
-- <br/>
|
||||
<t t-out="object.company_id.name or ''">YourCompany</t><br/>
|
||||
The HR Team
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</field>
|
||||
<field name="auto_delete" eval="True"/>
|
||||
<field name="lang">{{ object.partner_id.lang or '' }}</field>
|
||||
</record>
|
||||
|
||||
<record id="email_template_data_applicant_interest" model="mail.template">
|
||||
<field name="name">Recruitment: Interest</field>
|
||||
<field name="model_id" ref="hr_recruitment.model_hr_applicant"/>
|
||||
<field name="subject">Your Job Application: {{ object.job_id.name }}</field>
|
||||
<field name="email_to">{{ (not object.partner_id and object.email_from or '') }}</field>
|
||||
<field name="partner_to">{{ object.partner_id.id or '' }}</field>
|
||||
<field name="description">Set this template to a recruitment stage to send it when applications reach that stage</field>
|
||||
<field name="body_html" type="html">
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="590" style="background-color: white; border-collapse: collapse; margin-left: 20px;">
|
||||
<tr>
|
||||
<td valign="top" style="padding: 0px 10px;">
|
||||
<div style="text-align: center">
|
||||
<h2>Congratulations!</h2>
|
||||
<div style="color:grey;">Your resume has been positively reviewed.</div>
|
||||
</div>
|
||||
<div style="font-size: 13px; margin: 0px; padding: 0px;">
|
||||
We just reviewed your resume, and it caught our
|
||||
attention. As we think you might be great for the
|
||||
position, your application has been short listed for a
|
||||
call or an interview.
|
||||
<br/><br/>
|
||||
<div t-if="'website_url' in object.job_id and object.job_id.website_url" style="padding: 16px 8px 16px 8px;">
|
||||
<a t-att-href="object.job_id.website_url"
|
||||
style="background-color: #875a7b; text-decoration: none; color: #fff; padding: 8px 16px 8px 16px; border-radius: 5px;">Job Description</a>
|
||||
</div>
|
||||
|
||||
<t t-if="object.user_id">
|
||||
You will soon be contacted by:<br/>
|
||||
<strong t-out="object.user_id.name or ''">Mitchell Admin</strong><br/>
|
||||
<span>Email: <t t-out="object.user_id.email or ''">admin@yourcompany.example.com</t></span><br/>
|
||||
<span>Phone: <t t-out="object.user_id.phone or ''">+1 650-123-4567</t></span>
|
||||
<br/><br/>
|
||||
</t>
|
||||
See you soon,
|
||||
<div style="font-size: 11px; color: grey;">
|
||||
-- <br/>
|
||||
The HR Team
|
||||
<t t-if="'website_url' in object.job_id and hasattr(object.job_id, 'website_url') and object.job_id.website_url">
|
||||
Discover <a href="/jobs" style="text-decoration:none;color:#717188;">all our jobs</a>.<br/>
|
||||
</t>
|
||||
</div>
|
||||
|
||||
<hr width="97%" style="background-color: rgb(204,204,204); border: medium none; clear: both; display: block; font-size: 0px; min-height: 1px; line-height: 0; margin: 16px 0px 16px 0px;"/>
|
||||
<h3 style="color:#9A6C8E;"><strong>What is the next step?</strong></h3>
|
||||
We usually <strong>answer applications within a few days</strong>.
|
||||
<br/><br/>
|
||||
The next step is either a call or a meeting in our offices.
|
||||
<br/>
|
||||
Feel free to <strong>contact us if you want a faster
|
||||
feedback</strong> or if you don't get news from us
|
||||
quickly enough (just reply to this email).
|
||||
<br/>
|
||||
|
||||
<hr width="97%" style="background-color: rgb(204,204,204); border: medium none; clear: both; display: block; font-size: 0px; min-height: 1px; line-height: 0; margin: 17px 0px 16px 0px;"/>
|
||||
<t t-set="location" t-value="''"/>
|
||||
<t t-if="object.job_id.address_id.name">
|
||||
<strong t-out="object.job_id.address_id.name or ''">Teksa SpA</strong><br/>
|
||||
</t>
|
||||
<t t-if="object.job_id.address_id.street">
|
||||
<t t-out="object.job_id.address_id.street or ''">Puerto Madero 9710</t><br/>
|
||||
<t t-set="location" t-value="object.job_id.address_id.street"/>
|
||||
</t>
|
||||
<t t-if="object.job_id.address_id.street2">
|
||||
<t t-out="object.job_id.address_id.street2 or ''">Of A15, Santiago (RM)</t><br/>
|
||||
<t t-set="location" t-value="'%s, %s' % (location, object.job_id.address_id.street2)"/>
|
||||
</t>
|
||||
<t t-if="object.job_id.address_id.city">
|
||||
<t t-out="object.job_id.address_id.city or ''">Pudahuel</t>,
|
||||
<t t-set="location" t-value="'%s, %s' % (location, object.job_id.address_id.city)"/>
|
||||
</t>
|
||||
<t t-if="object.job_id.address_id.state_id.name">
|
||||
<t t-out="object.job_id.address_id.state_id.name or ''">C1</t>,
|
||||
<t t-set="location" t-value="'%s, %s' % (location, object.job_id.address_id.state_id.name)"/>
|
||||
</t>
|
||||
<t t-if="object.job_id.address_id.zip">
|
||||
<t t-out="object.job_id.address_id.zip or ''">98450</t>
|
||||
<t t-set="location" t-value="'%s, %s' % (location, object.job_id.address_id.zip)"/>
|
||||
</t>
|
||||
<br/>
|
||||
<t t-if="object.job_id.address_id.country_id.name">
|
||||
<t t-out="object.job_id.address_id.country_id.name or ''">Argentina</t><br/>
|
||||
<t t-set="location" t-value="'%s, %s' % (location, object.job_id.address_id.country_id.name)"/>
|
||||
</t>
|
||||
<br/>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table></field>
|
||||
<field name="auto_delete" eval="True"/>
|
||||
<field name="lang">{{ object.partner_id.lang or '' }}</field>
|
||||
</record>
|
||||
|
||||
<record id="email_template_data_applicant_congratulations" model="mail.template">
|
||||
<field name="name">Recruitment: Application Acknowledgement</field>
|
||||
<field name="model_id" ref="hr_recruitment.model_hr_applicant"/>
|
||||
<field name="subject">Your Job Application: {{ object.job_id.name }}</field>
|
||||
<field name="email_to">{{ (not object.partner_id and object.email_from or '') }}</field>
|
||||
<field name="partner_to">{{ object.partner_id.id or '' }}</field>
|
||||
<field name="description">Confirmation email sent to all new job applications</field>
|
||||
<field name="body_html" type="html">
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="590" style="background-color: white; border-collapse: collapse; margin-left: 20px;">
|
||||
<tr>
|
||||
<td valign="top" style="padding: 0px 10px;">
|
||||
<div style="font-size: 13px; margin: 0px; padding: 0px;">
|
||||
Hello,
|
||||
<br/><br/>
|
||||
We confirm we successfully received your application for the job
|
||||
"<a t-att-href="hasattr(object.job_id, 'website_url') and object.job_id.website_url or ''" style="color:#9A6C8E;"><strong t-out="object.job_id.name or ''">Experienced Developer</strong></a>" at <strong t-out="object.company_id.name or ''">YourCompany</strong>.
|
||||
<br/><br/>
|
||||
We will come back to you shortly.
|
||||
|
||||
<div t-if="'website_url' in object.job_id and object.job_id.website_url" style="padding: 16px 8px 16px 8px;">
|
||||
<a t-att-href="object.job_id.website_url"
|
||||
style="background-color: #875a7b; text-decoration: none; color: #fff; padding: 8px 16px 8px 16px; border-radius: 5px;">Job Description</a>
|
||||
</div>
|
||||
|
||||
<hr width="97%" style="background-color: rgb(204,204,204); border: medium none; clear: both; display: block; font-size: 0px; min-height: 1px; line-height: 0; margin: 16px 0px 16px 0px;"/>
|
||||
<t t-if="object.user_id">
|
||||
<h3 style="color:#9A6C8E;"><strong>Your Contact:</strong></h3>
|
||||
<p>
|
||||
<strong t-out="object.user_id.name or ''">Mitchell Admin</strong><br/>
|
||||
<span>Email: <t t-out="object.user_id.email or ''">admin@yourcompany.example.com</t></span><br/>
|
||||
<span>Phone: <t t-out="object.user_id.phone or ''">+1 650-123-4567</t></span>
|
||||
</p>
|
||||
<hr width="97%" style="background-color: rgb(204,204,204); border: medium none; clear: both; display: block; font-size: 0px; min-height: 1px; line-height: 0; margin: 16px 0px 16px 0px;"/>
|
||||
</t>
|
||||
|
||||
<h3 style="color:#9A6C8E;"><strong>What is the next step?</strong></h3>
|
||||
We usually <strong>answer applications within a few days.</strong><br/><br/>
|
||||
Feel free to <strong>contact us if you want a faster
|
||||
feedback</strong> or if you don't get news from us
|
||||
quickly enough (just reply to this email).
|
||||
|
||||
<hr width="97%" style="background-color: rgb(204,204,204); border: medium none; clear: both; display: block; font-size: 0px; min-height: 1px; line-height: 0; margin: 17px 0px 16px 0px;"/>
|
||||
<t t-set="location" t-value="''" />
|
||||
<t t-if="object.job_id.address_id.name">
|
||||
<strong t-out="object.job_id.address_id.name or ''">Teksa SpA</strong><br/>
|
||||
</t>
|
||||
<t t-if="object.job_id.address_id.street">
|
||||
<t t-out="object.job_id.address_id.street or ''">Puerto Madero 9710</t><br/>
|
||||
<t t-set="location" t-value="object.job_id.address_id.street"/>
|
||||
</t>
|
||||
<t t-if="object.job_id.address_id.street2">
|
||||
<t t-out="object.job_id.address_id.street2 or ''">Of A15, Santiago (RM)</t><br/>
|
||||
<t t-set="location" t-value="'%s, %s' % (location, object.job_id.address_id.street2)"/>
|
||||
</t>
|
||||
<t t-if="object.job_id.address_id.city">
|
||||
<t t-out="object.job_id.address_id.city or ''">Pudahuel</t>,
|
||||
<t t-set="location" t-value="'%s, %s' % (location, object.job_id.address_id.city)"/>
|
||||
</t>
|
||||
<t t-if="object.job_id.address_id.state_id.name">
|
||||
<t t-out="object.job_id.address_id.state_id.name or ''">C1</t>,
|
||||
<t t-set="location" t-value="'%s, %s' % (location, object.job_id.address_id.state_id.name)"/>
|
||||
</t>
|
||||
<t t-if="object.job_id.address_id.zip">
|
||||
<t t-out="object.job_id.address_id.zip or ''">98450</t>
|
||||
<t t-set="location" t-value="'%s, %s' % (location, object.job_id.address_id.zip)"/>
|
||||
</t>
|
||||
<br/>
|
||||
<t t-if="object.job_id.address_id.country_id.name">
|
||||
<t t-out="object.job_id.address_id.country_id.name or ''">Argentina</t><br/>
|
||||
<t t-set="location" t-value="'%s, %s' % (location, object.job_id.address_id.country_id.name)"/>
|
||||
</t>
|
||||
<br/>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table></field>
|
||||
<field name="auto_delete" eval="True"/>
|
||||
<field name="lang">{{ object.partner_id.lang or '' }}</field>
|
||||
</record>
|
||||
|
||||
<record id="email_template_data_applicant_not_interested" model="mail.template">
|
||||
<field name="name">Recruitment: Not interested anymore</field>
|
||||
<field name="model_id" ref="hr_recruitment.model_hr_applicant"/>
|
||||
<field name="subject">Your Job Application: {{ object.job_id.name }}</field>
|
||||
<field name="email_to">{{ (not object.partner_id and object.email_from or '') }}</field>
|
||||
<field name="partner_to">{{ object.partner_id.id or '' }}</field>
|
||||
<field name="description">When you refuse an application, you can choose this template</field>
|
||||
<field name="body_html" type="html">
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="590" style="min-width: 590px; background-color: white; padding: 0px 8px 0px 8px; border-collapse:separate;">
|
||||
<tr>
|
||||
<td valign="top">
|
||||
<div style="font-size: 13px; margin: 0px; padding: 0px;">
|
||||
Dear,<br/><br/>
|
||||
We would like to thank you for your interest and your time.<br/>
|
||||
We wish you all the best in your future endeavors.
|
||||
<br/><br/>
|
||||
Best<br/>
|
||||
<div style="font-size: 11px; color: grey;">
|
||||
<t t-if="object.user_id">
|
||||
-- <br/>
|
||||
<strong t-out="object.user_id.name or ''">Marc Demo</strong><br/>
|
||||
Email: <t t-out="object.user_id.email or ''">mark.brown23@example.com</t><br/>
|
||||
Phone: <t t-out="object.user_id.phone or ''">+1 650-123-4567</t>
|
||||
</t>
|
||||
<t t-else="">
|
||||
-- <br/>
|
||||
<t t-out="object.company_id.name or ''">YourCompany</t><br/>
|
||||
The HR Team<br/>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</field>
|
||||
<field name="auto_delete" eval="True"/>
|
||||
<field name="lang">{{ object.partner_id.lang or '' }}</field>
|
||||
</record>
|
||||
|
||||
</data></odoo>
|
14
data/mail_templates.xml
Normal file
14
data/mail_templates.xml
Normal file
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo><data noupdate="1">
|
||||
|
||||
<template id="applicant_hired_template">
|
||||
Employee created: <a href="#" t-att-data-oe-id="applicant.emp_id.id" data-oe-model="hr.employee"><t t-esc="applicant.emp_id.name"/></a>
|
||||
</template>
|
||||
|
||||
<template id="mail_notification_light_without_background" inherit_id="mail.mail_notification_light">
|
||||
<xpath expr="//t//table[@role='presentation']" position="attributes">
|
||||
<attribute name="style" add="background-color: white;" separator=" "/>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
</data></odoo>
|
2701
i18n/af.po
Normal file
2701
i18n/af.po
Normal file
File diff suppressed because it is too large
Load Diff
2697
i18n/am.po
Normal file
2697
i18n/am.po
Normal file
File diff suppressed because it is too large
Load Diff
3305
i18n/ar.po
Normal file
3305
i18n/ar.po
Normal file
File diff suppressed because it is too large
Load Diff
2715
i18n/az.po
Normal file
2715
i18n/az.po
Normal file
File diff suppressed because it is too large
Load Diff
3017
i18n/bg.po
Normal file
3017
i18n/bg.po
Normal file
File diff suppressed because it is too large
Load Diff
2702
i18n/bs.po
Normal file
2702
i18n/bs.po
Normal file
File diff suppressed because it is too large
Load Diff
3113
i18n/ca.po
Normal file
3113
i18n/ca.po
Normal file
File diff suppressed because it is too large
Load Diff
3056
i18n/cs.po
Normal file
3056
i18n/cs.po
Normal file
File diff suppressed because it is too large
Load Diff
3123
i18n/da.po
Normal file
3123
i18n/da.po
Normal file
File diff suppressed because it is too large
Load Diff
3329
i18n/de.po
Normal file
3329
i18n/de.po
Normal file
File diff suppressed because it is too large
Load Diff
2712
i18n/el.po
Normal file
2712
i18n/el.po
Normal file
File diff suppressed because it is too large
Load Diff
2700
i18n/en_GB.po
Normal file
2700
i18n/en_GB.po
Normal file
File diff suppressed because it is too large
Load Diff
3341
i18n/es.po
Normal file
3341
i18n/es.po
Normal file
File diff suppressed because it is too large
Load Diff
3346
i18n/es_419.po
Normal file
3346
i18n/es_419.po
Normal file
File diff suppressed because it is too large
Load Diff
2700
i18n/es_BO.po
Normal file
2700
i18n/es_BO.po
Normal file
File diff suppressed because it is too large
Load Diff
2700
i18n/es_CL.po
Normal file
2700
i18n/es_CL.po
Normal file
File diff suppressed because it is too large
Load Diff
2700
i18n/es_CO.po
Normal file
2700
i18n/es_CO.po
Normal file
File diff suppressed because it is too large
Load Diff
2700
i18n/es_CR.po
Normal file
2700
i18n/es_CR.po
Normal file
File diff suppressed because it is too large
Load Diff
2700
i18n/es_DO.po
Normal file
2700
i18n/es_DO.po
Normal file
File diff suppressed because it is too large
Load Diff
2700
i18n/es_EC.po
Normal file
2700
i18n/es_EC.po
Normal file
File diff suppressed because it is too large
Load Diff
2700
i18n/es_PE.po
Normal file
2700
i18n/es_PE.po
Normal file
File diff suppressed because it is too large
Load Diff
2700
i18n/es_PY.po
Normal file
2700
i18n/es_PY.po
Normal file
File diff suppressed because it is too large
Load Diff
2700
i18n/es_VE.po
Normal file
2700
i18n/es_VE.po
Normal file
File diff suppressed because it is too large
Load Diff
3293
i18n/et.po
Normal file
3293
i18n/et.po
Normal file
File diff suppressed because it is too large
Load Diff
2700
i18n/eu.po
Normal file
2700
i18n/eu.po
Normal file
File diff suppressed because it is too large
Load Diff
3030
i18n/fa.po
Normal file
3030
i18n/fa.po
Normal file
File diff suppressed because it is too large
Load Diff
3335
i18n/fi.po
Normal file
3335
i18n/fi.po
Normal file
File diff suppressed because it is too large
Load Diff
2700
i18n/fo.po
Normal file
2700
i18n/fo.po
Normal file
File diff suppressed because it is too large
Load Diff
3319
i18n/fr.po
Normal file
3319
i18n/fr.po
Normal file
File diff suppressed because it is too large
Load Diff
2699
i18n/fr_BE.po
Normal file
2699
i18n/fr_BE.po
Normal file
File diff suppressed because it is too large
Load Diff
2700
i18n/fr_CA.po
Normal file
2700
i18n/fr_CA.po
Normal file
File diff suppressed because it is too large
Load Diff
2700
i18n/gl.po
Normal file
2700
i18n/gl.po
Normal file
File diff suppressed because it is too large
Load Diff
2705
i18n/gu.po
Normal file
2705
i18n/gu.po
Normal file
File diff suppressed because it is too large
Load Diff
3031
i18n/he.po
Normal file
3031
i18n/he.po
Normal file
File diff suppressed because it is too large
Load Diff
2699
i18n/hi.po
Normal file
2699
i18n/hi.po
Normal file
File diff suppressed because it is too large
Load Diff
2730
i18n/hr.po
Normal file
2730
i18n/hr.po
Normal file
File diff suppressed because it is too large
Load Diff
2955
i18n/hr_recruitment.pot
Normal file
2955
i18n/hr_recruitment.pot
Normal file
File diff suppressed because it is too large
Load Diff
3023
i18n/hu.po
Normal file
3023
i18n/hu.po
Normal file
File diff suppressed because it is too large
Load Diff
2955
i18n/hy.po
Normal file
2955
i18n/hy.po
Normal file
File diff suppressed because it is too large
Load Diff
3306
i18n/id.po
Normal file
3306
i18n/id.po
Normal file
File diff suppressed because it is too large
Load Diff
2965
i18n/is.po
Normal file
2965
i18n/is.po
Normal file
File diff suppressed because it is too large
Load Diff
3313
i18n/it.po
Normal file
3313
i18n/it.po
Normal file
File diff suppressed because it is too large
Load Diff
3220
i18n/ja.po
Normal file
3220
i18n/ja.po
Normal file
File diff suppressed because it is too large
Load Diff
2700
i18n/ka.po
Normal file
2700
i18n/ka.po
Normal file
File diff suppressed because it is too large
Load Diff
2700
i18n/kab.po
Normal file
2700
i18n/kab.po
Normal file
File diff suppressed because it is too large
Load Diff
2703
i18n/km.po
Normal file
2703
i18n/km.po
Normal file
File diff suppressed because it is too large
Load Diff
3233
i18n/ko.po
Normal file
3233
i18n/ko.po
Normal file
File diff suppressed because it is too large
Load Diff
2701
i18n/lb.po
Normal file
2701
i18n/lb.po
Normal file
File diff suppressed because it is too large
Load Diff
2700
i18n/lo.po
Normal file
2700
i18n/lo.po
Normal file
File diff suppressed because it is too large
Load Diff
3036
i18n/lt.po
Normal file
3036
i18n/lt.po
Normal file
File diff suppressed because it is too large
Load Diff
3021
i18n/lv.po
Normal file
3021
i18n/lv.po
Normal file
File diff suppressed because it is too large
Load Diff
2700
i18n/mk.po
Normal file
2700
i18n/mk.po
Normal file
File diff suppressed because it is too large
Load Diff
2735
i18n/mn.po
Normal file
2735
i18n/mn.po
Normal file
File diff suppressed because it is too large
Load Diff
2711
i18n/nb.po
Normal file
2711
i18n/nb.po
Normal file
File diff suppressed because it is too large
Load Diff
2697
i18n/ne.po
Normal file
2697
i18n/ne.po
Normal file
File diff suppressed because it is too large
Load Diff
3316
i18n/nl.po
Normal file
3316
i18n/nl.po
Normal file
File diff suppressed because it is too large
Load Diff
3297
i18n/pl.po
Normal file
3297
i18n/pl.po
Normal file
File diff suppressed because it is too large
Load Diff
3013
i18n/pt.po
Normal file
3013
i18n/pt.po
Normal file
File diff suppressed because it is too large
Load Diff
3323
i18n/pt_BR.po
Normal file
3323
i18n/pt_BR.po
Normal file
File diff suppressed because it is too large
Load Diff
2715
i18n/ro.po
Normal file
2715
i18n/ro.po
Normal file
File diff suppressed because it is too large
Load Diff
3328
i18n/ru.po
Normal file
3328
i18n/ru.po
Normal file
File diff suppressed because it is too large
Load Diff
3046
i18n/sk.po
Normal file
3046
i18n/sk.po
Normal file
File diff suppressed because it is too large
Load Diff
3032
i18n/sl.po
Normal file
3032
i18n/sl.po
Normal file
File diff suppressed because it is too large
Load Diff
2700
i18n/sq.po
Normal file
2700
i18n/sq.po
Normal file
File diff suppressed because it is too large
Load Diff
3289
i18n/sr.po
Normal file
3289
i18n/sr.po
Normal file
File diff suppressed because it is too large
Load Diff
2703
i18n/sr@latin.po
Normal file
2703
i18n/sr@latin.po
Normal file
File diff suppressed because it is too large
Load Diff
3336
i18n/sv.po
Normal file
3336
i18n/sv.po
Normal file
File diff suppressed because it is too large
Load Diff
3278
i18n/th.po
Normal file
3278
i18n/th.po
Normal file
File diff suppressed because it is too large
Load Diff
3083
i18n/tr.po
Normal file
3083
i18n/tr.po
Normal file
File diff suppressed because it is too large
Load Diff
3314
i18n/uk.po
Normal file
3314
i18n/uk.po
Normal file
File diff suppressed because it is too large
Load Diff
3310
i18n/vi.po
Normal file
3310
i18n/vi.po
Normal file
File diff suppressed because it is too large
Load Diff
3210
i18n/zh_CN.po
Normal file
3210
i18n/zh_CN.po
Normal file
File diff suppressed because it is too large
Load Diff
3212
i18n/zh_TW.po
Normal file
3212
i18n/zh_TW.po
Normal file
File diff suppressed because it is too large
Load Diff
16
models/__init__.py
Normal file
16
models/__init__.py
Normal file
@ -0,0 +1,16 @@
|
||||
from . import hr_department
|
||||
from . import hr_applicant
|
||||
from . import hr_applicant_category
|
||||
from . import hr_applicant_refuse_reason
|
||||
from . import hr_recruitment_degree
|
||||
from . import hr_recruitment_source
|
||||
from . import hr_recruitment_stage
|
||||
from . import hr_employee
|
||||
from . import hr_job
|
||||
from . import res_config_settings
|
||||
from . import calendar
|
||||
from . import digest
|
||||
from . import utm_campaign
|
||||
from . import utm_source
|
||||
from . import res_users
|
||||
from . import ir_ui_menu
|
62
models/calendar.py
Normal file
62
models/calendar.py
Normal file
@ -0,0 +1,62 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models
|
||||
from odoo.exceptions import AccessError
|
||||
|
||||
|
||||
class CalendarEvent(models.Model):
|
||||
""" Model for Calendar Event """
|
||||
_inherit = 'calendar.event'
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
events = super(CalendarEvent, self).create(vals_list)
|
||||
try:
|
||||
self.env['hr.applicant'].check_access_rights('read')
|
||||
except AccessError:
|
||||
return events
|
||||
|
||||
if "default_applicant_id" in self.env.context:
|
||||
applicant_attachments = self.env['hr.applicant'].browse(self.env.context['default_applicant_id']).attachment_ids
|
||||
for event in events:
|
||||
self.env['ir.attachment'].create([{
|
||||
'name': att.name,
|
||||
'type': 'binary',
|
||||
'datas': att.datas,
|
||||
'res_model': event._name,
|
||||
'res_id': event.id
|
||||
} for att in applicant_attachments])
|
||||
return events
|
||||
|
||||
@api.model
|
||||
def default_get(self, fields):
|
||||
if self.env.context.get('default_applicant_id'):
|
||||
self = self.with_context(
|
||||
default_res_model='hr.applicant', #res_model seems to be lost without this
|
||||
default_res_model_id=self.env.ref('hr_recruitment.model_hr_applicant').id,
|
||||
default_res_id=self.env.context.get('default_applicant_id'),
|
||||
default_partner_ids=self.env.context.get('default_partner_ids'),
|
||||
default_name=self.env.context.get('default_name')
|
||||
)
|
||||
|
||||
defaults = super(CalendarEvent, self).default_get(fields)
|
||||
|
||||
# sync res_model / res_id to opportunity id (aka creating meeting from lead chatter)
|
||||
if 'applicant_id' not in defaults:
|
||||
res_model = defaults.get('res_model', False) or self.env.context.get('default_res_model')
|
||||
res_model_id = defaults.get('res_model_id', False) or self.env.context.get('default_res_model_id')
|
||||
if (res_model and res_model == 'hr.applicant') or (res_model_id and self.env['ir.model'].sudo().browse(res_model_id).model == 'hr.applicant'):
|
||||
defaults['applicant_id'] = defaults.get('res_id', False) or self.env.context.get('default_res_id', False)
|
||||
|
||||
return defaults
|
||||
|
||||
def _compute_is_highlighted(self):
|
||||
super(CalendarEvent, self)._compute_is_highlighted()
|
||||
applicant_id = self.env.context.get('active_id')
|
||||
if self.env.context.get('active_model') == 'hr.applicant' and applicant_id:
|
||||
for event in self:
|
||||
if event.applicant_id.id == applicant_id:
|
||||
event.is_highlighted = True
|
||||
|
||||
applicant_id = fields.Many2one('hr.applicant', string="Applicant", index='btree_not_null', ondelete='set null')
|
26
models/digest.py
Normal file
26
models/digest.py
Normal file
@ -0,0 +1,26 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import fields, models, _
|
||||
from odoo.exceptions import AccessError
|
||||
|
||||
|
||||
class Digest(models.Model):
|
||||
_inherit = 'digest.digest'
|
||||
|
||||
kpi_hr_recruitment_new_colleagues = fields.Boolean('New Employees')
|
||||
kpi_hr_recruitment_new_colleagues_value = fields.Integer(compute='_compute_kpi_hr_recruitment_new_colleagues_value')
|
||||
|
||||
def _compute_kpi_hr_recruitment_new_colleagues_value(self):
|
||||
if not self.env.user.has_group('hr_recruitment.group_hr_recruitment_user'):
|
||||
raise AccessError(_("Do not have access, skip this data for user's digest email"))
|
||||
|
||||
self._calculate_company_based_kpi(
|
||||
'hr.employee',
|
||||
'kpi_hr_recruitment_new_colleagues_value',
|
||||
)
|
||||
|
||||
def _compute_kpis_actions(self, company, user):
|
||||
res = super(Digest, self)._compute_kpis_actions(company, user)
|
||||
res['kpi_hr_recruitment_new_colleagues'] = 'hr.open_view_employee_list_my&menu_id=%s' % self.env.ref('hr.menu_hr_root').id
|
||||
return res
|
734
models/hr_applicant.py
Normal file
734
models/hr_applicant.py
Normal file
@ -0,0 +1,734 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from markupsafe import Markup
|
||||
|
||||
from odoo import api, fields, models, tools, SUPERUSER_ID
|
||||
from odoo.exceptions import AccessError, UserError
|
||||
from odoo.osv import expression
|
||||
from odoo.tools import Query
|
||||
from odoo.tools.translate import _
|
||||
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
AVAILABLE_PRIORITIES = [
|
||||
('0', 'Normal'),
|
||||
('1', 'Good'),
|
||||
('2', 'Very Good'),
|
||||
('3', 'Excellent')
|
||||
]
|
||||
|
||||
|
||||
class Applicant(models.Model):
|
||||
_name = "hr.applicant"
|
||||
_description = "Applicant"
|
||||
_order = "priority desc, id desc"
|
||||
_inherit = ['mail.thread.cc',
|
||||
'mail.thread.main.attachment',
|
||||
'mail.thread.blacklist',
|
||||
'mail.thread.phone',
|
||||
'mail.activity.mixin',
|
||||
'utm.mixin']
|
||||
_mailing_enabled = True
|
||||
_primary_email = 'email_from'
|
||||
|
||||
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, compute='_compute_partner_phone_email',
|
||||
inverse='_inverse_partner_email', store=True, index='trigram')
|
||||
email_normalized = fields.Char(index='trigram') # inherited via mail.thread.blacklist
|
||||
probability = fields.Float("Probability")
|
||||
partner_id = fields.Many2one('res.partner', "Contact", copy=False, index='btree_not_null')
|
||||
create_date = fields.Datetime("Applied on", readonly=True)
|
||||
stage_id = fields.Many2one('hr.recruitment.stage', 'Stage', ondelete='restrict', tracking=True,
|
||||
compute='_compute_stage', store=True, readonly=False,
|
||||
domain="['|', ('job_ids', '=', False), ('job_ids', '=', job_id)]",
|
||||
copy=False, index=True,
|
||||
group_expand='_read_group_stage_ids')
|
||||
last_stage_id = fields.Many2one('hr.recruitment.stage', "Last Stage",
|
||||
help="Stage of the applicant before being in the current stage. Used for lost cases analysis.")
|
||||
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', 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)
|
||||
date_last_stage_update = fields.Datetime("Last Stage Update", index=True, default=fields.Datetime.now)
|
||||
priority = fields.Selection(AVAILABLE_PRIORITIES, "Evaluation", default='0')
|
||||
job_id = fields.Many2one('hr.job', "Applied Job", domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]", tracking=True, index=True)
|
||||
salary_proposed_extra = fields.Char("Proposed Salary Extra", help="Salary Proposed by the Organisation, extra advantages", tracking=True, groups="hr_recruitment.group_hr_recruitment_user")
|
||||
salary_expected_extra = fields.Char("Expected Salary Extra", help="Salary Expected by Applicant, extra advantages", tracking=True, groups="hr_recruitment.group_hr_recruitment_user")
|
||||
salary_proposed = fields.Float("Proposed Salary", group_operator="avg", help="Salary Proposed by the Organisation", tracking=True, groups="hr_recruitment.group_hr_recruitment_user")
|
||||
salary_expected = fields.Float("Expected Salary", group_operator="avg", help="Salary Expected by Applicant", tracking=True, groups="hr_recruitment.group_hr_recruitment_user")
|
||||
availability = fields.Date("Availability", help="The date at which the applicant will be available to start working", tracking=True)
|
||||
partner_name = fields.Char("Applicant's Name")
|
||||
partner_phone = fields.Char("Phone", size=32, compute='_compute_partner_phone_email',
|
||||
store=True, readonly=False, index='btree_not_null', inverse='_inverse_partner_email')
|
||||
partner_phone_sanitized = fields.Char(string='Sanitized Phone Number', compute='_compute_partner_phone_sanitized', store=True, index='btree_not_null')
|
||||
partner_mobile = fields.Char("Mobile", size=32, compute='_compute_partner_phone_email',
|
||||
store=True, readonly=False, index='btree_not_null', inverse='_inverse_partner_email')
|
||||
partner_mobile_sanitized = fields.Char(string='Sanitized Mobile Number', compute='_compute_partner_mobile_sanitized', store=True, index='btree_not_null')
|
||||
type_id = fields.Many2one('hr.recruitment.degree', "Degree")
|
||||
department_id = fields.Many2one(
|
||||
'hr.department', "Department", compute='_compute_department', store=True, readonly=False,
|
||||
domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]", tracking=True)
|
||||
day_open = fields.Float(compute='_compute_day', string="Days to Open", compute_sudo=True)
|
||||
day_close = fields.Float(compute='_compute_day', string="Days to Close", compute_sudo=True)
|
||||
delay_close = fields.Float(compute="_compute_delay", string='Delay to Close', readonly=True, group_operator="avg", help="Number of days to close", store=True)
|
||||
color = fields.Integer("Color Index", default=0)
|
||||
emp_id = fields.Many2one('hr.employee', string="Employee", help="Employee linked to the applicant.", copy=False)
|
||||
emp_is_active = fields.Boolean(string="Employee Active", related='emp_id.active')
|
||||
user_email = fields.Char(related='user_id.email', string="User Email", readonly=True)
|
||||
attachment_number = fields.Integer(compute='_get_attachment_number', string="Number of Attachments")
|
||||
employee_name = fields.Char(related='emp_id.name', string="Employee Name", readonly=False, tracking=False)
|
||||
attachment_ids = fields.One2many('ir.attachment', 'res_id', domain=[('res_model', '=', 'hr.applicant')], string='Attachments')
|
||||
kanban_state = fields.Selection([
|
||||
('normal', 'Grey'),
|
||||
('done', 'Green'),
|
||||
('blocked', 'Red')], string='Kanban State',
|
||||
copy=False, default='normal', required=True)
|
||||
legend_blocked = fields.Char(related='stage_id.legend_blocked', string='Kanban Blocked')
|
||||
legend_done = fields.Char(related='stage_id.legend_done', string='Kanban Valid')
|
||||
legend_normal = fields.Char(related='stage_id.legend_normal', string='Kanban Ongoing')
|
||||
application_count = fields.Integer(compute='_compute_application_count', help='Applications with the same email or phone or mobile')
|
||||
refuse_reason_id = fields.Many2one('hr.applicant.refuse.reason', string='Refuse Reason', tracking=True)
|
||||
meeting_ids = fields.One2many('calendar.event', 'applicant_id', 'Meetings')
|
||||
meeting_display_text = fields.Char(compute='_compute_meeting_display')
|
||||
meeting_display_date = fields.Date(compute='_compute_meeting_display')
|
||||
# UTMs - enforcing the fact that we want to 'set null' when relation is unlinked
|
||||
campaign_id = fields.Many2one(ondelete='set null')
|
||||
medium_id = fields.Many2one(ondelete='set null')
|
||||
source_id = fields.Many2one(ondelete='set null')
|
||||
interviewer_ids = fields.Many2many('res.users', 'hr_applicant_res_users_interviewers_rel',
|
||||
string='Interviewers', index=True, tracking=True,
|
||||
domain="[('share', '=', False), ('company_ids', 'in', company_id)]")
|
||||
linkedin_profile = fields.Char('LinkedIn Profile')
|
||||
application_status = fields.Selection([
|
||||
('ongoing', 'Ongoing'),
|
||||
('hired', 'Hired'),
|
||||
('refused', 'Refused'),
|
||||
('archived', 'Archived'),
|
||||
], compute="_compute_application_status")
|
||||
applicant_properties = fields.Properties('Properties', definition='job_id.applicant_properties_definition', copy=True)
|
||||
|
||||
def init(self):
|
||||
self.env.cr.execute("""
|
||||
CREATE INDEX IF NOT EXISTS hr_applicant_job_id_stage_id_idx
|
||||
ON hr_applicant(job_id, stage_id)
|
||||
WHERE active IS TRUE
|
||||
""")
|
||||
self.env.cr.execute("""
|
||||
CREATE INDEX IF NOT EXISTS hr_applicant_email_partner_phone_mobile
|
||||
ON hr_applicant(email_normalized, partner_mobile_sanitized, partner_phone_sanitized);
|
||||
""")
|
||||
|
||||
@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:
|
||||
if applicant.date_open:
|
||||
date_create = applicant.create_date
|
||||
date_open = applicant.date_open
|
||||
applicant.day_open = (date_open - date_create).total_seconds() / (24.0 * 3600)
|
||||
else:
|
||||
applicant.day_open = False
|
||||
if applicant.date_closed:
|
||||
date_create = applicant.create_date
|
||||
date_closed = applicant.date_closed
|
||||
applicant.day_close = (date_closed - date_create).total_seconds() / (24.0 * 3600)
|
||||
else:
|
||||
applicant.day_close = False
|
||||
|
||||
@api.depends('day_open', 'day_close')
|
||||
def _compute_delay(self):
|
||||
for applicant in self:
|
||||
if applicant.date_open and applicant.day_close:
|
||||
applicant.delay_close = applicant.day_close - applicant.day_open
|
||||
else:
|
||||
applicant.delay_close = False
|
||||
|
||||
@api.depends('email_from', 'partner_mobile_sanitized', 'partner_phone_sanitized')
|
||||
def _compute_application_count(self):
|
||||
"""
|
||||
The field application_count is only used on the form view.
|
||||
Thus, using ORM rather then querying, should not make much
|
||||
difference in terms of performance, while being more readable and secure.
|
||||
"""
|
||||
if not any(self._ids):
|
||||
for applicant in self:
|
||||
domain = applicant._get_similar_applicants_domain()
|
||||
if domain:
|
||||
applicant.application_count = max(0, self.env["hr.applicant"].with_context(active_test=False).search_count(domain) - 1)
|
||||
else:
|
||||
applicant.application_count = 0
|
||||
return
|
||||
self.flush_recordset(['email_normalized', 'partner_phone_sanitized', 'partner_mobile_sanitized'])
|
||||
self.env.cr.execute("""
|
||||
SELECT
|
||||
id,
|
||||
(
|
||||
SELECT COUNT(*)
|
||||
FROM hr_applicant AS sub
|
||||
WHERE a.id != sub.id
|
||||
AND ((a.email_normalized <> '' AND sub.email_normalized = a.email_normalized)
|
||||
OR (a.partner_mobile_sanitized <> '' AND a.partner_mobile_sanitized = sub.partner_mobile_sanitized)
|
||||
OR (a.partner_mobile_sanitized <> '' AND a.partner_mobile_sanitized = sub.partner_phone_sanitized)
|
||||
OR (a.partner_phone_sanitized <> '' AND a.partner_phone_sanitized = sub.partner_mobile_sanitized)
|
||||
OR (a.partner_phone_sanitized <> '' AND a.partner_phone_sanitized = sub.partner_phone_sanitized))
|
||||
) AS similar_applicants
|
||||
FROM hr_applicant AS a
|
||||
WHERE id IN %(ids)s
|
||||
""", {'ids': tuple(self._origin.ids)})
|
||||
query_results = self.env.cr.dictfetchall()
|
||||
mapped_data = {result['id']: result['similar_applicants'] for result in query_results}
|
||||
for applicant in self:
|
||||
applicant.application_count = mapped_data.get(applicant.id, 0)
|
||||
|
||||
def _get_similar_applicants_domain(self):
|
||||
"""
|
||||
This method returns a domain for the applicants whitch match with the
|
||||
current applicant according to email_from, partner_phone or partner_mobile.
|
||||
Thus, search on the domain will return the current applicant as well if any of
|
||||
the following fields are filled.
|
||||
"""
|
||||
self.ensure_one()
|
||||
if not self:
|
||||
return None
|
||||
domain = []
|
||||
if self.email_normalized:
|
||||
domain = expression.OR([domain, [('email_normalized', '=', self.email_normalized)]])
|
||||
if self.partner_phone_sanitized:
|
||||
domain = expression.OR([domain, ['|', ('partner_phone_sanitized', '=', self.partner_phone_sanitized), ('partner_mobile_sanitized', '=', self.partner_phone_sanitized)]])
|
||||
if self.partner_mobile_sanitized:
|
||||
domain = expression.OR([domain, ['|', ('partner_mobile_sanitized', '=', self.partner_mobile_sanitized), ('partner_phone_sanitized', '=', self.partner_mobile_sanitized)]])
|
||||
return domain if domain else None
|
||||
|
||||
@api.depends_context('lang')
|
||||
@api.depends('meeting_ids', 'meeting_ids.start')
|
||||
def _compute_meeting_display(self):
|
||||
applicant_with_meetings = self.filtered('meeting_ids')
|
||||
(self - applicant_with_meetings).update({
|
||||
'meeting_display_text': _('No Meeting'),
|
||||
'meeting_display_date': ''
|
||||
})
|
||||
today = fields.Date.today()
|
||||
for applicant in applicant_with_meetings:
|
||||
count = len(applicant.meeting_ids)
|
||||
dates = applicant.meeting_ids.mapped('start')
|
||||
min_date, max_date = min(dates).date(), max(dates).date()
|
||||
if min_date >= today:
|
||||
applicant.meeting_display_date = min_date
|
||||
else:
|
||||
applicant.meeting_display_date = max_date
|
||||
if count == 1:
|
||||
applicant.meeting_display_text = _('1 Meeting')
|
||||
elif applicant.meeting_display_date >= today:
|
||||
applicant.meeting_display_text = _('Next Meeting')
|
||||
else:
|
||||
applicant.meeting_display_text = _('Last Meeting')
|
||||
|
||||
@api.depends('refuse_reason_id', 'date_closed')
|
||||
def _compute_application_status(self):
|
||||
for applicant in self:
|
||||
if applicant.refuse_reason_id:
|
||||
applicant.application_status = 'refused'
|
||||
elif not applicant.active:
|
||||
applicant.application_status = 'archived'
|
||||
elif applicant.date_closed:
|
||||
applicant.application_status = 'hired'
|
||||
else:
|
||||
applicant.application_status = 'ongoing'
|
||||
|
||||
def _get_attachment_number(self):
|
||||
read_group_res = self.env['ir.attachment']._read_group(
|
||||
[('res_model', '=', 'hr.applicant'), ('res_id', 'in', self.ids)],
|
||||
['res_id'], ['__count'])
|
||||
attach_data = dict(read_group_res)
|
||||
for record in self:
|
||||
record.attachment_number = attach_data.get(record.id, 0)
|
||||
|
||||
@api.model
|
||||
def _read_group_stage_ids(self, stages, domain, order):
|
||||
# retrieve job_id from the context and write the domain: ids + contextual columns (job or default)
|
||||
job_id = self._context.get('default_job_id')
|
||||
search_domain = [('job_ids', '=', False)]
|
||||
if job_id:
|
||||
search_domain = ['|', ('job_ids', '=', job_id)] + search_domain
|
||||
if stages:
|
||||
search_domain = ['|', ('id', 'in', stages.ids)] + search_domain
|
||||
|
||||
stage_ids = stages._search(search_domain, order=order, access_rights_uid=SUPERUSER_ID)
|
||||
return stages.browse(stage_ids)
|
||||
|
||||
@api.depends('job_id', 'department_id')
|
||||
def _compute_company(self):
|
||||
for applicant in self:
|
||||
company_id = False
|
||||
if applicant.department_id:
|
||||
company_id = applicant.department_id.company_id.id
|
||||
if not company_id and applicant.job_id:
|
||||
company_id = applicant.job_id.company_id.id
|
||||
applicant.company_id = company_id or self.env.company.id
|
||||
|
||||
@api.depends('job_id')
|
||||
def _compute_department(self):
|
||||
for applicant in self:
|
||||
applicant.department_id = applicant.job_id.department_id.id
|
||||
|
||||
@api.depends('job_id')
|
||||
def _compute_stage(self):
|
||||
for applicant in self:
|
||||
if applicant.job_id:
|
||||
if not applicant.stage_id:
|
||||
stage_ids = self.env['hr.recruitment.stage'].search([
|
||||
'|',
|
||||
('job_ids', '=', False),
|
||||
('job_ids', '=', applicant.job_id.id),
|
||||
('fold', '=', False)
|
||||
], order='sequence asc', limit=1).ids
|
||||
applicant.stage_id = stage_ids[0] if stage_ids else False
|
||||
else:
|
||||
applicant.stage_id = False
|
||||
|
||||
@api.depends('job_id')
|
||||
def _compute_user(self):
|
||||
for applicant in self:
|
||||
applicant.user_id = applicant.job_id.user_id.id
|
||||
|
||||
@api.depends('partner_id')
|
||||
def _compute_partner_phone_email(self):
|
||||
for applicant in self:
|
||||
if not applicant.partner_id:
|
||||
continue
|
||||
applicant.email_from = applicant.partner_id.email
|
||||
if not applicant.partner_phone:
|
||||
applicant.partner_phone = applicant.partner_id.phone
|
||||
if not applicant.partner_mobile:
|
||||
applicant.partner_mobile = applicant.partner_id.mobile
|
||||
|
||||
def _inverse_partner_email(self):
|
||||
for applicant in self:
|
||||
if not applicant.email_from:
|
||||
continue
|
||||
if not applicant.partner_id:
|
||||
if not applicant.partner_name:
|
||||
raise UserError(_('You must define a Contact Name for this applicant.'))
|
||||
applicant.partner_id = self.env['res.partner'].create({
|
||||
'is_company': False,
|
||||
'name': applicant.partner_name,
|
||||
'email': applicant.email_from,
|
||||
'mobile': applicant.partner_mobile,
|
||||
'phone': applicant.partner_phone,
|
||||
})
|
||||
else:
|
||||
applicant.partner_id.email = applicant.email_from
|
||||
applicant.partner_id.mobile = applicant.partner_mobile
|
||||
applicant.partner_id.phone = applicant.partner_phone
|
||||
|
||||
@api.depends('partner_phone')
|
||||
def _compute_partner_phone_sanitized(self):
|
||||
for applicant in self:
|
||||
applicant.partner_phone_sanitized = applicant._phone_format(fname='partner_phone') or applicant.partner_phone
|
||||
|
||||
@api.depends('partner_mobile')
|
||||
def _compute_partner_mobile_sanitized(self):
|
||||
for applicant in self:
|
||||
applicant.partner_mobile_sanitized = applicant._phone_format(fname='partner_mobile') or applicant.partner_mobile
|
||||
|
||||
def _phone_get_number_fields(self):
|
||||
""" This method returns the fields to use to find the number to use to
|
||||
send an SMS on a record. """
|
||||
return ['partner_mobile', 'partner_phone']
|
||||
|
||||
@api.depends('stage_id.hired_stage')
|
||||
def _compute_date_closed(self):
|
||||
for applicant in self:
|
||||
if applicant.stage_id and applicant.stage_id.hired_stage and not applicant.date_closed:
|
||||
applicant.date_closed = fields.datetime.now()
|
||||
if not applicant.stage_id.hired_stage:
|
||||
applicant.date_closed = False
|
||||
|
||||
def _check_interviewer_access(self):
|
||||
if self.user_has_groups('hr_recruitment.group_hr_recruitment_interviewer') and not self.user_has_groups('hr_recruitment.group_hr_recruitment_user'):
|
||||
raise AccessError(_('You are not allowed to perform this action.'))
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
for vals in vals_list:
|
||||
if vals.get('user_id'):
|
||||
vals['date_open'] = fields.Datetime.now()
|
||||
if vals.get('email_from'):
|
||||
vals['email_from'] = vals['email_from'].strip()
|
||||
applicants = super().create(vals_list)
|
||||
applicants.sudo().interviewer_ids._create_recruitment_interviewers()
|
||||
# Record creation through calendar, creates the calendar event directly, it will also create the activity.
|
||||
if 'default_activity_date_deadline' in self.env.context:
|
||||
deadline = fields.Datetime.to_datetime(self.env.context.get('default_activity_date_deadline'))
|
||||
category = self.env.ref('hr_recruitment.categ_meet_interview')
|
||||
for applicant in applicants:
|
||||
partners = applicant.partner_id | applicant.user_id.partner_id | applicant.department_id.manager_id.user_id.partner_id
|
||||
self.env['calendar.event'].sudo().with_context(default_applicant_id=applicant.id).create({
|
||||
'applicant_id': applicant.id,
|
||||
'partner_ids': [(6, 0, partners.ids)],
|
||||
'user_id': self.env.uid,
|
||||
'name': applicant.name,
|
||||
'categ_ids': [category.id],
|
||||
'start': deadline,
|
||||
'stop': deadline + relativedelta(minutes=30),
|
||||
})
|
||||
return applicants
|
||||
|
||||
def write(self, vals):
|
||||
# user_id change: update date_open
|
||||
if vals.get('user_id'):
|
||||
vals['date_open'] = fields.Datetime.now()
|
||||
if vals.get('email_from'):
|
||||
vals['email_from'] = vals['email_from'].strip()
|
||||
if self._email_is_blacklisted(vals['email_from']):
|
||||
del vals['email_from']
|
||||
old_interviewers = self.interviewer_ids
|
||||
# stage_id: track last stage before update
|
||||
if 'stage_id' in vals:
|
||||
vals['date_last_stage_update'] = fields.Datetime.now()
|
||||
if 'kanban_state' not in vals:
|
||||
vals['kanban_state'] = 'normal'
|
||||
for applicant in self:
|
||||
vals['last_stage_id'] = applicant.stage_id.id
|
||||
res = super(Applicant, self).write(vals)
|
||||
else:
|
||||
res = super(Applicant, self).write(vals)
|
||||
if 'interviewer_ids' in vals:
|
||||
interviewers_to_clean = old_interviewers - self.interviewer_ids
|
||||
interviewers_to_clean._remove_recruitment_interviewers()
|
||||
self.sudo().interviewer_ids._create_recruitment_interviewers()
|
||||
if vals.get('emp_id'):
|
||||
self._update_employee_from_applicant()
|
||||
return res
|
||||
|
||||
def _email_is_blacklisted(self, mail):
|
||||
normalized_mail = tools.email_normalize(mail)
|
||||
return normalized_mail in [m.strip() for m in self.env['ir.config_parameter'].sudo().get_param('hr_recruitment.blacklisted_emails', '').split(',')]
|
||||
|
||||
def get_empty_list_help(self, help_message):
|
||||
if 'active_id' in self.env.context and self.env.context.get('active_model') == 'hr.job':
|
||||
hr_job = self.env['hr.job'].browse(self.env.context['active_id'])
|
||||
elif self.env.context.get('default_job_id'):
|
||||
hr_job = self.env['hr.job'].browse(self.env.context['default_job_id'])
|
||||
else:
|
||||
hr_job = self.env['hr.job']
|
||||
|
||||
nocontent_body = Markup("""
|
||||
<p class="o_view_nocontent_smiling_face">%(help_title)s</p>
|
||||
<p>%(para_1)s<br/>%(para_2)s</p>""") % {
|
||||
'help_title': _("No application found. Let's create one !"),
|
||||
'para_1': _('People can also apply by email to save time.'),
|
||||
'para_2': _("You can search into attachment's content, like resumes, with the searchbar."),
|
||||
}
|
||||
|
||||
if hr_job.alias_email:
|
||||
nocontent_body += Markup('<p class="o_copy_paste_email oe_view_nocontent_alias">%(helper_email)s <a href="mailto:%(email)s">%(email)s</a></p>') % {
|
||||
'helper_email': _("Create new applications by sending an email to"),
|
||||
'email': hr_job.alias_email,
|
||||
}
|
||||
|
||||
return super().get_empty_list_help(nocontent_body)
|
||||
|
||||
@api.model
|
||||
def get_view(self, view_id=None, view_type='form', **options):
|
||||
if view_type == 'form' and self.user_has_groups('hr_recruitment.group_hr_recruitment_interviewer')\
|
||||
and not self.user_has_groups('hr_recruitment.group_hr_recruitment_user'):
|
||||
view_id = self.env.ref('hr_recruitment.hr_applicant_view_form_interviewer').id
|
||||
return super().get_view(view_id, view_type, **options)
|
||||
|
||||
def action_makeMeeting(self):
|
||||
""" This opens Meeting's calendar view to schedule meeting on current applicant
|
||||
@return: Dictionary value for created Meeting view
|
||||
"""
|
||||
self.ensure_one()
|
||||
if not self.partner_id:
|
||||
if not self.partner_name:
|
||||
raise UserError(_('You must define a Contact Name for this applicant.'))
|
||||
self.partner_id = self.env['res.partner'].create({
|
||||
'is_company': False,
|
||||
'name': self.partner_name,
|
||||
'email': self.email_from,
|
||||
})
|
||||
|
||||
partners = self.partner_id | self.department_id.manager_id.user_id.partner_id
|
||||
if self.user_has_groups('hr_recruitment.group_hr_recruitment_interviewer') and not self.user_has_groups('hr_recruitment.group_hr_recruitment_user'):
|
||||
partners |= self.env.user.partner_id
|
||||
else:
|
||||
partners |= self.user_id.partner_id
|
||||
|
||||
category = self.env.ref('hr_recruitment.categ_meet_interview')
|
||||
res = self.env['ir.actions.act_window']._for_xml_id('calendar.action_calendar_event')
|
||||
# As we are redirected from the hr.applicant, calendar checks rules on "hr.applicant",
|
||||
# in order to decide whether to allow creation of a meeting.
|
||||
# As interviewer does not have create right on the hr.applicant, in order to allow them
|
||||
# to create a meeting for an applicant, we pass 'create': True to the context.
|
||||
res['context'] = {
|
||||
'create': True,
|
||||
'default_applicant_id': self.id,
|
||||
'default_partner_ids': partners.ids,
|
||||
'default_user_id': self.env.uid,
|
||||
'default_name': self.name,
|
||||
'default_categ_ids': category and [category.id] or False,
|
||||
'attachment_ids': self.attachment_ids.ids
|
||||
}
|
||||
return res
|
||||
|
||||
def action_open_attachments(self):
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_model': 'ir.attachment',
|
||||
'name': _('Documents'),
|
||||
'context': {
|
||||
'default_res_model': 'hr.applicant',
|
||||
'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):
|
||||
self.ensure_one()
|
||||
other_applicants = self.env['hr.applicant']
|
||||
domain = self._get_similar_applicants_domain()
|
||||
if domain:
|
||||
other_applicants = self.env['hr.applicant'].with_context(active_test=False).search(domain)
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': _('Job Applications'),
|
||||
'res_model': self._name,
|
||||
'view_mode': 'tree,kanban,form,pivot,graph,calendar,activity',
|
||||
'domain': [('id', 'in', other_applicants.ids)],
|
||||
'context': {
|
||||
'active_test': False,
|
||||
'search_default_stage': 1,
|
||||
},
|
||||
}
|
||||
|
||||
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]
|
||||
# When applcant is unarchived, they are put back to the default stage automatically. In this case,
|
||||
# don't post automated message related to the stage change.
|
||||
if 'stage_id' in changes and applicant.exists() and applicant.stage_id.template_id and not applicant._context.get('just_unarchived'):
|
||||
res['stage_id'] = (applicant.stage_id.template_id, {
|
||||
'auto_delete_keep_log': False,
|
||||
'subtype_id': self.env['ir.model.data']._xmlid_to_res_id('mail.mt_note'),
|
||||
'email_layout_xmlid': 'hr_recruitment.mail_notification_light_without_background'
|
||||
})
|
||||
return res
|
||||
|
||||
def _creation_subtype(self):
|
||||
return self.env.ref('hr_recruitment.mt_applicant_new')
|
||||
|
||||
def _track_subtype(self, init_values):
|
||||
record = self[0]
|
||||
if 'stage_id' in init_values and record.stage_id:
|
||||
return self.env.ref('hr_recruitment.mt_applicant_stage_changed')
|
||||
return super(Applicant, self)._track_subtype(init_values)
|
||||
|
||||
def _notify_get_reply_to(self, default=None):
|
||||
""" Override to set alias of applicants to their job definition if any. """
|
||||
aliases = self.mapped('job_id')._notify_get_reply_to(default=default)
|
||||
res = {app.id: aliases.get(app.job_id.id) for app in self}
|
||||
leftover = self.filtered(lambda rec: not rec.job_id)
|
||||
if leftover:
|
||||
res.update(super(Applicant, leftover)._notify_get_reply_to(default=default))
|
||||
return res
|
||||
|
||||
def _message_get_suggested_recipients(self):
|
||||
recipients = super(Applicant, self)._message_get_suggested_recipients()
|
||||
for applicant in self:
|
||||
if applicant.partner_id:
|
||||
applicant._message_add_suggested_recipient(recipients, partner=applicant.partner_id.sudo(), reason=_('Contact'))
|
||||
elif applicant.email_from:
|
||||
email_from = tools.email_normalize(applicant.email_from)
|
||||
if email_from and applicant.partner_name:
|
||||
email_from = tools.formataddr((applicant.partner_name, email_from))
|
||||
applicant._message_add_suggested_recipient(recipients, email=email_from, reason=_('Contact Email'))
|
||||
return recipients
|
||||
|
||||
@api.depends('partner_name')
|
||||
@api.depends_context('show_partner_name')
|
||||
def _compute_display_name(self):
|
||||
if not self.env.context.get('show_partner_name'):
|
||||
return super()._compute_display_name()
|
||||
for applicant in self:
|
||||
applicant.display_name = applicant.partner_name or applicant.name
|
||||
|
||||
@api.model
|
||||
def message_new(self, msg, custom_values=None):
|
||||
""" Overrides mail_thread message_new that is called by the mailgateway
|
||||
through message_process.
|
||||
This override updates the document according to the email.
|
||||
"""
|
||||
# Remove default author when going through the mail gateway. Indeed, we
|
||||
# do not want to explicitly set user_id to False; however we do not
|
||||
# want the gateway user to be responsible if no other responsible is
|
||||
# found.
|
||||
self = self.with_context(default_user_id=False)
|
||||
stage = False
|
||||
if custom_values and 'job_id' in custom_values:
|
||||
stage = self.env['hr.job'].browse(custom_values['job_id'])._get_first_stage()
|
||||
partner_name, email_from_normalized = tools.parse_contact_from_email(msg.get('from'))
|
||||
defaults = {
|
||||
'name': msg.get('subject') or _("No Subject"),
|
||||
'partner_name': partner_name or email_from_normalized,
|
||||
}
|
||||
if msg.get('from') and not self._email_is_blacklisted(msg.get('from')):
|
||||
defaults['email_from'] = msg.get('from')
|
||||
defaults['partner_id'] = msg.get('author_id', False)
|
||||
if msg.get('email_from') and self._email_is_blacklisted(msg.get('email_from')):
|
||||
del msg['email_from']
|
||||
if msg.get('priority'):
|
||||
defaults['priority'] = msg.get('priority')
|
||||
if stage and stage.id:
|
||||
defaults['stage_id'] = stage.id
|
||||
if custom_values:
|
||||
defaults.update(custom_values)
|
||||
return super(Applicant, self).message_new(msg, custom_values=defaults)
|
||||
|
||||
def _message_post_after_hook(self, message, msg_vals):
|
||||
if self.email_from and not self.partner_id:
|
||||
# we consider that posting a message with a specified recipient (not a follower, a specific one)
|
||||
# on a document without customer means that it was created through the chatter using
|
||||
# suggested recipients. This heuristic allows to avoid ugly hacks in JS.
|
||||
email_normalized = tools.email_normalize(self.email_from)
|
||||
new_partner = message.partner_ids.filtered(
|
||||
lambda partner: partner.email == self.email_from or (email_normalized and partner.email_normalized == email_normalized)
|
||||
)
|
||||
if new_partner:
|
||||
if new_partner[0].create_date.date() == fields.Date.today():
|
||||
new_partner[0].write({
|
||||
'name': self.partner_name or self.email_from,
|
||||
})
|
||||
if new_partner[0].email_normalized:
|
||||
email_domain = ('email_from', 'in', [new_partner[0].email, new_partner[0].email_normalized])
|
||||
else:
|
||||
email_domain = ('email_from', '=', new_partner[0].email)
|
||||
self.search([
|
||||
('partner_id', '=', False), email_domain, ('stage_id.fold', '=', False)
|
||||
]).write({'partner_id': new_partner[0].id})
|
||||
return super(Applicant, self)._message_post_after_hook(message, msg_vals)
|
||||
|
||||
def create_employee_from_applicant(self):
|
||||
""" Create an employee from applicant """
|
||||
self.ensure_one()
|
||||
self._check_interviewer_access()
|
||||
|
||||
if not self.partner_id:
|
||||
if not self.partner_name:
|
||||
raise UserError(_('Please provide an applicant name.'))
|
||||
self.partner_id = self.env['res.partner'].create({
|
||||
'is_company': False,
|
||||
'name': self.partner_name,
|
||||
'email': self.email_from,
|
||||
})
|
||||
|
||||
action = self.env['ir.actions.act_window']._for_xml_id('hr.open_view_employee_list')
|
||||
employee = self.env['hr.employee'].create(self._get_employee_create_vals())
|
||||
action['res_id'] = employee.id
|
||||
return action
|
||||
|
||||
def _get_employee_create_vals(self):
|
||||
self.ensure_one()
|
||||
address_id = self.partner_id.address_get(['contact'])['contact']
|
||||
address_sudo = self.env['res.partner'].sudo().browse(address_id)
|
||||
return {
|
||||
'name': self.partner_name or self.partner_id.display_name,
|
||||
'work_contact_id': self.partner_id.id,
|
||||
'job_id': self.job_id.id,
|
||||
'job_title': self.job_id.name,
|
||||
'private_street': address_sudo.street,
|
||||
'private_street2': address_sudo.street2,
|
||||
'private_city': address_sudo.city,
|
||||
'private_state_id': address_sudo.state_id.id,
|
||||
'private_zip': address_sudo.zip,
|
||||
'private_country_id': address_sudo.country_id.id,
|
||||
'private_phone': address_sudo.phone,
|
||||
'private_email': address_sudo.email,
|
||||
'lang': address_sudo.lang,
|
||||
'department_id': self.department_id.id,
|
||||
'address_id': self.company_id.partner_id.id,
|
||||
'work_email': self.department_id.company_id.email or self.email_from, # To have a valid email address by default
|
||||
'work_phone': self.department_id.company_id.phone,
|
||||
'applicant_id': self.ids,
|
||||
'private_phone': self.partner_phone or self.partner_mobile
|
||||
}
|
||||
|
||||
def _update_employee_from_applicant(self):
|
||||
# This method is to be overriden
|
||||
return
|
||||
|
||||
def archive_applicant(self):
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': _('Refuse Reason'),
|
||||
'res_model': 'applicant.get.refuse.reason',
|
||||
'view_mode': 'form',
|
||||
'target': 'new',
|
||||
'context': {'default_applicant_ids': self.ids, 'active_test': False},
|
||||
'views': [[False, 'form']]
|
||||
}
|
||||
|
||||
def reset_applicant(self):
|
||||
""" Reinsert the applicant into the recruitment pipe in the first stage"""
|
||||
default_stage = dict()
|
||||
for job_id in self.mapped('job_id'):
|
||||
default_stage[job_id.id] = self.env['hr.recruitment.stage'].search(
|
||||
[
|
||||
'|',
|
||||
('job_ids', '=', False),
|
||||
('job_ids', '=', job_id.id),
|
||||
('fold', '=', False)
|
||||
], order='sequence asc', limit=1).id
|
||||
for applicant in self:
|
||||
applicant.write(
|
||||
{'stage_id': applicant.job_id.id and default_stage[applicant.job_id.id],
|
||||
'refuse_reason_id': False})
|
||||
|
||||
def toggle_active(self):
|
||||
self = self.with_context(just_unarchived=True)
|
||||
res = super(Applicant, self).toggle_active()
|
||||
active_applicants = self.filtered(lambda applicant: applicant.active)
|
||||
if active_applicants:
|
||||
active_applicants.reset_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,
|
||||
}
|
||||
}
|
20
models/hr_applicant_category.py
Normal file
20
models/hr_applicant_category.py
Normal file
@ -0,0 +1,20 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
from random import randint
|
||||
|
||||
class ApplicantCategory(models.Model):
|
||||
_name = "hr.applicant.category"
|
||||
_description = "Category of applicant"
|
||||
|
||||
def _get_default_color(self):
|
||||
return randint(1, 11)
|
||||
|
||||
name = fields.Char("Tag Name", required=True)
|
||||
color = fields.Integer(string='Color Index', default=_get_default_color)
|
||||
|
||||
_sql_constraints = [
|
||||
('name_uniq', 'unique (name)', "Tag name already exists!"),
|
||||
]
|
15
models/hr_applicant_refuse_reason.py
Normal file
15
models/hr_applicant_refuse_reason.py
Normal file
@ -0,0 +1,15 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class ApplicantRefuseReason(models.Model):
|
||||
_name = "hr.applicant.refuse.reason"
|
||||
_description = 'Refuse Reason of Applicant'
|
||||
_order = 'sequence'
|
||||
|
||||
sequence = fields.Integer(copy=False, default=10)
|
||||
name = fields.Char('Description', required=True, translate=True)
|
||||
template_id = fields.Many2one('mail.template', string='Email Template', domain="[('model', '=', 'hr.applicant')]")
|
||||
active = fields.Boolean('Active', default=True)
|
35
models/hr_department.py
Normal file
35
models/hr_department.py
Normal file
@ -0,0 +1,35 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class HrDepartment(models.Model):
|
||||
_inherit = 'hr.department'
|
||||
|
||||
new_applicant_count = fields.Integer(
|
||||
compute='_compute_new_applicant_count', string='New Applicant', compute_sudo=True)
|
||||
new_hired_employee = fields.Integer(
|
||||
compute='_compute_recruitment_stats', string='New Hired Employee')
|
||||
expected_employee = fields.Integer(
|
||||
compute='_compute_recruitment_stats', string='Expected Employee')
|
||||
|
||||
def _compute_new_applicant_count(self):
|
||||
if self.env.user.has_group('hr_recruitment.group_hr_recruitment_interviewer'):
|
||||
applicant_data = self.env['hr.applicant']._read_group(
|
||||
[('department_id', 'in', self.ids), ('stage_id.sequence', '<=', '1')],
|
||||
['department_id'], ['__count'])
|
||||
result = {department.id: count for department, count in applicant_data}
|
||||
for department in self:
|
||||
department.new_applicant_count = result.get(department.id, 0)
|
||||
else:
|
||||
self.new_applicant_count = 0
|
||||
|
||||
def _compute_recruitment_stats(self):
|
||||
job_data = self.env['hr.job']._read_group(
|
||||
[('department_id', 'in', self.ids)],
|
||||
['department_id'], ['no_of_hired_employee:sum', 'no_of_recruitment:sum'])
|
||||
new_emp = {department.id: nb_employee for department, nb_employee, __ in job_data}
|
||||
expected_emp = {department.id: nb_recruitment for department, __, nb_recruitment in job_data}
|
||||
for department in self:
|
||||
department.new_hired_employee = new_emp.get(department.id, 0)
|
||||
department.expected_employee = expected_emp.get(department.id, 0)
|
21
models/hr_employee.py
Normal file
21
models/hr_employee.py
Normal file
@ -0,0 +1,21 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models
|
||||
from datetime import timedelta
|
||||
|
||||
|
||||
class HrEmployee(models.Model):
|
||||
_inherit = "hr.employee"
|
||||
|
||||
applicant_id = fields.One2many('hr.applicant', 'emp_id', 'Applicant')
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
employees = super().create(vals_list)
|
||||
for employee in employees:
|
||||
if employee.applicant_id:
|
||||
employee.applicant_id._message_log_with_view(
|
||||
'hr_recruitment.applicant_hired_template',
|
||||
render_values={'applicant': employee.applicant_id}
|
||||
)
|
||||
return employees
|
319
models/hr_job.py
Normal file
319
models/hr_job.py
Normal file
@ -0,0 +1,319 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import ast
|
||||
from collections import defaultdict
|
||||
|
||||
from odoo import api, fields, models, SUPERUSER_ID, _
|
||||
|
||||
|
||||
class Job(models.Model):
|
||||
_name = "hr.job"
|
||||
_inherit = ["mail.alias.mixin", "hr.job"]
|
||||
_order = "sequence, name asc"
|
||||
|
||||
@api.model
|
||||
def _default_address_id(self):
|
||||
last_used_address = self.env['hr.job'].search([('company_id', 'in', self.env.companies.ids)], order='id desc', limit=1)
|
||||
if last_used_address:
|
||||
return last_used_address.address_id
|
||||
else:
|
||||
return self.env.company.partner_id
|
||||
|
||||
def _address_id_domain(self):
|
||||
return ['|', '&', '&', ('type', '!=', 'contact'), ('type', '!=', 'private'),
|
||||
('id', 'in', self.sudo().env.companies.partner_id.child_ids.ids),
|
||||
('id', 'in', self.sudo().env.companies.partner_id.ids)]
|
||||
|
||||
def _get_default_favorite_user_ids(self):
|
||||
return [(6, 0, [self.env.uid])]
|
||||
|
||||
address_id = fields.Many2one(
|
||||
'res.partner', "Job Location", default=_default_address_id,
|
||||
domain=lambda self: self._address_id_domain(),
|
||||
help="Select the location where the applicant will work. Addresses listed here are defined on the company's contact information.")
|
||||
application_ids = fields.One2many('hr.applicant', 'job_id', "Job Applications")
|
||||
application_count = fields.Integer(compute='_compute_application_count', string="Application Count")
|
||||
all_application_count = fields.Integer(compute='_compute_all_application_count', string="All Application Count")
|
||||
new_application_count = fields.Integer(
|
||||
compute='_compute_new_application_count', string="New Application",
|
||||
help="Number of applications that are new in the flow (typically at first step of the flow)")
|
||||
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", domain="[('share', '=', False), ('company_ids', 'in', company_id)]", tracking=True, help="The Recruiter will be the default value for all Applicants Recruiter's field in this job position. The Recruiter is automatically added to all meetings with the Applicant.")
|
||||
document_ids = fields.One2many('ir.attachment', compute='_compute_document_ids', string="Documents", readonly=True)
|
||||
documents_count = fields.Integer(compute='_compute_document_ids', string="Document Count")
|
||||
alias_id = fields.Many2one(help="Email alias for this job position. New emails will automatically create new applicants for this job position.")
|
||||
color = fields.Integer("Color Index")
|
||||
is_favorite = fields.Boolean(compute='_compute_is_favorite', inverse='_inverse_is_favorite')
|
||||
favorite_user_ids = fields.Many2many('res.users', 'job_favorite_user_rel', 'job_id', 'user_id', default=_get_default_favorite_user_ids)
|
||||
interviewer_ids = fields.Many2many('res.users', string='Interviewers', domain="[('share', '=', False), ('company_ids', 'in', company_id)]", help="The Interviewers set on the job position can see all Applicants in it. They have access to the information, the attachments, the meeting management and they can refuse him. You don't need to have Recruitment rights to be set as an interviewer.")
|
||||
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')
|
||||
|
||||
applicant_properties_definition = fields.PropertiesDefinition('Applicant Properties')
|
||||
|
||||
@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_ids')
|
||||
def _compute_extended_interviewer_ids(self):
|
||||
# Use SUPERUSER_ID as the search_read is protected in hr_referral
|
||||
results_raw = self.env['hr.applicant'].with_user(SUPERUSER_ID).search_read([
|
||||
('job_id', 'in', self.ids),
|
||||
('interviewer_ids', '!=', False)
|
||||
], ['interviewer_ids', 'job_id'])
|
||||
interviewers_by_job = defaultdict(set)
|
||||
for result_raw in results_raw:
|
||||
interviewers_by_job[result_raw['job_id'][0]] |= set(result_raw['interviewer_ids'])
|
||||
for job in self:
|
||||
job.extended_interviewer_ids = [(6, 0, list(interviewers_by_job[job.id]))]
|
||||
|
||||
def _compute_is_favorite(self):
|
||||
for job in self:
|
||||
job.is_favorite = self.env.user in job.favorite_user_ids
|
||||
|
||||
def _inverse_is_favorite(self):
|
||||
unfavorited_jobs = favorited_jobs = self.env['hr.job']
|
||||
for job in self:
|
||||
if self.env.user in job.favorite_user_ids:
|
||||
unfavorited_jobs |= job
|
||||
else:
|
||||
favorited_jobs |= job
|
||||
favorited_jobs.write({'favorite_user_ids': [(4, self.env.uid)]})
|
||||
unfavorited_jobs.write({'favorite_user_ids': [(3, self.env.uid)]})
|
||||
|
||||
def _compute_document_ids(self):
|
||||
applicants = self.mapped('application_ids').filtered(lambda self: not self.emp_id)
|
||||
app_to_job = dict((applicant.id, applicant.job_id.id) for applicant in applicants)
|
||||
attachments = self.env['ir.attachment'].search([
|
||||
'|',
|
||||
'&', ('res_model', '=', 'hr.job'), ('res_id', 'in', self.ids),
|
||||
'&', ('res_model', '=', 'hr.applicant'), ('res_id', 'in', applicants.ids)])
|
||||
result = dict.fromkeys(self.ids, self.env['ir.attachment'])
|
||||
for attachment in attachments:
|
||||
if attachment.res_model == 'hr.applicant':
|
||||
result[app_to_job[attachment.res_id]] |= attachment
|
||||
else:
|
||||
result[attachment.res_id] |= attachment
|
||||
|
||||
for job in self:
|
||||
job.document_ids = result.get(job.id, False)
|
||||
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),
|
||||
'|',
|
||||
('active', '=', True),
|
||||
'&',
|
||||
('active', '=', False), ('refuse_reason_id', '!=', False),
|
||||
], ['job_id'], ['__count'])
|
||||
result = {job.id: count for job, count in read_group_result}
|
||||
for job in self:
|
||||
job.all_application_count = result.get(job.id, 0)
|
||||
|
||||
def _compute_application_count(self):
|
||||
read_group_result = self.env['hr.applicant']._read_group([('job_id', 'in', self.ids)], ['job_id'], ['__count'])
|
||||
result = {job.id: count for job, count in read_group_result}
|
||||
for job in self:
|
||||
job.application_count = result.get(job.id, 0)
|
||||
|
||||
def _get_first_stage(self):
|
||||
self.ensure_one()
|
||||
return self.env['hr.recruitment.stage'].search([
|
||||
'|',
|
||||
('job_ids', '=', False),
|
||||
('job_ids', '=', self.id)], order='sequence asc', limit=1)
|
||||
|
||||
def _compute_new_application_count(self):
|
||||
self.env.cr.execute(
|
||||
"""
|
||||
WITH job_stage AS (
|
||||
SELECT DISTINCT ON (j.id) j.id AS job_id, s.id AS stage_id, s.sequence AS sequence
|
||||
FROM hr_job j
|
||||
LEFT JOIN hr_job_hr_recruitment_stage_rel rel
|
||||
ON rel.hr_job_id = j.id
|
||||
JOIN hr_recruitment_stage s
|
||||
ON s.id = rel.hr_recruitment_stage_id
|
||||
OR s.id NOT IN (
|
||||
SELECT "hr_recruitment_stage_id"
|
||||
FROM "hr_job_hr_recruitment_stage_rel"
|
||||
WHERE "hr_recruitment_stage_id" IS NOT NULL
|
||||
)
|
||||
WHERE j.id in %s
|
||||
ORDER BY 1, 3 asc
|
||||
)
|
||||
SELECT s.job_id, COUNT(a.id) AS new_applicant
|
||||
FROM hr_applicant a
|
||||
JOIN job_stage s
|
||||
ON s.job_id = a.job_id
|
||||
AND a.stage_id = s.stage_id
|
||||
AND a.active IS TRUE
|
||||
WHERE a.company_id in %s
|
||||
GROUP BY s.job_id
|
||||
""", [tuple(self.ids), tuple(self.env.companies.ids)]
|
||||
)
|
||||
|
||||
new_applicant_count = dict(self.env.cr.fetchall())
|
||||
for job in self:
|
||||
job.new_application_count = new_applicant_count.get(job.id, 0)
|
||||
|
||||
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'], ['__count'])
|
||||
job_hires = {job.id: count for job, count 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:
|
||||
job.old_application_count = job.application_count - job.new_application_count
|
||||
|
||||
def _alias_get_creation_values(self):
|
||||
values = super(Job, self)._alias_get_creation_values()
|
||||
values['alias_model_id'] = self.env['ir.model']._get('hr.applicant').id
|
||||
if self.id:
|
||||
values['alias_defaults'] = defaults = ast.literal_eval(self.alias_defaults or "{}")
|
||||
defaults.update({
|
||||
'job_id': self.id,
|
||||
'department_id': self.department_id.id,
|
||||
'company_id': self.department_id.company_id.id if self.department_id else self.company_id.id,
|
||||
'user_id': self.user_id.id,
|
||||
})
|
||||
return values
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
for vals in vals_list:
|
||||
vals['favorite_user_ids'] = vals.get('favorite_user_ids', []) + [(4, self.env.uid)]
|
||||
jobs = super().create(vals_list)
|
||||
utm_linkedin = self.env.ref("utm.utm_source_linkedin", raise_if_not_found=False)
|
||||
if utm_linkedin:
|
||||
source_vals = [{
|
||||
'source_id': utm_linkedin.id,
|
||||
'job_id': job.id,
|
||||
} for job in jobs]
|
||||
self.env['hr.recruitment.source'].create(source_vals)
|
||||
jobs.sudo().interviewer_ids._create_recruitment_interviewers()
|
||||
return jobs
|
||||
|
||||
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
|
||||
interviewers_to_clean._remove_recruitment_interviewers()
|
||||
self.sudo().interviewer_ids._create_recruitment_interviewers()
|
||||
|
||||
# Since the alias is created upon record creation, the default values do not reflect the current values unless
|
||||
# specifically rewritten
|
||||
# List of fields to keep synched with the alias
|
||||
alias_fields = {'department_id', 'user_id'}
|
||||
if any(field for field in alias_fields if field in vals):
|
||||
for job in self:
|
||||
alias_default_vals = job._alias_get_creation_values().get('alias_defaults', '{}')
|
||||
job.alias_defaults = alias_default_vals
|
||||
return res
|
||||
|
||||
def _creation_subtype(self):
|
||||
return self.env.ref('hr_recruitment.mt_job_new')
|
||||
|
||||
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',
|
||||
'views': [
|
||||
(self.env.ref('hr_recruitment.ir_attachment_hr_recruitment_list_view').id, 'tree')
|
||||
],
|
||||
'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,
|
||||
'search_default_running_applicant_activities': 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,
|
||||
}
|
||||
return action
|
||||
|
||||
def close_dialog(self):
|
||||
return {'type': 'ir.actions.act_window_close'}
|
||||
|
||||
def edit_dialog(self):
|
||||
form_view = self.env.ref('hr.view_hr_job_form')
|
||||
return {
|
||||
'name': _('Job'),
|
||||
'res_model': 'hr.job',
|
||||
'res_id': self.id,
|
||||
'views': [(form_view.id, 'form'),],
|
||||
'type': 'ir.actions.act_window',
|
||||
'target': 'inline'
|
||||
}
|
15
models/hr_recruitment_degree.py
Normal file
15
models/hr_recruitment_degree.py
Normal file
@ -0,0 +1,15 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class RecruitmentDegree(models.Model):
|
||||
_name = "hr.recruitment.degree"
|
||||
_description = "Applicant Degree"
|
||||
_sql_constraints = [
|
||||
('name_uniq', 'unique (name)', 'The name of the Degree of Recruitment must be unique!')
|
||||
]
|
||||
|
||||
name = fields.Char("Degree Name", required=True, translate=True)
|
||||
sequence = fields.Integer("Sequence", default=1)
|
54
models/hr_recruitment_source.py
Normal file
54
models/hr_recruitment_source.py
Normal file
@ -0,0 +1,54 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class RecruitmentSource(models.Model):
|
||||
_name = "hr.recruitment.source"
|
||||
_description = "Source of Applicants"
|
||||
_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", ondelete='restrict')
|
||||
medium_id = fields.Many2one('utm.medium', default=lambda self: self.env.ref('utm.utm_medium_website'))
|
||||
|
||||
def _compute_has_domain(self):
|
||||
for source in self:
|
||||
if source.alias_id:
|
||||
source.has_domain = bool(source.alias_id.alias_domain_id)
|
||||
else:
|
||||
source.has_domain = bool(source.job_id.company_id.alias_domain_id
|
||||
or self.env.company.alias_domain_id)
|
||||
|
||||
def create_alias(self):
|
||||
campaign = self.env.ref('hr_recruitment.utm_campaign_job')
|
||||
medium = self.env.ref('utm.utm_medium_email')
|
||||
for source in self.filtered(lambda s: not s.alias_id):
|
||||
vals = {
|
||||
'alias_defaults': {
|
||||
'job_id': source.job_id.id,
|
||||
'campaign_id': campaign.id,
|
||||
'medium_id': medium.id,
|
||||
'source_id': source.source_id.id,
|
||||
},
|
||||
'alias_domain_id': source.job_id.company_id.alias_domain_id.id or self.env.company.alias_domain_id.id,
|
||||
'alias_model_id': self.env['ir.model']._get_id('hr.applicant'),
|
||||
'alias_name': f"{source.job_id.alias_name or source.job_id.name}+{source.name}",
|
||||
'alias_parent_thread_id': source.job_id.id,
|
||||
'alias_parent_model_id': self.env['ir.model']._get_id('hr.job'),
|
||||
}
|
||||
|
||||
# check that you can create source before to call mail.alias in sudo with known/controlled vals
|
||||
source.check_access_rights('create')
|
||||
source.check_access_rule('create')
|
||||
source.alias_id = self.env['mail.alias'].sudo().create(vals)
|
||||
|
||||
def unlink(self):
|
||||
""" Cascade delete aliases to avoid useless / badly configured aliases. """
|
||||
aliases = self.alias_id
|
||||
res = super().unlink()
|
||||
aliases.sudo().unlink()
|
||||
return res
|
51
models/hr_recruitment_stage.py
Normal file
51
models/hr_recruitment_stage.py
Normal file
@ -0,0 +1,51 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
|
||||
|
||||
class RecruitmentStage(models.Model):
|
||||
_name = "hr.recruitment.stage"
|
||||
_description = "Recruitment Stages"
|
||||
_order = 'sequence'
|
||||
|
||||
name = fields.Char("Stage Name", required=True, translate=True)
|
||||
sequence = fields.Integer(
|
||||
"Sequence", default=10)
|
||||
job_ids = fields.Many2many(
|
||||
'hr.job', string='Job Specific',
|
||||
help='Specific jobs that uses this stage. Other jobs will not use this stage.')
|
||||
requirements = fields.Text("Requirements")
|
||||
template_id = fields.Many2one(
|
||||
'mail.template', "Email Template",
|
||||
help="If set, a message is posted on the applicant using the template when the applicant is set to the stage.")
|
||||
fold = fields.Boolean(
|
||||
"Folded in Kanban",
|
||||
help="This stage is folded in the kanban view when there are no records in that stage to display.")
|
||||
hired_stage = fields.Boolean('Hired Stage',
|
||||
help="If checked, this stage is used to determine the hire date of an applicant")
|
||||
legend_blocked = fields.Char(
|
||||
'Red Kanban Label', default=lambda self: _('Blocked'), translate=True, required=True)
|
||||
legend_done = fields.Char(
|
||||
'Green Kanban Label', default=lambda self: _('Ready for Next Stage'), translate=True, required=True)
|
||||
legend_normal = fields.Char(
|
||||
'Grey Kanban Label', default=lambda self: _('In Progress'), translate=True, required=True)
|
||||
is_warning_visible = fields.Boolean(compute='_compute_is_warning_visible')
|
||||
|
||||
@api.model
|
||||
def default_get(self, fields):
|
||||
if self._context and self._context.get('default_job_id') and not self._context.get('hr_recruitment_stage_mono', False):
|
||||
context = dict(self._context)
|
||||
context.pop('default_job_id')
|
||||
self = self.with_context(context)
|
||||
return super(RecruitmentStage, self).default_get(fields)
|
||||
|
||||
@api.depends('hired_stage')
|
||||
def _compute_is_warning_visible(self):
|
||||
applicant_data = self.env['hr.applicant']._read_group([('stage_id', 'in', self.ids)], ['stage_id'], ['__count'])
|
||||
applicants = {stage.id: count for stage, count in applicant_data}
|
||||
for stage in self:
|
||||
if stage._origin.hired_stage and not stage.hired_stage and applicants.get(stage._origin.id):
|
||||
stage.is_warning_visible = True
|
||||
else:
|
||||
stage.is_warning_visible = False
|
16
models/ir_ui_menu.py
Normal file
16
models/ir_ui_menu.py
Normal file
@ -0,0 +1,16 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import models
|
||||
|
||||
|
||||
class IrUiMenu(models.Model):
|
||||
_inherit = 'ir.ui.menu'
|
||||
|
||||
def _load_menus_blacklist(self):
|
||||
res = super()._load_menus_blacklist()
|
||||
if self.env.user.has_group('hr_recruitment.group_hr_recruitment_interviewer') and not self.env.user.has_group('hr_recruitment.group_hr_recruitment_user'):
|
||||
res.append(self.env.ref('hr_recruitment.menu_hr_job_position').id)
|
||||
elif self.env.user.has_group('hr_recruitment.group_hr_recruitment_user'):
|
||||
res.append(self.env.ref('hr_recruitment.menu_hr_job_position_interviewer').id)
|
||||
return res
|
13
models/res_config_settings.py
Normal file
13
models/res_config_settings.py
Normal file
@ -0,0 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class ResConfigSettings(models.TransientModel):
|
||||
_inherit = ['res.config.settings']
|
||||
|
||||
module_website_hr_recruitment = fields.Boolean(string='Online Posting')
|
||||
module_hr_recruitment_survey = fields.Boolean(string='Interview Forms')
|
||||
group_applicant_cv_display = fields.Boolean(implied_group="hr_recruitment.group_applicant_cv_display")
|
||||
module_hr_recruitment_extract = fields.Boolean(string='Send CV to OCR to fill applications')
|
36
models/res_users.py
Normal file
36
models/res_users.py
Normal file
@ -0,0 +1,36 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import models
|
||||
|
||||
class ResUsers(models.Model):
|
||||
_inherit = 'res.users'
|
||||
|
||||
def _create_recruitment_interviewers(self):
|
||||
if not self:
|
||||
return
|
||||
interviewer_group = self.env.ref('hr_recruitment.group_hr_recruitment_interviewer')
|
||||
recruitment_group = self.env.ref('hr_recruitment.group_hr_recruitment_user')
|
||||
|
||||
interviewers = self - recruitment_group.users
|
||||
interviewers.sudo().write({
|
||||
'groups_id': [(4, interviewer_group.id)]
|
||||
})
|
||||
|
||||
def _remove_recruitment_interviewers(self):
|
||||
if not self:
|
||||
return
|
||||
interviewer_group = self.env.ref('hr_recruitment.group_hr_recruitment_interviewer')
|
||||
recruitment_group = self.env.ref('hr_recruitment.group_hr_recruitment_user')
|
||||
|
||||
job_interviewers = self.env['hr.job']._read_group([('interviewer_ids', 'in', self.ids)], ['interviewer_ids'])
|
||||
user_ids = {interviewer.id for [interviewer] in job_interviewers}
|
||||
|
||||
application_interviewers = self.env['hr.applicant']._read_group([('interviewer_ids', 'in', self.ids)], ['interviewer_ids'])
|
||||
user_ids |= {interviewer.id for [interviewer] in application_interviewers}
|
||||
|
||||
# Remove users that are no longer interviewers on at least a job or an application
|
||||
users_to_remove = set(self.ids) - (user_ids | set(recruitment_group.users.ids))
|
||||
self.env['res.users'].browse(users_to_remove).sudo().write({
|
||||
'groups_id': [(3, interviewer_group.id)]
|
||||
})
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user