#
##############################################################################
-from base_status.base_stage import base_stage
+import logging
import time
-from datetime import datetime, timedelta
-from lxml import etree
-from osv import fields, osv
-from crm import crm
-import tools
-import collections
-import binascii
+from datetime import datetime
+
import tools
+from osv import fields, osv
+from openerp.modules.registry import RegistryManager
+from openerp import SUPERUSER_ID
+from base_status.base_stage import base_stage
from tools.translate import _
-from crm import wizard
-wizard.mail_compose_message.SUPPORTED_MODELS.append('hr.applicant')
+_logger = logging.getLogger(__name__)
AVAILABLE_STATES = [
('draft', 'New'),
_columns = {
'name': fields.char('Name', size=64, required=True, translate=True),
'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of stages."),
- 'department_id':fields.many2one('hr.department', 'Specific to a Department', help="Stages of the recruitment process may be different per department. If this stage is common to all departments, keep tempy this field."),
- 'state': fields.selection(AVAILABLE_STATES, 'State', required=True, help="The related state for the stage. The state of your document will automatically change regarding the selected stage. Example, a stage is related to the state 'Close', when your document reach this stage, it will be automatically closed."),
+ 'department_id':fields.many2one('hr.department', 'Specific to a Department', help="Stages of the recruitment process may be different per department. If this stage is common to all departments, keep this field empty."),
+ 'state': fields.selection(AVAILABLE_STATES, 'State', required=True, help="The related state for the stage. The state of your document will automatically change according to the selected stage. Example, a stage is related to the state 'Close', when your document reach this stage, it will be automatically closed."),
'fold': fields.boolean('Hide in views if empty', help="This stage is not visible, for example in status bar or kanban view, when there are no records in that stage to display."),
'requirements': fields.text('Requirements'),
}
_description = "Applicant"
_order = "id desc"
_inherit = ['ir.needaction_mixin', 'mail.thread']
+ _mail_compose_message = True
def _get_default_department_id(self, cr, uid, context=None):
""" Gives default department by checking if present in the context """
return res
_columns = {
- 'name': fields.char('Name', size=128, required=True),
+ 'name': fields.char('Subject', size=128, required=True),
'active': fields.boolean('Active', help="If the active field is set to false, it will allow you to hide the case without removing it."),
'description': fields.text('Description'),
'email_from': fields.char('Email', size=128, help="These people will receive email."),
'salary_expected_extra': fields.char('Expected Salary Extra', size=100, help="Salary Expected by Applicant, extra advantages"),
'salary_proposed': fields.float('Proposed Salary', help="Salary Proposed by the Organisation"),
'salary_expected': fields.float('Expected Salary', help="Salary Expected by Applicant"),
- 'availability': fields.integer('Availability (Days)'),
+ 'availability': fields.integer('Availability'),
'partner_name': fields.char("Applicant's Name", size=64),
'partner_phone': fields.char('Phone', size=32),
'partner_mobile': fields.char('Mobile', size=32),
'stage_id': lambda s, cr, uid, c: s._get_default_stage_id(cr, uid, c),
'department_id': lambda s, cr, uid, c: s._get_default_department_id(cr, uid, c),
'priority': lambda *a: '',
- 'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'crm.helpdesk', context=c),
+ 'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'hr.applicant', context=c),
'color': 0,
}
return {'value': {'department_id': False}}
def onchange_department_id(self, cr, uid, ids, department_id=False, context=None):
- if not department_id:
- return {'value': {'stage_id': False}}
obj_recru_stage = self.pool.get('hr.recruitment.stage')
stage_ids = obj_recru_stage.search(cr, uid, ['|',('department_id','=',department_id),('department_id','=',False)], context=context)
stage_id = stage_ids and stage_ids[0] or False
return False
def action_makeMeeting(self, cr, uid, ids, context=None):
+ """ This opens Meeting's calendar view to schedule meeting on current applicant
+ @return: Dictionary value for created Meeting view
"""
- This opens Meeting's calendar view to schedule meeting on current Opportunity
- @param self: The object pointer
- @param cr: the current row, from the database cursor,
- @param uid: the current user’s ID for security checks,
- @param ids: List of Opportunity to Meeting IDs
- @param context: A standard dictionary for contextual values
-
- @return: Dictionary value for created Meeting view
- """
- data_obj = self.pool.get('ir.model.data')
- if context is None:
- context = {}
- value = {}
- for opp in self.browse(cr, uid, ids, context=context):
- # Get meeting views
- search_view = data_obj.get_object(cr, uid, 'crm', 'view_crm_case_meetings_filter', context)
- calendar_view = data_obj.get_object(cr, uid, 'crm', 'crm_case_calendar_view_meet', context)
- form_view = data_obj.get_object(cr, uid, 'crm', 'crm_case_form_view_meet', context)
- tree_view = data_obj.get_object(cr, uid, 'crm', 'crm_case_tree_view_meet', context)
- category = data_obj.get_object(cr, uid, 'hr_recruitment', 'categ_meet_interview', context)
- context.update({
- 'default_applicant_id': opp.id,
- 'default_partner_id': opp.partner_id and opp.partner_id.id or False,
- 'default_email_from': opp.email_from,
- 'default_state': 'open',
- 'default_categ_id': category.id,
- 'default_name': opp.name,
- })
- value = {
- 'name': ('Meetings'),
- 'domain': "[('user_id','=',%s)]" % (uid),
- 'context': context,
- 'view_type': 'form',
- 'view_mode': 'calendar,form,tree',
- 'res_model': 'crm.meeting',
- 'view_id': False,
- 'views': [(calendar_view.id, 'calendar'), (form_view.id, 'form'), (tree_view.id, 'tree')],
- 'type': 'ir.actions.act_window',
- 'search_view_id': search_view.id,
- 'nodestroy': True,
- }
- return value
+ applicant = self.browse(cr, uid, ids[0], context)
+ category = self.pool.get('ir.model.data').get_object(cr, uid, 'hr_recruitment', 'categ_meet_interview', context)
+ res = self.pool.get('ir.actions.act_window').for_xml_id(cr, uid, 'base_calendar', 'action_crm_meeting', context)
+ res['context'] = {
+ 'default_partner_ids': applicant.partner_id and [applicant.partner_id.id] or False,
+ 'default_user_id': uid,
+ 'default_state': 'open',
+ 'default_name': applicant.name,
+ 'default_categ_ids': category and [category.id] or False,
+ }
+ return res
def action_print_survey(self, cr, uid, ids, context=None):
"""
return value
def message_new(self, cr, uid, msg, custom_values=None, context=None):
- """Automatically called when new email message arrives"""
- res_id = super(hr_applicant,self).message_new(cr, uid, msg, custom_values=custom_values, context=context)
- subject = msg.get('subject') or _("No Subject")
- body = msg.get('body_text')
- msg_from = msg.get('from')
- priority = msg.get('priority')
- vals = {
- 'name': subject,
- 'email_from': msg_from,
+ """ Overrides mail_thread message_new that is called by the mailgateway
+ through message_process.
+ This override updates the document according to the email.
+ """
+ if custom_values is None: custom_values = {}
+ custom_values.update({
+ 'name': msg.get('subject') or _("No Subject"),
+ 'description': msg.get('body_text'),
+ 'email_from': msg.get('from'),
'email_cc': msg.get('cc'),
- 'description': body,
'user_id': False,
- }
- if priority:
- vals['priority'] = priority
- vals.update(self.message_partner_by_email(cr, uid, msg.get('from', False)))
- self.write(cr, uid, [res_id], vals, context)
- return res_id
-
- def message_update(self, cr, uid, ids, msg, vals=None, default_act='pending', context=None):
+ })
+ if msg.get('priority'):
+ custom_values['priority'] = msg.get('priority')
+ custom_values.update(self.message_partner_by_email(cr, uid, msg.get('from', False), context=context))
+ return super(hr_applicant,self).message_new(cr, uid, msg, custom_values=custom_values, context=context)
+
+ def message_update(self, cr, uid, ids, msg, update_vals=None, context=None):
+ """ Override mail_thread message_update that is called by the mailgateway
+ through message_process.
+ This method updates the document according to the email.
+ """
if isinstance(ids, (str, int, long)):
ids = [ids]
- if vals is None:
- vals = {}
- msg_from = msg['from']
- vals.update({
- 'description': msg['body_text']
+ if update_vals is None: vals = {}
+
+ update_vals.update({
+ 'description': msg.get('body'),
+ 'email_from': msg.get('from'),
+ 'email_cc': msg.get('cc'),
})
- if msg.get('priority', False):
- vals['priority'] = msg.get('priority')
+ if msg.get('priority'):
+ update_vals['priority'] = msg.get('priority')
maps = {
- 'cost':'planned_cost',
+ 'cost': 'planned_cost',
'revenue': 'planned_revenue',
- 'probability':'probability'
+ 'probability': 'probability',
}
- vls = { }
- for line in msg['body_text'].split('\n'):
+ for line in msg.get('body_text', '').split('\n'):
line = line.strip()
res = tools.misc.command_re.match(line)
if res and maps.get(res.group(1).lower(), False):
key = maps.get(res.group(1).lower())
- vls[key] = res.group(2).lower()
+ update_vals[key] = res.group(2).lower()
- vals.update(vls)
- res = self.write(cr, uid, ids, vals, context=context)
- self.message_append_dict(cr, uid, ids, msg, context=context)
- return res
+ return super(hr_applicant, self).message_update(cr, uids, ids, update_vals=update_vals, context=context)
def create(self, cr, uid, vals, context=None):
obj_id = super(hr_applicant, self).create(cr, uid, vals, context=context)
# -------------------------------------------------------
# OpenChatter methods and notifications
# -------------------------------------------------------
-
- def message_get_subscribers(self, cr, uid, ids, context=None):
- sub_ids = self.message_get_subscribers_ids(cr, uid, ids, context=context);
- for obj in self.browse(cr, uid, ids, context=context):
- if obj.user_id:
- sub_ids.append(obj.user_id.id)
- return self.pool.get('res.users').read(cr, uid, sub_ids, context=context)
- def get_needaction_user_ids(self, cr, uid, ids, context=None):
- result = dict.fromkeys(ids, [])
+ def message_get_subscribers(self, cr, uid, ids, context=None):
+ """ Override to add responsible user. """
+ user_ids = super(hr_applicant, self).message_get_subscribers(cr, uid, ids, context=context)
for obj in self.browse(cr, uid, ids, context=context):
- if obj.state == 'draft' and obj.user_id:
- result[obj.id] = [obj.user_id.id]
- return result
+ if obj.user_id and not obj.user_id.id in user_ids:
+ user_ids.append(obj.user_id.id)
+ return user_ids
def stage_set_send_note(self, cr, uid, ids, stage_id, context=None):
""" Override of the (void) default notification method. """
_inherits = {'mail.alias': 'alias_id'}
_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', 'Mail Alias', ondelete="cascade", required=True),
+ 'alias_id': fields.many2one('mail.alias', 'Alias', ondelete="cascade", required=True,
+ help="Email alias for this job position. New emails will automatically "
+ "create new applicants for this job position."),
}
-
- def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
- res = super(hr_job,self).fields_view_get(cr, uid, view_id, view_type, context, toolbar=toolbar, submenu=submenu)
- if view_type == 'form':
- domain = self.pool.get("ir.config_parameter").get_param(cr, uid, "mail.catchall.domain", context=context)
- if not domain:
- doc = etree.XML(res['arch'])
- alias_node = doc.xpath("//field[@name='alias_id']")[0]
- parent = alias_node.getparent()
- parent.remove(alias_node)
- res['arch'] = etree.tostring(doc)
- return res
-
+
+ _defaults = {
+ 'alias_domain': False, # always hide alias during creation
+ }
+
+ def _auto_init(self, cr, context=None):
+ """Installation hook to create aliases for all jobs and avoid constraint errors."""
+
+ # disable the unique alias_id not null constraint, to avoid spurious warning during
+ # super.auto_init. We'll reinstall it afterwards.
+ self._columns['alias_id'].required = False
+
+ super(hr_job,self)._auto_init(cr, context=context)
+
+ registry = RegistryManager.get(cr.dbname)
+ mail_alias = registry.get('mail.alias')
+ hr_jobs = registry.get('hr.job')
+ jobs_no_alias = hr_jobs.search(cr, SUPERUSER_ID, [('alias_id', '=', False)])
+ # Use read() not browse(), to avoid prefetching uninitialized inherited fields
+ for job_data in hr_jobs.read(cr, SUPERUSER_ID, jobs_no_alias, ['name']):
+ alias_id = mail_alias.create_unique_alias(cr, SUPERUSER_ID, {'alias_name': 'job+'+job_data['name'],
+ 'alias_defaults': {'job_id': job_data['id']}},
+ model_name='hr.applicant')
+ hr_jobs.write(cr, SUPERUSER_ID, job_data['id'], {'alias_id': alias_id})
+ _logger.info('Mail alias created for hr.job %s (uid %s)', job_data['name'], job_data['id'])
+
+ # Finally attempt to reinstate the missing constraint
+ try:
+ cr.execute('ALTER TABLE hr_job ALTER COLUMN alias_id SET NOT NULL')
+ except Exception:
+ _logger.warning("Table '%s': unable to set a NOT NULL constraint on column '%s' !\n"\
+ "If you want to have it, you should update the records and execute manually:\n"\
+ "ALTER TABLE %s ALTER COLUMN %s SET NOT NULL",
+ self._table, 'alias_id', self._table, 'alias_id')
+
+ self._columns['alias_id'].required = True
+
def create(self, cr, uid, vals, context=None):
- model_pool = self.pool.get('ir.model.data')
- alias_pool = self.pool.get('mail.alias')
- res_id = model_pool.get_object( cr, uid, "hr_recruitment", "model_hr_applicant")
- vals.update({'alias_name': "job",
- 'alias_model_id': res_id.id})
- alias_pool.create_unique_alias(cr, uid, vals, context=context)
- res = super( hr_job, self).create(cr, uid, vals, context)
- record = self.read(cr, uid, res, context)
- alias_pool.write(cr, uid, [record['alias_id']], {"alias_defaults": {'job_id': res}}, context)
+ mail_alias = self.pool.get('mail.alias')
+ if not vals.get('alias_id'):
+ vals.pop('alias_name', None) # prevent errors during copy()
+ alias_id = mail_alias.create_unique_alias(cr, uid,
+ # Using '+' allows using subaddressing for those who don't
+ # have a catchall domain setup.
+ {'alias_name': 'jobs+'+vals['name']},
+ model_name="hr.applicant",
+ context=context)
+ vals['alias_id'] = alias_id
+ res = super(hr_job, self).create(cr, uid, vals, context)
+ mail_alias.write(cr, uid, [vals['alias_id']], {"alias_defaults": {'job_id': res}}, context)
return res
+ def unlink(self, cr, uid, ids, context=None):
+ # Cascade-delete mail aliases as well, as they should not exist without the job position.
+ mail_alias = self.pool.get('mail.alias')
+ alias_ids = [job.alias_id.id for job in self.browse(cr, uid, ids, context=context) if job.alias_id]
+ res = super(hr_job, self).unlink(cr, uid, ids, context=context)
+ mail_alias.unlink(cr, uid, alias_ids, context=context)
+ return res
def action_print_survey(self, cr, uid, ids, context=None):
if context is None:
'nodestroy':True,
}
-
-class crm_meeting(osv.osv):
- _inherit = 'crm.meeting'
- _columns = {
- 'applicant_id': fields.many2one('hr.applicant','Applicant'),
- }
-
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: