[WIP] Followers rewrite: in create/write, the purpose is to add the field in the...
[odoo/odoo.git] / addons / hr_recruitment / hr_recruitment.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
6 #
7 #    This program is free software: you can redistribute it and/or modify
8 #    it under the terms of the GNU Affero General Public License as
9 #    published by the Free Software Foundation, either version 3 of the
10 #    License, or (at your option) any later version.
11 #
12 #    This program is distributed in the hope that it will be useful,
13 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
14 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 #    GNU Affero General Public License for more details.
16 #
17 #    You should have received a copy of the GNU Affero General Public License
18 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 #
20 ##############################################################################
21
22 import logging
23 import time
24 from datetime import datetime
25
26 import tools
27 from osv import fields, osv
28 from openerp.modules.registry import RegistryManager
29 from openerp import SUPERUSER_ID
30 from base_status.base_stage import base_stage
31 from tools.translate import _
32
33 _logger = logging.getLogger(__name__)
34
35 AVAILABLE_STATES = [
36     ('draft', 'New'),
37     ('cancel', 'Refused'),
38     ('open', 'In Progress'),
39     ('pending', 'Pending'),
40     ('done', 'Hired')
41 ]
42
43 AVAILABLE_PRIORITIES = [
44     ('', ''),
45     ('5', 'Not Good'),
46     ('4', 'On Average'),
47     ('3', 'Good'),
48     ('2', 'Very Good'),
49     ('1', 'Excellent')
50 ]
51
52 class hr_recruitment_source(osv.osv):
53     """ Sources of HR Recruitment """
54     _name = "hr.recruitment.source"
55     _description = "Source of Applicants"
56     _columns = {
57         'name': fields.char('Source Name', size=64, required=True, translate=True),
58     }
59
60 class hr_recruitment_stage(osv.osv):
61     """ Stage of HR Recruitment """
62     _name = "hr.recruitment.stage"
63     _description = "Stage of Recruitment"
64     _order = 'sequence'
65     _columns = {
66         'name': fields.char('Name', size=64, required=True, translate=True),
67         'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of stages."),
68         '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."),
69         '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."),
70         '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."),
71         'requirements': fields.text('Requirements'),
72     }
73     _defaults = {
74         'sequence': 1,
75         'state': 'draft',
76         'fold': False,
77     }
78
79 class hr_recruitment_degree(osv.osv):
80     """ Degree of HR Recruitment """
81     _name = "hr.recruitment.degree"
82     _description = "Degree of Recruitment"
83     _columns = {
84         'name': fields.char('Name', size=64, required=True, translate=True),
85         'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of degrees."),
86     }
87     _defaults = {
88         'sequence': 1,
89     }
90     _sql_constraints = [
91         ('name_uniq', 'unique (name)', 'The name of the Degree of Recruitment must be unique!')
92     ]
93
94 class hr_applicant(base_stage, osv.Model):
95     _name = "hr.applicant"
96     _description = "Applicant"
97     _order = "id desc"
98     _inherit = ['ir.needaction_mixin', 'mail.thread']
99     _mail_compose_message = True
100
101     def _get_default_department_id(self, cr, uid, context=None):
102         """ Gives default department by checking if present in the context """
103         return (self._resolve_department_id_from_context(cr, uid, context=context) or False)
104
105     def _get_default_stage_id(self, cr, uid, context=None):
106         """ Gives default stage_id """
107         department_id = self._get_default_department_id(cr, uid, context=context)
108         return self.stage_find(cr, uid, [], department_id, [('state', '=', 'draft')], context=context)
109
110     def _resolve_department_id_from_context(self, cr, uid, context=None):
111         """ Returns ID of department based on the value of 'default_department_id'
112             context key, or None if it cannot be resolved to a single
113             department.
114         """
115         if context is None:
116             context = {}
117         if type(context.get('default_department_id')) in (int, long):
118             return context.get('default_department_id')
119         if isinstance(context.get('default_department_id'), basestring):
120             department_name = context['default_department_id']
121             department_ids = self.pool.get('hr.department').name_search(cr, uid, name=department_name, context=context)
122             if len(department_ids) == 1:
123                 return int(department_ids[0][0])
124         return None
125
126     def _read_group_stage_ids(self, cr, uid, ids, domain, read_group_order=None, access_rights_uid=None, context=None):
127         access_rights_uid = access_rights_uid or uid
128         stage_obj = self.pool.get('hr.recruitment.stage')
129         order = stage_obj._order
130         # lame hack to allow reverting search, should just work in the trivial case
131         if read_group_order == 'stage_id desc':
132             order = "%s desc" % order
133         # retrieve section_id from the context and write the domain
134         # - ('id', 'in', 'ids'): add columns that should be present
135         # - OR ('department_id', '=', False), ('fold', '=', False): add default columns that are not folded
136         # - OR ('department_id', 'in', department_id), ('fold', '=', False) if department_id: add department columns that are not folded
137         department_id = self._resolve_department_id_from_context(cr, uid, context=context)
138         search_domain = []
139         if department_id:
140             search_domain += ['|', '&', ('department_id', '=', department_id), ('fold', '=', False)]
141         search_domain += ['|', ('id', 'in', ids), '&', ('department_id', '=', False), ('fold', '=', False)]
142         stage_ids = stage_obj._search(cr, uid, search_domain, order=order, access_rights_uid=access_rights_uid, context=context)
143         result = stage_obj.name_get(cr, access_rights_uid, stage_ids, context=context)
144         # restore order of the search
145         result.sort(lambda x,y: cmp(stage_ids.index(x[0]), stage_ids.index(y[0])))
146         return result
147
148     def _compute_day(self, cr, uid, ids, fields, args, context=None):
149         """
150         @param cr: the current row, from the database cursor,
151         @param uid: the current user’s ID for security checks,
152         @param ids: List of Openday’s IDs
153         @return: difference between current date and log date
154         @param context: A standard dictionary for contextual values
155         """
156         res = {}
157         for issue in self.browse(cr, uid, ids, context=context):
158             for field in fields:
159                 res[issue.id] = {}
160                 duration = 0
161                 ans = False
162                 hours = 0
163
164                 if field in ['day_open']:
165                     if issue.date_open:
166                         date_create = datetime.strptime(issue.create_date, "%Y-%m-%d %H:%M:%S")
167                         date_open = datetime.strptime(issue.date_open, "%Y-%m-%d %H:%M:%S")
168                         ans = date_open - date_create
169                         date_until = issue.date_open
170
171                 elif field in ['day_close']:
172                     if issue.date_closed:
173                         date_create = datetime.strptime(issue.create_date, "%Y-%m-%d %H:%M:%S")
174                         date_close = datetime.strptime(issue.date_closed, "%Y-%m-%d %H:%M:%S")
175                         date_until = issue.date_closed
176                         ans = date_close - date_create
177                 if ans:
178                     duration = float(ans.days)
179                     res[issue.id][field] = abs(float(duration))
180         return res
181
182     _columns = {
183         'name': fields.char('Subject', size=128, required=True),
184         'active': fields.boolean('Active', help="If the active field is set to false, it will allow you to hide the case without removing it."),
185         'description': fields.text('Description'),
186         'email_from': fields.char('Email', size=128, help="These people will receive email."),
187         'email_cc': fields.text('Watchers Emails', size=252, help="These email addresses will be added to the CC field of all inbound and outbound emails for this record before being sent. Separate multiple email addresses with a comma"),
188         'probability': fields.float('Probability'),
189         'partner_id': fields.many2one('res.partner', 'Contact'),
190         'create_date': fields.datetime('Creation Date', readonly=True, select=True),
191         'write_date': fields.datetime('Update Date', readonly=True),
192         'stage_id': fields.many2one ('hr.recruitment.stage', 'Stage',
193                         domain="['|', ('department_id', '=', department_id), ('department_id', '=', False)]"),
194         'state': fields.related('stage_id', 'state', type="selection", store=True,
195                 selection=AVAILABLE_STATES, string="State", readonly=True,
196                 help='The state is set to \'Draft\', when a case is created.\
197                       If the case is in progress the state is set to \'Open\'.\
198                       When the case is over, the state is set to \'Done\'.\
199                       If the case needs to be reviewed then the state is \
200                       set to \'Pending\'.'),
201         'company_id': fields.many2one('res.company', 'Company'),
202         'user_id': fields.many2one('res.users', 'Responsible'),
203         # Applicant Columns
204         'date_closed': fields.datetime('Closed', readonly=True, select=True),
205         'date_open': fields.datetime('Opened', readonly=True, select=True),
206         'date': fields.datetime('Date'),
207         'date_action': fields.date('Next Action Date'),
208         'title_action': fields.char('Next Action', size=64),
209         'priority': fields.selection(AVAILABLE_PRIORITIES, 'Appreciation'),
210         'job_id': fields.many2one('hr.job', 'Applied Job'),
211         'salary_proposed_extra': fields.char('Proposed Salary Extra', size=100, help="Salary Proposed by the Organisation, extra advantages"),
212         'salary_expected_extra': fields.char('Expected Salary Extra', size=100, help="Salary Expected by Applicant, extra advantages"),
213         'salary_proposed': fields.float('Proposed Salary', help="Salary Proposed by the Organisation"),
214         'salary_expected': fields.float('Expected Salary', help="Salary Expected by Applicant"),
215         'availability': fields.integer('Availability'),
216         'partner_name': fields.char("Applicant's Name", size=64),
217         'partner_phone': fields.char('Phone', size=32),
218         'partner_mobile': fields.char('Mobile', size=32),
219         'type_id': fields.many2one('hr.recruitment.degree', 'Degree'),
220         'department_id': fields.many2one('hr.department', 'Department'),
221         'survey': fields.related('job_id', 'survey_id', type='many2one', relation='survey', string='Survey'),
222         'response': fields.integer("Response"),
223         'reference': fields.char('Referred By', size=128),
224         'source_id': fields.many2one('hr.recruitment.source', 'Source'),
225         'day_open': fields.function(_compute_day, string='Days to Open', \
226                                 multi='day_open', type="float", store=True),
227         'day_close': fields.function(_compute_day, string='Days to Close', \
228                                 multi='day_close', type="float", store=True),
229         'color': fields.integer('Color Index'),
230         'emp_id': fields.many2one('hr.employee', 'employee'),
231         'user_email': fields.related('user_id', 'user_email', type='char', string='User Email', readonly=True),
232     }
233
234     _defaults = {
235         'active': lambda *a: 1,
236         'user_id':  lambda s, cr, uid, c: uid,
237         'email_from': lambda s, cr, uid, c: s._get_default_email(cr, uid, c),
238         'stage_id': lambda s, cr, uid, c: s._get_default_stage_id(cr, uid, c),
239         'department_id': lambda s, cr, uid, c: s._get_default_department_id(cr, uid, c),
240         'priority': lambda *a: '',
241         'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'hr.applicant', context=c),
242         'color': 0,
243     }
244
245     _group_by_full = {
246         'stage_id': _read_group_stage_ids
247     }
248
249     def onchange_job(self,cr, uid, ids, job, context=None):
250         result = {}
251
252         if job:
253             job_obj = self.pool.get('hr.job')
254             result['department_id'] = job_obj.browse(cr, uid, job, context=context).department_id.id
255             return {'value': result}
256         return {'value': {'department_id': False}}
257
258     def onchange_department_id(self, cr, uid, ids, department_id=False, context=None):
259         obj_recru_stage = self.pool.get('hr.recruitment.stage')
260         stage_ids = obj_recru_stage.search(cr, uid, ['|',('department_id','=',department_id),('department_id','=',False)], context=context)
261         stage_id = stage_ids and stage_ids[0] or False
262         return {'value': {'stage_id': stage_id}}
263
264     def stage_find(self, cr, uid, cases, section_id, domain=[], order='sequence', context=None):
265         """ Override of the base.stage method
266             Parameter of the stage search taken from the lead:
267             - department_id: if set, stages must belong to this section or
268               be a default case
269         """
270         if isinstance(cases, (int, long)):
271             cases = self.browse(cr, uid, cases, context=context)
272         # collect all section_ids
273         department_ids = []
274         if section_id:
275             department_ids.append(section_id)
276         for case in cases:
277             if case.department_id:
278                 department_ids.append(case.department_id.id)
279         # OR all section_ids and OR with case_default
280         search_domain = []
281         if department_ids:
282             search_domain += ['|', ('department_id', 'in', department_ids)]
283         search_domain.append(('department_id', '=', False))
284         # AND with the domain in parameter
285         search_domain += list(domain)
286         # perform search, return the first found
287         stage_ids = self.pool.get('hr.recruitment.stage').search(cr, uid, search_domain, order=order, context=context)
288         if stage_ids:
289             return stage_ids[0]
290         return False
291
292     def action_makeMeeting(self, cr, uid, ids, context=None):
293         """ This opens Meeting's calendar view to schedule meeting on current applicant
294             @return: Dictionary value for created Meeting view
295         """
296         applicant = self.browse(cr, uid, ids[0], context)
297         category = self.pool.get('ir.model.data').get_object(cr, uid, 'hr_recruitment', 'categ_meet_interview', context)
298         res = self.pool.get('ir.actions.act_window').for_xml_id(cr, uid, 'base_calendar', 'action_crm_meeting', context)
299         res['context'] = {
300             'default_partner_ids': applicant.partner_id and [applicant.partner_id.id] or False,
301             'default_user_id': uid,
302             'default_state': 'open',
303             'default_name': applicant.name,
304             'default_categ_ids': category and [category.id] or False,
305         }
306         return res
307
308     def action_print_survey(self, cr, uid, ids, context=None):
309         """
310         If response is available then print this response otherwise print survey form(print template of the survey).
311
312         @param self: The object pointer
313         @param cr: the current row, from the database cursor,
314         @param uid: the current user’s ID for security checks,
315         @param ids: List of Survey IDs
316         @param context: A standard dictionary for contextual values
317         @return: Dictionary value for print survey form.
318         """
319         if context is None:
320             context = {}
321         record = self.browse(cr, uid, ids, context=context)
322         record = record and record[0]
323         context.update({'survey_id': record.survey.id, 'response_id': [record.response], 'response_no': 0, })
324         value = self.pool.get("survey").action_print_survey(cr, uid, ids, context=context)
325         return value
326
327     def message_new(self, cr, uid, msg, custom_values=None, context=None):
328         """ Overrides mail_thread message_new that is called by the mailgateway
329             through message_process.
330             This override updates the document according to the email.
331         """
332         if custom_values is None: custom_values = {}
333         custom_values.update({
334             'name':  msg.get('subject') or _("No Subject"),
335             'description': msg.get('body_text'),
336             'email_from': msg.get('from'),
337             'email_cc': msg.get('cc'),
338             'user_id': False,
339         })
340         if msg.get('priority'):
341             custom_values['priority'] = msg.get('priority')
342         custom_values.update(self.message_partner_by_email(cr, uid, msg.get('from', False), context=context))
343         return super(hr_applicant,self).message_new(cr, uid, msg, custom_values=custom_values, context=context)
344
345     def message_update(self, cr, uid, ids, msg, update_vals=None, context=None):
346         """ Override mail_thread message_update that is called by the mailgateway
347             through message_process.
348             This method updates the document according to the email.
349         """
350         if isinstance(ids, (str, int, long)):
351             ids = [ids]
352         if update_vals is None: vals = {}
353         
354         update_vals.update({
355             'description': msg.get('body'),
356             'email_from': msg.get('from'),
357             'email_cc': msg.get('cc'),
358         })
359         if msg.get('priority'):
360             update_vals['priority'] = msg.get('priority')
361
362         maps = {
363             'cost': 'planned_cost',
364             'revenue': 'planned_revenue',
365             'probability': 'probability',
366         }
367         for line in msg.get('body_text', '').split('\n'):
368             line = line.strip()
369             res = tools.misc.command_re.match(line)
370             if res and maps.get(res.group(1).lower(), False):
371                 key = maps.get(res.group(1).lower())
372                 update_vals[key] = res.group(2).lower()
373
374         return super(hr_applicant, self).message_update(cr, uids, ids, update_vals=update_vals, context=context)
375
376     def create(self, cr, uid, vals, context=None):
377         obj_id = super(hr_applicant, self).create(cr, uid, vals, context=context)
378         self.create_send_note(cr, uid, [obj_id], context=context)
379         return obj_id
380
381     def case_open(self, cr, uid, ids, context=None):
382         """
383             open Request of the applicant for the hr_recruitment
384         """
385         res = super(hr_applicant, self).case_open(cr, uid, ids, context)
386         date = self.read(cr, uid, ids, ['date_open'])[0]
387         if not date['date_open']:
388             self.write(cr, uid, ids, {'date_open': time.strftime('%Y-%m-%d %H:%M:%S'),})
389         return res
390
391     def case_close(self, cr, uid, ids, context=None):
392         res = super(hr_applicant, self).case_close(cr, uid, ids, context)
393         return res
394
395     def case_close_with_emp(self, cr, uid, ids, context=None):
396         if context is None:
397             context = {}
398         hr_employee = self.pool.get('hr.employee')
399         model_data = self.pool.get('ir.model.data')
400         act_window = self.pool.get('ir.actions.act_window')
401         emp_id = False
402         for applicant in self.browse(cr, uid, ids, context=context):
403             address_id = False
404             if applicant.partner_id:
405                 address_id = applicant.partner_id.address_get(['contact'])['contact']
406             if applicant.job_id:
407                 applicant.job_id.write({'no_of_recruitment': applicant.job_id.no_of_recruitment - 1})
408                 emp_id = hr_employee.create(cr,uid,{'name': applicant.partner_name or applicant.name,
409                                                      'job_id': applicant.job_id.id,
410                                                      'address_home_id': address_id,
411                                                      'department_id': applicant.department_id.id
412                                                      })
413                 self.write(cr, uid, [applicant.id], {'emp_id': emp_id}, context=context)
414                 self.case_close(cr, uid, [applicant.id], context)
415             else:
416                 raise osv.except_osv(_('Warning!'),_('You must define Applied Job for this applicant.'))
417
418         action_model, action_id = model_data.get_object_reference(cr, uid, 'hr', 'open_view_employee_list')
419         dict_act_window = act_window.read(cr, uid, action_id, [])
420         if emp_id:
421             dict_act_window['res_id'] = emp_id
422         dict_act_window['view_mode'] = 'form,tree'
423         return dict_act_window
424
425     def case_cancel(self, cr, uid, ids, context=None):
426         """Overrides cancel for crm_case for setting probability
427         """
428         res = super(hr_applicant, self).case_cancel(cr, uid, ids, context)
429         self.write(cr, uid, ids, {'probability' : 0.0})
430         return res
431
432     def case_pending(self, cr, uid, ids, context=None):
433         """Marks case as pending"""
434         res = super(hr_applicant, self).case_pending(cr, uid, ids, context)
435         self.write(cr, uid, ids, {'probability' : 0.0})
436         return res
437
438     def case_reset(self, cr, uid, ids, context=None):
439         """Resets case as draft
440         """
441         res = super(hr_applicant, self).case_reset(cr, uid, ids, context)
442         self.write(cr, uid, ids, {'date_open': False, 'date_closed': False})
443         return res
444
445     def set_priority(self, cr, uid, ids, priority, *args):
446         """Set applicant priority
447         """
448         return self.write(cr, uid, ids, {'priority' : priority})
449
450     def set_high_priority(self, cr, uid, ids, *args):
451         """Set applicant priority to high
452         """
453         return self.set_priority(cr, uid, ids, '1')
454
455     def set_normal_priority(self, cr, uid, ids, *args):
456         """Set applicant priority to normal
457         """
458         return self.set_priority(cr, uid, ids, '3')
459
460     # -------------------------------------------------------
461     # OpenChatter methods and notifications
462     # -------------------------------------------------------
463
464     def message_get_follower_fields(self, cr, uid, ids, context=None):
465         """ Override to add 'user_id' field to automatic subscription. """
466         res = super(hr_applicant, self).message_get_follower_fields(cr, uid, ids, context=context)
467         return res.append('user_id')
468
469     def stage_set_send_note(self, cr, uid, ids, stage_id, context=None):
470         """ Override of the (void) default notification method. """
471         if not stage_id: return True
472         stage_name = self.pool.get('hr.recruitment.stage').name_get(cr, uid, [stage_id], context=context)[0][1]
473         return self.message_append_note(cr, uid, ids, body= _("Stage changed to <b>%s</b>.") % (stage_name), context=context)
474
475     def case_get_note_msg_prefix(self, cr, uid, id, context=None):
476                 return 'Applicant'
477
478     def case_open_send_note(self, cr, uid, ids, context=None):
479         message = _("Applicant has been set <b>in progress</b>.")
480         return self.message_append_note(cr, uid, ids, body=message, context=context)
481
482     def case_close_send_note(self, cr, uid, ids, context=None):
483         if context is None:
484             context = {}
485         for applicant in self.browse(cr, uid, ids, context=context):
486             if applicant.emp_id:
487                 message = _("Applicant has been <b>hired</b> and created as an employee.")
488                 self.message_append_note(cr, uid, [applicant.id], body=message, context=context)
489             else:
490                 message = _("Applicant has been <b>hired</b>.")
491                 self.message_append_note(cr, uid, [applicant.id], body=message, context=context)
492         return True
493
494     def case_cancel_send_note(self, cr, uid, ids, context=None):
495         msg = 'Applicant <b>refused</b>.'
496         return self.message_append_note(cr, uid, ids, body=msg, context=context)
497
498     def case_reset_send_note(self,  cr, uid, ids, context=None):
499         message =_("Applicant has been set as <b>new</b>.")
500         return self.message_append_note(cr, uid, ids, body=message, context=context)
501
502     def create_send_note(self, cr, uid, ids, context=None):
503         message = _("Applicant has been <b>created</b>.")
504         return self.message_append_note(cr, uid, ids, body=message, context=context)
505
506
507 class hr_job(osv.osv):
508     _inherit = "hr.job"
509     _name = "hr.job"
510     _inherits = {'mail.alias': 'alias_id'}
511     _columns = {
512         '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"),
513         'alias_id': fields.many2one('mail.alias', 'Alias', ondelete="cascade", required=True, 
514                                     help="Email alias for this job position. New emails will automatically "
515                                          "create new applicants for this job position."),
516     }
517
518     def init(self, cr):
519         # Installation hook to create aliases for all jobs, right after _auto_init
520         registry = RegistryManager.get(cr.dbname)
521         mail_alias = registry.get('mail.alias')
522         hr_job = registry.get('hr.job')
523         jobs_no_alias = hr_job.search(cr, SUPERUSER_ID, [('alias_id', '=', False)])
524         # Use read() not browse(), to avoid prefetching uninitialized inherited fields
525         for job_data in hr_job.read(cr, SUPERUSER_ID, jobs_no_alias, ['name']):
526             alias_id = mail_alias.create_unique_alias(cr, SUPERUSER_ID, {'alias_name': 'job_'+job_data['name'],
527                                                                          'alias_force_id': job_data['id']},
528                                                       model_name=self._name)
529             hr_job.write(cr, SUPERUSER_ID, job_data['id'], {'alias_id': alias_id})
530             _logger.info('Mail alias created for hr.job %s (uid %s)', job_data['name'], job_data['id'])
531
532         # Finally attempt to reinstate the missing constraint
533         try:
534             cr.execute('ALTER TABLE hr_job ALTER COLUMN alias_id SET NOT NULL')
535         except Exception:
536             pass
537     
538     def create(self, cr, uid, vals, context=None):
539         alias_pool = self.pool.get('mail.alias')
540         if not vals.get('alias_id'):
541             name = vals.pop('alias_name', None) or vals['name']
542             alias_id = alias_pool.create_unique_alias(cr, uid, 
543                           {'alias_name': "job_"+name},
544                           model_name="hr.applicant",
545                           context=context)
546             vals['alias_id'] = alias_id
547         res = super( hr_job, self).create(cr, uid, vals, context)
548         alias_pool.write(cr, uid, [vals['alias_id']], {"alias_defaults": {'job_id': res}}, context)
549         return res
550
551     def unlink(self, cr, uid, ids, context=None):
552         # Cascade-delete mail aliases as well, as they should not exist without the job position.
553         mail_alias = self.pool.get('mail.alias')
554         alias_ids = [job.alias_id.id for job in self.browse(cr, uid, ids, context=context) if job.alias_id]
555         res = super(hr_job, self).unlink(cr, uid, ids, context=context)
556         mail_alias.unlink(cr, uid, alias_ids, context=context)
557         return res
558     
559     def action_print_survey(self, cr, uid, ids, context=None):
560         if context is None:
561             context = {}
562         datas = {}
563         record = self.browse(cr, uid, ids, context=context)[0]
564         if record.survey_id:
565             datas['ids'] = [record.survey_id.id]
566         datas['model'] = 'survey.print'
567         context.update({'response_id': [0], 'response_no': 0,})
568         return {
569                 'type': 'ir.actions.report.xml',
570                 'report_name': 'survey.form',
571                 'datas': datas,
572                 'context' : context,
573                 'nodestroy':True,
574             }
575
576 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: