+
@@ -378,6 +378,7 @@
+
@@ -389,34 +390,18 @@
-
-
+
+
-
-
-
+
+
+
-
- hr.job.employee.form
- hr.job
- 20
-
-
-
-
-
Job Positions
hr.job
@@ -441,7 +426,6 @@
-
diff --git a/addons/hr/res_config_view.xml b/addons/hr/res_config_view.xml
index 3dc1795139c..98b61814873 100644
--- a/addons/hr/res_config_view.xml
+++ b/addons/hr/res_config_view.xml
@@ -35,7 +35,7 @@
-
+
diff --git a/addons/hr/test/hr_users.yml b/addons/hr/test/hr_users.yml
index e69c5ad5cb0..820180bfc9b 100644
--- a/addons/hr/test/hr_users.yml
+++ b/addons/hr/test/hr_users.yml
@@ -20,6 +20,7 @@
name: HR Officer
login: hro
password: hro
+ email: hro@example.com
-
I added groups for HR Officer.
-
diff --git a/addons/hr/test/open2recruit2close_job.yml b/addons/hr/test/open2recruit2close_job.yml
index 5519635d2fc..267c6c1f083 100644
--- a/addons/hr/test/open2recruit2close_job.yml
+++ b/addons/hr/test/open2recruit2close_job.yml
@@ -15,10 +15,10 @@
- state == 'open'
- no_of_recruitment == 0
-
- Now, Recruitement is started so I start recruitement of Job Postion of "Developer" Profile.
+ Now, Recruitment is started so I start recruitment of Job Postion of "Developer" Profile.
-
!python {model: hr.job}: |
- self.job_recruitement(cr, uid, [ref('job_developer')])
+ self.job_recruitment(cr, uid, [ref('job_developer')])
-
I check 'state' and number of 'Expected in Recruitment' after initiating the recruitment
-
diff --git a/addons/hr_contract/hr_contract_view.xml b/addons/hr_contract/hr_contract_view.xml
index 57a04dc9517..c21690401f2 100644
--- a/addons/hr_contract/hr_contract_view.xml
+++ b/addons/hr_contract/hr_contract_view.xml
@@ -81,7 +81,7 @@
-
+
diff --git a/addons/hr_recruitment/__openerp__.py b/addons/hr_recruitment/__openerp__.py
index cb1dd54e902..e6c94b4245f 100644
--- a/addons/hr_recruitment/__openerp__.py
+++ b/addons/hr_recruitment/__openerp__.py
@@ -37,13 +37,14 @@ You can define the different phases of interviews and easily rate the applicant
""",
'author': 'OpenERP SA',
'website': 'http://www.openerp.com',
- 'images': ['images/hr_recruitment_analysis.jpeg','images/hr_recruitment_applicants.jpeg'],
+ 'images': ['images/hr_recruitment_analysis.jpeg','images/hr_recruitment_applicants.jpeg','static/src/img/down1.png'],
'depends': [
'decimal_precision',
'hr',
'survey',
'calendar',
'fetchmail',
+ 'web_kanban_gauge',
],
'data': [
'wizard/hr_recruitment_create_partner_job_view.xml',
@@ -58,7 +59,11 @@ You can define the different phases of interviews and easily rate the applicant
'hr_recruitment_data.xml',
],
'demo': ['hr_recruitment_demo.xml'],
+ 'js': [
+ 'static/src/js/job_position.js',
+ ],
'test': ['test/recruitment_process.yml'],
+ 'css':['static/src/css/job_position.css'],
'installable': True,
'auto_install': False,
'application': True,
diff --git a/addons/hr_recruitment/hr_recruitment.py b/addons/hr_recruitment/hr_recruitment.py
index 89dd592e032..05c39147cf9 100644
--- a/addons/hr_recruitment/hr_recruitment.py
+++ b/addons/hr_recruitment/hr_recruitment.py
@@ -19,12 +19,10 @@
#
##############################################################################
-from openerp import tools
-
from datetime import datetime
from openerp.osv import fields, osv
from openerp.tools.translate import _
-from openerp.tools import html2plaintext
+
AVAILABLE_PRIORITIES = [
('', ''),
@@ -246,7 +244,8 @@ class hr_applicant(osv.Model):
if job_id:
job_record = self.pool.get('hr.job').browse(cr, uid, job_id, context=context)
department_id = job_record and job_record.department_id and job_record.department_id.id or False
- return {'value': {'department_id': department_id}}
+ user_id = job_record and job_record.user_id and job_record.user_id.id or False
+ return {'value': {'department_id': department_id, 'user_id': user_id}}
def onchange_department_id(self, cr, uid, ids, department_id=False, stage_id=False, context=None):
if not stage_id:
@@ -331,19 +330,12 @@ class hr_applicant(osv.Model):
value = self.pool.get("survey").action_print_survey(cr, uid, ids, context=context)
return value
- def action_get_attachment_tree_view(self, cr, uid, ids, context):
- domain = ['&', ('res_model', '=', 'hr.applicant'), ('res_id', 'in', ids)]
- return {
- 'name': _('Attachments'),
- 'domain': domain,
- 'res_model': 'ir.attachment',
- 'type': 'ir.actions.act_window',
- 'view_id': False,
- 'view_mode': 'tree,form',
- 'view_type': 'form',
- 'limit': 80,
- 'context': "{'default_res_model': '%s'}" % (self._name)
- }
+ def action_get_attachment_tree_view(self, cr, uid, ids, context=None):
+ model, action_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'base', 'action_attachment')
+ action = self.pool.get(model).read(cr, uid, action_id, context=context)
+ action['context'] = {'default_res_model': self._name, 'default_res_id': ids[0]}
+ action['domain'] = str(['&', ('res_model', '=', self._name), ('res_id', 'in', ids)])
+ return action
def message_get_suggested_recipients(self, cr, uid, ids, context=None):
recipients = super(hr_applicant, self).message_get_suggested_recipients(cr, uid, ids, context=context)
@@ -364,7 +356,7 @@ class hr_applicant(osv.Model):
val = msg.get('from').split('<')[0]
defaults = {
'name': msg.get('subject') or _("No Subject"),
- 'partner_name':val,
+ 'partner_name': val,
'email_from': msg.get('from'),
'email_cc': msg.get('cc'),
'user_id': False,
@@ -378,13 +370,20 @@ class hr_applicant(osv.Model):
def create(self, cr, uid, vals, context=None):
if context is None:
context = {}
+ context['mail_create_nolog'] = True
if vals.get('department_id') and not context.get('default_department_id'):
context['default_department_id'] = vals.get('department_id')
-
+ if vals.get('job_id') or context.get('default_job_id'):
+ job_id = vals.get('job_id') or context.get('default_job_id')
+ vals.update(self.onchange_job(cr, uid, [], job_id, context=context)['value'])
obj_id = super(hr_applicant, self).create(cr, uid, vals, context=context)
applicant = self.browse(cr, uid, obj_id, context=context)
if applicant.job_id:
- self.pool.get('hr.job').message_post(cr, uid, [applicant.job_id.id], body=_('Applicant created'), subtype="hr_recruitment.mt_job_new_applicant", context=context)
+ name = applicant.partner_name if applicant.partner_name else applicant.name
+ self.pool['hr.job'].message_post(
+ cr, uid, [applicant.job_id.id],
+ body=_('New application from %s') % name,
+ subtype="hr_recruitment.mt_job_applicant_new", context=context)
return obj_id
def write(self, cr, uid, ids, vals, context=None):
@@ -404,6 +403,15 @@ class hr_applicant(osv.Model):
else:
res = super(hr_applicant, self).write(cr, uid, ids, vals, context=context)
+ # post processing: if job changed, post a message on the job
+ if vals.get('job_id'):
+ for applicant in self.browse(cr, uid, ids, context=None):
+ name = applicant.partner_name if applicant.partner_name else applicant.name
+ self.pool['hr.job'].message_post(
+ cr, uid, [vals['job_id']],
+ body=_('New application from %s') % name,
+ subtype="hr_recruitment.mt_job_applicant_new", context=context)
+
# post processing: if stage changed, post a message in the chatter
if vals.get('stage_id'):
stage = self.pool['hr.recruitment.stage'].browse(cr, uid, vals['stage_id'], context=context)
@@ -444,7 +452,7 @@ class hr_applicant(osv.Model):
address_id = self.pool.get('res.partner').address_get(cr, uid, [applicant.partner_id.id], ['contact'])['contact']
contact_name = self.pool.get('res.partner').name_get(cr, uid, [applicant.partner_id.id])[0][1]
if applicant.job_id and (applicant.partner_name or contact_name):
- applicant.job_id.write({'no_of_recruitment': applicant.job_id.no_of_recruitment - 1})
+ applicant.job_id.write({'no_of_hired_employee': applicant.job_id.no_of_hired_employee + 1}, context=context)
emp_id = hr_employee.create(cr, uid, {'name': applicant.partner_name or contact_name,
'job_id': applicant.job_id.id,
'address_home_id': address_id,
@@ -454,6 +462,10 @@ class hr_applicant(osv.Model):
'work_phone': applicant.department_id and applicant.department_id.company_id and applicant.department_id.company_id.phone or False,
})
self.write(cr, uid, [applicant.id], {'emp_id': emp_id}, context=context)
+ self.pool['hr.job'].message_post(
+ cr, uid, [applicant.job_id.id],
+ body=_('New Employee %s Hired') % applicant.partner_name if applicant.partner_name else applicant.name,
+ subtype="hr_recruitment.mt_job_applicant_hired", context=context)
else:
raise osv.except_osv(_('Warning!'), _('You must define an Applied Job and a Contact Name for this applicant.'))
@@ -490,16 +502,37 @@ class hr_job(osv.osv):
_inherit = "hr.job"
_name = "hr.job"
_inherits = {'mail.alias': 'alias_id'}
+
+ def _get_attached_docs(self, cr, uid, ids, field_name, arg, context=None):
+ res = {}
+ attachment_obj = self.pool.get('ir.attachment')
+ for job_id in ids:
+ applicant_ids = self.pool.get('hr.applicant').search(cr, uid, [('job_id', '=', job_id)], context=context)
+ res[job_id] = attachment_obj.search(
+ cr, uid, [
+ '|',
+ '&', ('res_model', '=', 'hr.job'), ('res_id', '=', job_id),
+ '&', ('res_model', '=', 'hr.applicant'), ('res_id', 'in', applicant_ids)
+ ], context=context)
+ return res
+
_columns = {
'survey_id': fields.many2one('survey', 'Interview Form', help="Choose an interview form for this job position and you will be able to print/answer this interview from all applicants who apply for this job"),
'alias_id': fields.many2one('mail.alias', 'Alias', ondelete="restrict", required=True,
help="Email alias for this job position. New emails will automatically "
"create new applicants for this job position."),
'address_id': fields.many2one('res.partner', 'Job Location', help="Address where employees are working"),
+ 'application_ids': fields.one2many('hr.applicant', 'job_id', 'Applications'),
+ 'manager_id': fields.related('department_id', 'manager_id', type='many2one', string='Department Manager', relation='hr.employee', readonly=True, store=True),
+ 'document_ids': fields.function(_get_attached_docs, type='one2many', relation='ir.attachment', string='Applications'),
+ 'user_id': fields.many2one('res.users', 'Recruitment Responsible', track_visibility='onchange'),
+ 'color': fields.integer('Color Index'),
}
+
def _address_get(self, cr, uid, context=None):
user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
return user.company_id.partner_id.id
+
_defaults = {
'address_id': _address_get
}
@@ -541,6 +574,18 @@ class hr_job(osv.osv):
'nodestroy': True,
}
+ def action_get_attachment_tree_view(self, cr, uid, ids, context=None):
+ #open attachments of job and related applicantions.
+ model, action_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'base', 'action_attachment')
+ action = self.pool.get(model).read(cr, uid, action_id, context=context)
+ applicant_ids = self.pool.get('hr.applicant').search(cr, uid, [('job_id', 'in', ids)], context=context)
+ action['context'] = {'default_res_model': self._name, 'default_res_id': ids[0]}
+ action['domain'] = str(['|', '&', ('res_model', '=', 'hr.job'), ('res_id', 'in', ids), '&', ('res_model', '=', 'hr.applicant'), ('res_id', 'in', applicant_ids)])
+ return action
+
+ def action_set_no_of_recruitment(self, cr, uid, id, value, context=None):
+ return self.write(cr, uid, [id], {'no_of_recruitment': value}, context=context)
+
class applicant_category(osv.osv):
""" Category of applicant """
diff --git a/addons/hr_recruitment/hr_recruitment_data.xml b/addons/hr_recruitment/hr_recruitment_data.xml
index 76f3d3b72be..8a88d8ebe5f 100644
--- a/addons/hr_recruitment/hr_recruitment_data.xml
+++ b/addons/hr_recruitment/hr_recruitment_data.xml
@@ -495,13 +495,9 @@
jobs
+
-
-
- New Applicant
- hr.job
-
New Applicant
@@ -515,12 +511,35 @@
Stage changed
-
+
Applicant Hired
hr.applicant
Applicant hired
+
+
+ Applicant Created
+ hr.job
+
+
+ job_id
+
+
+ Applicant Stage Changed
+ hr.job
+
+
+ job_id
+
+
+ Applicant Hired
+ hr.job
+
+
+ job_id
+
+
Reserve
diff --git a/addons/hr_recruitment/hr_recruitment_menu.xml b/addons/hr_recruitment/hr_recruitment_menu.xml
index 2dcd490b075..4cc17ae098f 100644
--- a/addons/hr_recruitment/hr_recruitment_menu.xml
+++ b/addons/hr_recruitment/hr_recruitment_menu.xml
@@ -1,7 +1,6 @@
-
######################## JOB OPPORTUNITIES (menu) ###########################
Applications
@@ -9,16 +8,15 @@
kanban,tree,form,graph,calendar
+ {'empty_list_help_model': 'hr.job'}
-
- Click to add a new job applicant.
+
+ OpenERP helps you track applicants in the recruitment
+ process and follow up all operations: meetings, interviews, etc.
- OpenERP helps you track applicants in the recruitment process
- and follow up all operations: meetings, interviews, etc.
- Candidates and their cv's are automatically created when they
- apply for a job. If you install the document management modules,
- all resumes are indexed automatically, so that you can easily
- search through their content in the recruitment menu.
+ Applicants and their attached CV are created automatically when an email is sent.
+ If you install the document management modules, all resumes are indexed automatically,
+ so that you can easily search through their content.
@@ -58,15 +56,10 @@
sequence="1"/>
-
+
-
-
-
-
-
+ id="menu_crm_case_categ0_act_job" action="crm_case_categ0_act_job" sequence="2"/>
diff --git a/addons/hr_recruitment/hr_recruitment_view.xml b/addons/hr_recruitment/hr_recruitment_view.xml
index eb823364e96..c9dc0d04ffb 100644
--- a/addons/hr_recruitment/hr_recruitment_view.xml
+++ b/addons/hr_recruitment/hr_recruitment_view.xml
@@ -34,7 +34,7 @@
-
+
Applicants
hr.applicant
@@ -91,10 +91,10 @@
+
-
@@ -307,46 +307,190 @@
-
+
+ Applications
+ hr.applicant
+ kanban,tree,form,graph,calendar
+ {'search_default_job_id': [active_id], 'default_job_id': active_id, 'empty_list_help_model': 'hr.job'}
+
+
+ OpenERP helps you track applicants in the recruitment
+ process and follow up all operations: meetings, interviews, etc.
+
+ Applicants and their attached CV are created automatically when an email is sent.
+ If you install the document management modules, all resumes are indexed automatically,
+ so that you can easily search through their content.
+
+
+
+
+
+
+ Job
+ hr.job
+
+
+
+
+
+
+
+
+
hr.job.form1
hr.job
-
+
+
+
+
+
+
(empty = remote work)
-
-
-
-
-
-
-
-
-
-
-
+
+
+
-
-
+
+
+
+
+
+
+
+
+ hr.job.kanban
+ hr.job
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ í
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ()
+
+
+
+ %%
+
+
+
+
+
+
+
+ Hired Employees
+
+
+
+
+
+
click here, To start the recruitment
+

+
+
+
+
+
+
+
+
+
+
+
+
+
+ Job Positions
+ hr.job
+ form
+ kanban,tree,form
+ {'search_default_in_recruitment': 1}
+
+
+ Click here to create a new job or remove the filter on "In Recruitment" to recruit for an on hold job.
+
+
+ Define job position profile and manage recruitment in a context of a particular job: print interview survey, define number of expected new employees, and manage its recruitment pipe
+
+
+
+
hr.recruitment.stage.tree
diff --git a/addons/hr_recruitment/res_config.py b/addons/hr_recruitment/res_config.py
index ca70150b592..5f71d1f9bba 100644
--- a/addons/hr_recruitment/res_config.py
+++ b/addons/hr_recruitment/res_config.py
@@ -2,7 +2,7 @@
##############################################################################
#
# OpenERP, Open Source Business Applications
-# Copyright (C) 2004-2012 OpenERP S.A. ().
+# Copyright (C) 2004-Today OpenERP S.A. ().
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@@ -19,9 +19,11 @@
#
##############################################################################
+from openerp import SUPERUSER_ID
from openerp.osv import fields, osv
-class hr_applicant_settings(osv.osv_memory):
+
+class hr_applicant_settings(osv.TransientModel):
_name = 'hr.config.settings'
_inherit = ['hr.config.settings', 'fetchmail.config.settings']
@@ -32,6 +34,44 @@ class hr_applicant_settings(osv.osv_memory):
'fetchmail_applicants': fields.boolean('Create applicants from an incoming email account',
fetchmail_model='hr.applicant', fetchmail_name='Incoming HR Applications',
help='Allow applicants to send their job application to an email address (jobs@mycompany.com), '
- 'and create automatically application documents in the system.'),
+ 'and create automatically application documents in the system.',
+ deprecated='Will be removed with OpenERP v8, not applicable anymore. Use aliases instead.'),
+ 'alias_prefix': fields.char('Default Alias Name for Jobs'),
+ 'alias_domain': fields.char('Alias Domain'),
}
+ _defaults = {
+ 'alias_domain': lambda self, cr, uid, context: self.pool['mail.alias']._get_alias_domain(cr, SUPERUSER_ID, [1], None, None)[1],
+ }
+
+ def _find_default_job_alias_id(self, cr, uid, context=None):
+ alias_id = self.pool['ir.model.data'].xmlid_to_res_id(cr, uid, 'hr_recruitment.mail_alias_jobs')
+ if not alias_id:
+ alias_ids = self.pool['mail.alias'].search(
+ cr, uid, [
+ ('alias_model_id.model', '=', 'hr.applicant'),
+ ('alias_force_thread_id', '=', 0),
+ ('alias_parent_model_id.model', '=', 'hr.job'),
+ ('alias_parent_thread_id', '=', 0),
+ ('alias_defaults', '=', '{}')
+ ], context=context)
+ alias_id = alias_ids and alias_ids[0] or False
+ return alias_id
+
+ def get_default_alias_prefix(self, cr, uid, ids, context=None):
+ alias_name = False
+ alias_id = self._find_default_job_alias_id(cr, uid, context=context)
+ if alias_id:
+ alias_name = self.pool['mail.alias'].browse(cr, uid, alias_id, context=context).alias_name
+ return {'alias_prefix': alias_name}
+
+ def set_default_alias_prefix(self, cr, uid, ids, context=None):
+ mail_alias = self.pool.get('mail.alias')
+ for record in self.browse(cr, uid, ids, context=context):
+ alias_id = self._find_default_job_alias_id(cr, uid, context=context)
+ if not alias_id:
+ create_ctx = dict(context, alias_model_name='hr.applicant', alias_parent_model_name='hr.job')
+ alias_id = self.pool['mail.alias'].create(cr, uid, {'alias_name': record.alias_prefix}, context=create_ctx)
+ else:
+ mail_alias.write(cr, uid, alias_id, {'alias_name': record.alias_prefix}, context=context)
+ return True
diff --git a/addons/hr_recruitment/res_config_view.xml b/addons/hr_recruitment/res_config_view.xml
index b0dfc5bb232..d4a16afca62 100644
--- a/addons/hr_recruitment/res_config_view.xml
+++ b/addons/hr_recruitment/res_config_view.xml
@@ -19,6 +19,14 @@
+
+
+
+
+ @
+
+
+
diff --git a/addons/hr_recruitment/static/src/css/Makefile b/addons/hr_recruitment/static/src/css/Makefile
new file mode 100644
index 00000000000..3b99d5c5523
--- /dev/null
+++ b/addons/hr_recruitment/static/src/css/Makefile
@@ -0,0 +1,2 @@
+job_position.css: job_position.sass
+ sass --trace -t expanded job_position.sass job_position.css
diff --git a/addons/hr_recruitment/static/src/css/job_position.css b/addons/hr_recruitment/static/src/css/job_position.css
new file mode 100644
index 00000000000..0376b83cc06
--- /dev/null
+++ b/addons/hr_recruitment/static/src/css/job_position.css
@@ -0,0 +1,86 @@
+.openerp .oe_kanban_job{
+ width: 355px;
+ min-height: 165px !important;
+}
+.openerp .oe_job_alias{
+ margin: 3px;
+}
+.openerp .oe_job_detail{
+ height: 70px;
+ width: 308px
+}
+.openerp .oe_job_alias .oe_e {
+ font-size: 30px;
+ line-height: 6px;
+ vertical-align: top;
+ margin-right: 3px;
+ color: white;
+ text-shadow: 0px 0px 2px black;
+ float: left;
+}
+.openerp .oe_job {
+ font-size: 112%;
+ position: inline;
+ margin: 3px 3px;
+ color: #4c4c4c;
+ height: 16px;
+}
+.openerp img.oe_job_avatar {
+ position: absolute;
+ width: 24px;
+ height: 24px;
+ margin-left: 295px;
+ margin-top: -5px;
+}
+.openerp .oe_launch_recruitment{
+ float: left;
+ position: absolute;
+ bottom: 3px;
+ left: 10px;
+}
+.openerp div.oe_applications {
+ position: absolute;;
+ margin-top: 16px;
+ font-size: 14px;
+}
+.openerp .oe_applications > a > span:hover{
+ margin: 4px 0;
+ text-decoration: underline;
+}
+.openerp .oe_name {
+ font-size: 14px;
+ font-weight: bold;
+}
+.openerp .oe_manager_name {
+ width: 135px;
+ font-size: 11px;
+ color: gray;
+}
+.openerp .oe_job_justgage {
+ float: right;
+ margin-top: -40px;
+ margin-right: -58px;
+ width:200px;
+ height:130px;
+}
+.openerp .oe_department {
+ width: 350px;
+}
+.openerp .oe_start_recruitment {
+ padding-top: 10px;
+}
+.openerp .oe_start_recruitment p {
+ font-size: 14px;
+ color: gray;
+ padding-left: 50px;
+}
+.openerp .oe_start_recruitment img {
+ margin-top: -22px;
+ width: 32px;
+ height: 34px;
+ float: left;
+ padding-left: 12px;
+}
+.openerp .oe_job_messages{
+ margin-top: 40px !important;
+}
diff --git a/addons/hr_recruitment/static/src/css/job_position.sass b/addons/hr_recruitment/static/src/css/job_position.sass
new file mode 100644
index 00000000000..fa82b6ed4a2
--- /dev/null
+++ b/addons/hr_recruitment/static/src/css/job_position.sass
@@ -0,0 +1,70 @@
+.openerp
+ .oe_kanban_job
+ width: 355px
+ min-height: 165px !important
+ .oe_job_alias
+ margin: 3px
+ .oe_job_detail
+ height: 70px
+ width: 308px
+ .oe_job_alias .oe_e
+ font-size: 30px
+ line-height: 6px
+ vertical-align: top
+ margin-right: 3px
+ color: white
+ text-shadow: 0px 0px 2px black
+ float: left
+ .oe_job
+ font-size: 112%
+ position: inline
+ margin: 3px 3px
+ color: #4c4c4c
+ height: 16px
+ img.oe_job_avatar
+ position: absolute
+ width: 24px
+ height: 24px
+ margin-left: 295px
+ margin-top: -5px
+ .oe_launch_recruitment
+ float: left
+ position: absolute
+ bottom: 3px
+ left: 10px
+ div.oe_applications
+ position: absolute
+ margin-top: 16px
+ font-size: 14px
+ .oe_applications > a > span:hover
+ margin: 4px 0
+ text-decoration: underline
+ .oe_name
+ font-size: 14px
+ font-weight: bold
+ .oe_manager_name
+ width: 135px
+ font-size: 11px
+ color: gray
+ .oe_job_justgage
+ float: right
+ margin-top: -40px
+ margin-right: -58px
+ width: 200px
+ height: 130px
+ .oe_department
+ width: 350px
+ .oe_start_recruitment
+ padding-top: 10px
+ p
+ font-size: 14px
+ color: gray
+ padding-left: 50px
+ img
+ margin-top: -22px
+ width: 32px
+ height: 34px
+ float: left
+ padding-left: 12px
+ .oe_job_messages
+ margin-top: 40px !important
diff --git a/addons/hr_recruitment/static/src/img/down1.png b/addons/hr_recruitment/static/src/img/down1.png
new file mode 100644
index 00000000000..b97f34383c8
Binary files /dev/null and b/addons/hr_recruitment/static/src/img/down1.png differ
diff --git a/addons/hr_recruitment/static/src/js/job_position.js b/addons/hr_recruitment/static/src/js/job_position.js
new file mode 100644
index 00000000000..06e880cb40d
--- /dev/null
+++ b/addons/hr_recruitment/static/src/js/job_position.js
@@ -0,0 +1,13 @@
+openerp.hr_recruitment = function (openerp) {
+
+ openerp.web_kanban.KanbanRecord.include({
+ on_card_clicked: function() {
+ if (this.view.dataset.model === 'hr.job') {
+ this.$('.oe_applications a').first().click();
+ } else {
+ this._super.apply(this, arguments);
+ }
+ },
+ });
+
+}
\ No newline at end of file
diff --git a/addons/mail/mail_alias.py b/addons/mail/mail_alias.py
index 1eacb902e0f..d8a79dd98e0 100644
--- a/addons/mail/mail_alias.py
+++ b/addons/mail/mail_alias.py
@@ -154,6 +154,12 @@ class mail_alias(osv.Model):
sequence = (sequence + 1) if sequence else 2
return new_name
+ def _clean_and_make_unique(self, cr, uid, name, context=None):
+ # when an alias name appears to already be an email, we keep the local part only
+ name = remove_accents(name).lower().split('@')[0]
+ name = re.sub(r'[^\w+.]+', '-', name)
+ return self._find_unique(cr, uid, name, context=context)
+
def migrate_to_alias(self, cr, child_model_name, child_table_name, child_model_auto_init_fct,
alias_model_name, alias_id_column, alias_key, alias_prefix='', alias_force_key='', alias_defaults={},
alias_generate_name=False, context=None):
@@ -199,7 +205,7 @@ class mail_alias(osv.Model):
alias_vals['alias_parent_thread_id'] = obj_data['id']
alias_create_ctx = dict(context, alias_model_name=alias_model_name, alias_parent_model_name=child_model_name)
alias_id = mail_alias.create(cr, SUPERUSER_ID, alias_vals, context=alias_create_ctx)
- child_class_model.write(cr, SUPERUSER_ID, obj_data['id'], {'alias_id': alias_id})
+ child_class_model.write(cr, SUPERUSER_ID, obj_data['id'], {'alias_id': alias_id}, context={'mail_notrack': True})
_logger.info('Mail alias created for %s %s (id %s)', child_model_name, obj_data[alias_key], obj_data['id'])
# Finally attempt to reinstate the missing constraint
@@ -227,11 +233,7 @@ class mail_alias(osv.Model):
model_name = context.get('alias_model_name')
parent_model_name = context.get('alias_parent_model_name')
if vals.get('alias_name'):
- # when an alias name appears to already be an email, we keep the local part only
- alias_name = remove_accents(vals['alias_name']).lower().split('@')[0]
- alias_name = re.sub(r'[^\w+.]+', '-', alias_name)
- alias_name = self._find_unique(cr, uid, alias_name, context=context)
- vals['alias_name'] = alias_name
+ vals['alias_name'] = self._clean_and_make_unique(cr, uid, vals.get('alias_name'), context=context)
if model_name:
model_id = self.pool.get('ir.model').search(cr, uid, [('model', '=', model_name)], context=context)[0]
vals['alias_model_id'] = model_id
@@ -240,6 +242,12 @@ class mail_alias(osv.Model):
vals['alias_parent_model_id'] = model_id
return super(mail_alias, self).create(cr, uid, vals, context=context)
+ def write(self, cr, uid, ids, vals, context=None):
+ """"give uniqe alias name if given alias name is allready assigned"""
+ if vals.get('alias_name'):
+ vals['alias_name'] = self._clean_and_make_unique(cr, uid, vals.get('alias_name'), context=context)
+ return super(mail_alias, self).write(cr, uid, ids, vals, context=context)
+
def open_document(self, cr, uid, ids, context=None):
alias = self.browse(cr, uid, ids, context=context)[0]
if not alias.alias_model_id or not alias.alias_force_thread_id:
diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py
index ee4dcac5414..9a8adee31c3 100644
--- a/addons/mail/mail_thread.py
+++ b/addons/mail/mail_thread.py
@@ -113,9 +113,9 @@ class mail_thread(osv.AbstractModel):
object_id.alias_id.alias_model_id.model == self._name and \
object_id.alias_id.alias_force_thread_id == 0:
alias = object_id.alias_id
- elif catchall_domain and model: # no specific res_id given -> generic help message, take an example alias (i.e. alias of some section_id)
+ if not alias and catchall_domain and model: # no res_id or res_id not linked to an alias -> generic help message, take a generic alias of the model
alias_obj = self.pool.get('mail.alias')
- alias_ids = alias_obj.search(cr, uid, [("alias_parent_model_id.model", "=", model), ("alias_name", "!=", False), ('alias_force_thread_id', '=', False)], context=context, order='id ASC')
+ alias_ids = alias_obj.search(cr, uid, [("alias_parent_model_id.model", "=", model), ("alias_name", "!=", False), ('alias_force_thread_id', '=', False), ('alias_parent_thread_id', '=', False)], context=context, order='id ASC')
if alias_ids and len(alias_ids) == 1:
alias = alias_obj.browse(cr, uid, alias_ids[0], context=context)
@@ -384,7 +384,10 @@ class mail_thread(osv.AbstractModel):
track_ctx = dict(context)
if 'lang' not in track_ctx:
track_ctx['lang'] = self.pool.get('res.users').browse(cr, uid, uid, context=context).lang
- tracked_fields = self._get_tracked_fields(cr, uid, values.keys(), context=track_ctx)
+ if not context.get('mail_notrack'):
+ tracked_fields = self._get_tracked_fields(cr, uid, values.keys(), context=track_ctx)
+ else:
+ tracked_fields = []
if tracked_fields:
records = self.browse(cr, uid, ids, context=track_ctx)
initial_values = dict((this.id, dict((key, getattr(this, key)) for key in tracked_fields.keys())) for this in records)
diff --git a/addons/sale_crm/sale_crm_view.xml b/addons/sale_crm/sale_crm_view.xml
index 8c3c98c3f97..7ed31586c17 100644
--- a/addons/sale_crm/sale_crm_view.xml
+++ b/addons/sale_crm/sale_crm_view.xml
@@ -301,7 +301,7 @@
Invoiced
Forecast
+ options="{'max_field': 'invoiced_target', 'on_change': 'action_forecast'}">Forecast