1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-today OpenERP SA (<http://www.openerp.com>)
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.
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.
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/>.
20 ##############################################################################
26 from osv import fields
28 from tools.translate import _
33 ('cancel', 'Cancelled'),
34 ('open', 'In Progress'),
35 ('pending', 'Pending'),
39 AVAILABLE_PRIORITIES = [
47 class crm_case_channel(osv.osv):
48 _name = "crm.case.channel"
49 _description = "Channels"
52 'name': fields.char('Channel Name', size=64, required=True),
53 'active': fields.boolean('Active'),
56 'active': lambda *a: 1,
59 class crm_case_stage(osv.osv):
60 """ Model for case stages. This models the main stages of a document
61 management flow. Main CRM objects (leads, opportunities, project
62 issues, ...) will now use only stages, instead of state and stages.
63 Stages are for example used to display the kanban view of records.
65 _name = "crm.case.stage"
66 _description = "Stage of case"
71 'name': fields.char('Stage Name', size=64, required=True, translate=True),
72 'sequence': fields.integer('Sequence', help="Used to order stages. Lower is better."),
73 'probability': fields.float('Probability (%)', required=True, help="This percentage depicts the default/average probability of the Case for this stage to be a success"),
74 'on_change': fields.boolean('Change Probability Automatically', help="Setting this stage will change the probability automatically on the opportunity."),
75 'requirements': fields.text('Requirements'),
76 'section_ids':fields.many2many('crm.case.section', 'section_stage_rel', 'stage_id', 'section_id', string='Sections',
77 help="Link between stages and sales teams. When set, this limitate the current stage to the selected sales teams."),
78 '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. For example, if a stage is related to the state 'Close', when your document reaches this stage, it will be automatically have the 'closed' state."),
79 'case_default': fields.boolean('Common to All Teams',
80 help="If you check this field, this stage will be proposed by default on each sales team. It will not assign this stage to existing teams."),
81 'fold': fields.boolean('Hide in Views when Empty',
82 help="This stage is not visible, for example in status bar or kanban view, when there are no records in that stage to display."),
83 'type': fields.selection([ ('lead','Lead'),
84 ('opportunity', 'Opportunity'),
86 string='Type', size=16, required=True,
87 help="This field is used to distinguish stages related to Leads from stages related to Opportunities, or to specify stages available for both types."),
91 'sequence': lambda *args: 1,
92 'probability': lambda *args: 0.0,
98 class crm_case_section(osv.osv):
99 """ Model for sales teams. """
100 _name = "crm.case.section"
101 _description = "Sales Teams"
102 _order = "complete_name"
104 def get_full_name(self, cr, uid, ids, field_name, arg, context=None):
105 return dict(self.name_get(cr, uid, ids, context=context))
108 'name': fields.char('Sales Team', size=64, required=True, translate=True),
109 'complete_name': fields.function(get_full_name, type='char', size=256, readonly=True, store=True),
110 'code': fields.char('Code', size=8),
111 'active': fields.boolean('Active', help="If the active field is set to "\
112 "true, it will allow you to hide the sales team without removing it."),
113 'allow_unlink': fields.boolean('Allow Delete', help="Allows to delete non draft cases"),
114 'change_responsible': fields.boolean('Reassign Escalated', help="When escalating to this team override the saleman with the team leader."),
115 'user_id': fields.many2one('res.users', 'Team Leader'),
116 'member_ids':fields.many2many('res.users', 'sale_member_rel', 'section_id', 'member_id', 'Team Members'),
117 'reply_to': fields.char('Reply-To', size=64, help="The email address put in the 'Reply-To' of all emails sent by OpenERP about cases in this sales team"),
118 'parent_id': fields.many2one('crm.case.section', 'Parent Team'),
119 'child_ids': fields.one2many('crm.case.section', 'parent_id', 'Child Teams'),
120 'resource_calendar_id': fields.many2one('resource.calendar', "Working Time", help="Used to compute open days"),
121 'note': fields.text('Description'),
122 'working_hours': fields.float('Working Hours', digits=(16,2 )),
123 'stage_ids': fields.many2many('crm.case.stage', 'section_stage_rel', 'section_id', 'stage_id', 'Stages'),
126 def _get_stage_common(self, cr, uid, context):
127 ids = self.pool.get('crm.case.stage').search(cr, uid, [('case_default','=',1)], context=context)
131 'active': lambda *a: 1,
132 'allow_unlink': lambda *a: 1,
133 'stage_ids': _get_stage_common
137 ('code_uniq', 'unique (code)', 'The code of the sales team must be unique !')
141 (osv.osv._check_recursion, 'Error ! You cannot create recursive Sales team.', ['parent_id'])
144 def name_get(self, cr, uid, ids, context=None):
145 """Overrides orm name_get method"""
146 if not isinstance(ids, list) :
151 reads = self.read(cr, uid, ids, ['name', 'parent_id'], context)
154 name = record['name']
155 if record['parent_id']:
156 name = record['parent_id'][1] + ' / ' + name
157 res.append((record['id'], name))
160 class crm_case_categ(osv.osv):
161 """ Category of Case """
162 _name = "crm.case.categ"
163 _description = "Category of Case"
165 'name': fields.char('Name', size=64, required=True, translate=True),
166 'section_id': fields.many2one('crm.case.section', 'Sales Team'),
167 'object_id': fields.many2one('ir.model', 'Object Name'),
170 def _find_object_id(self, cr, uid, context=None):
171 """Finds id for case object"""
172 object_id = context and context.get('object_id', False) or False
173 ids = self.pool.get('ir.model').search(cr, uid, [('id', '=', object_id)])
174 return ids and ids[0] or False
177 'object_id' : _find_object_id
180 class crm_case_resource_type(osv.osv):
181 """ Resource Type of case """
182 _name = "crm.case.resource.type"
183 _description = "Campaign"
186 'name': fields.char('Campaign Name', size=64, required=True, translate=True),
187 'section_id': fields.many2one('crm.case.section', 'Sales Team'),
190 class crm_base(object):
191 """ Base utility mixin class for objects willing to manage their state.
192 Object subclassing this class should define the following colums:
193 - ``date_open`` (datetime field)
194 - ``date_closed`` (datetime field)
195 - ``user_id`` (many2one to res.users)
196 - ``partner_id`` (many2one to res.partner)
197 - ``state`` (selection field)
200 def onchange_partner_address_id(self, cr, uid, ids, add, email=False):
201 """ This function returns value of partner email based on Partner Address
202 :param add: Id of Partner's address
203 :param email: Partner's email ID
205 data = {'value': {'email_from': False, 'phone':False}}
207 address = self.pool.get('res.partner').browse(cr, uid, add)
208 data['value'] = {'email_from': address and address.email or False ,
209 'phone': address and address.phone or False}
210 if 'phone' not in self._columns:
211 del data['value']['phone']
214 def onchange_partner_id(self, cr, uid, ids, part, email=False):
215 """ This function returns value of partner address based on partner
216 :param part: Partner's id
217 :param email: Partner's email ID
221 addr = self.pool.get('res.partner').address_get(cr, uid, [part], ['contact'])
222 data.update(self.onchange_partner_address_id(cr, uid, ids, addr['contact'])['value'])
223 return {'value': data}
225 def case_open(self, cr, uid, ids, context=None):
227 cases = self.browse(cr, uid, ids, context=context)
229 values = {'active': True}
230 if case.state == 'draft':
231 values['date_open'] = fields.datetime.now()
233 values['user_id'] = uid
234 self.case_set(cr, uid, [case.id], 'open', values, context=context)
235 self.case_open_send_note(cr, uid, [case.id], context=context)
238 def case_close(self, cr, uid, ids, context=None):
240 self.case_set(cr, uid, ids, 'done', {'date_closed': fields.datetime.now()}, context=context)
241 self.case_close_send_note(cr, uid, ids, context=context)
244 def case_cancel(self, cr, uid, ids, context=None):
246 self.case_set(cr, uid, ids, 'cancel', {'active': True}, context=context)
247 self.case_cancel_send_note(cr, uid, ids, context=context)
250 def case_pending(self, cr, uid, ids, context=None):
251 """ Sets case as pending """
252 self.case_set(cr, uid, ids, 'pending', {'active': True}, context=context)
253 self.case_pending_send_note(cr, uid, ids, context=context)
256 def case_reset(self, cr, uid, ids, context=None):
257 """ Resets case as draft """
258 self.case_set(cr, uid, ids, 'draft', {'active': True}, context=context)
259 self.case_reset_send_note(cr, uid, ids, context=context)
262 def case_set(self, cr, uid, ids, state_name, update_values=None, context=None):
263 """ Generic method for setting case. This methods wraps the update
264 of the record, as well as call to _action and browse record
267 :params: state_name: the new value of the state, such as
269 :params: update_values: values that will be added with the state
270 update when writing values to the record.
272 cases = self.browse(cr, uid, ids, context=context)
273 cases[0].state # fill browse record cache, for _action having old and new values
274 if update_values is None:
276 update_values.update({'state': state_name})
277 self.write(cr, uid, ids, update_values, context=context)
278 self._action(cr, uid, cases, state_name, context=context)
280 def _action(self, cr, uid, cases, state_to, scrit=None, context=None):
283 context['state_to'] = state_to
284 rule_obj = self.pool.get('base.action.rule')
285 model_obj = self.pool.get('ir.model')
286 model_ids = model_obj.search(cr, uid, [('model','=',self._name)])
287 rule_ids = rule_obj.search(cr, uid, [('model_id','=',model_ids[0])])
288 return rule_obj._action(cr, uid, rule_ids, cases, scrit=scrit, context=context)
290 # ******************************
292 # ******************************
294 def case_get_note_msg_prefix(self, cr, uid, id, context=None):
297 def case_open_send_note(self, cr, uid, ids, context=None):
299 msg = _('%s has been <b>opened</b>.') % (self.case_get_note_msg_prefix(cr, uid, id, context=context))
300 self.message_append_note(cr, uid, [id], body=msg, context=context)
303 def case_close_send_note(self, cr, uid, ids, context=None):
305 msg = _('%s has been <b>closed</b>.') % (self.case_get_note_msg_prefix(cr, uid, id, context=context))
306 self.message_append_note(cr, uid, [id], body=msg, context=context)
309 def case_cancel_send_note(self, cr, uid, ids, context=None):
311 msg = _('%s has been <b>canceled</b>.') % (self.case_get_note_msg_prefix(cr, uid, id, context=context))
312 self.message_append_note(cr, uid, [id], body=msg, context=context)
315 def case_pending_send_note(self, cr, uid, ids, context=None):
317 msg = _('%s is now <b>pending</b>.') % (self.case_get_note_msg_prefix(cr, uid, id, context=context))
318 self.message_append_note(cr, uid, [id], body=msg, context=context)
321 def case_reset_send_note(self, cr, uid, ids, context=None):
323 msg = _('%s has been <b>renewed</b>.') % (self.case_get_note_msg_prefix(cr, uid, id, context=context))
324 self.message_append_note(cr, uid, [id], body=msg, context=context)
327 class crm_case(object):
328 """ Base utility mixin class for objects willing to manage their stages.
329 Object that inherit from this class should inherit from mailgate.thread
330 to have access to the mail gateway, as well as Chatter. Objects
331 subclassing this class should define the following colums:
332 - ``date_open`` (datetime field)
333 - ``date_closed`` (datetime field)
334 - ``user_id`` (many2one to res.users)
335 - ``partner_id`` (many2one to res.partner)
336 - ``stage_id`` (many2one to a stage definition model)
337 - ``state`` (selection field, related to the stage_id.state)
340 def _get_default_partner(self, cr, uid, context=None):
341 """ Gives id of partner for current user
342 :param context: if portal in context is false return false anyway
346 if not context.get('portal', False):
348 user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
349 if hasattr(user, 'partner_address_id') and user.partner_address_id:
350 return user.partner_address_id
351 return user.company_id.partner_id.id
353 def _get_default_email(self, cr, uid, context=None):
354 """ Gives default email address for current user
355 :param context: if portal in context is false return false anyway
357 if context and context.get('portal'):
359 user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
360 return user.user_email
362 def _get_default_user(self, cr, uid, context=None):
363 """ Gives current user id
364 :param context: if portal in context is false return false anyway
366 if context and context.get('portal'):
370 def onchange_partner_address_id(self, cr, uid, ids, add, email=False):
371 """ This function returns value of partner email based on Partner Address
372 :param add: Id of Partner's address
373 :param email: Partner's email ID
375 data = {'value': {'email_from': False, 'phone':False}}
377 address = self.pool.get('res.partner').browse(cr, uid, add)
378 data['value'] = {'email_from': address and address.email or False ,
379 'phone': address and address.phone or False}
380 if 'phone' not in self._columns:
381 del data['value']['phone']
384 def onchange_partner_id(self, cr, uid, ids, part, email=False):
385 """ This function returns value of partner address based on partner
386 :param part: Partner's id
387 :param email: Partner's email ID
391 addr = self.pool.get('res.partner').address_get(cr, uid, [part], ['contact'])
392 data.update(self.onchange_partner_address_id(cr, uid, ids, addr['contact'])['value'])
393 return {'value': data}
395 def _get_default_section_id(self, cr, uid, context=None):
396 """ Gives default section """
399 def _get_default_stage_id(self, cr, uid, context=None):
400 """ Gives default stage_id """
401 return self.stage_find(cr, uid, [], None, [('state', '=', 'draft')], context=context)
403 def stage_find(self, cr, uid, cases, section_id, domain=[], order='sequence', context=None):
404 """ Find stage, with a given (optional) domain on the search,
405 ordered by the order parameter. If several stages match the
406 search criterions, the first one will be returned, according
407 to the requested search order.
408 This method is meant to be overriden by subclasses. That way
409 specific behaviors can be achieved for every class inheriting
412 :param cases: browse_record of cases
413 :param section_id: section limitating the search, given for
414 a generic search (for example default search).
415 A section models concepts such as Sales team
416 (for CRM), ou departments (for HR).
417 :param domain: a domain on the search of stages
418 :param order: order of the search
422 def stage_set_with_state_name(self, cr, uid, cases, state_name, context=None):
423 """ Set a new stage, with a state_name instead of a stage_id
424 :param cases: browse_record of cases
426 if isinstance(cases, (int, long)):
427 cases = self.browse(cr, uid, cases, context=context)
429 stage_id = self.stage_find(cr, uid, [case], None, [('state', '=', state_name)], context=context)
431 self.stage_set(cr, uid, [case.id], stage_id, context=context)
434 def stage_set(self, cr, uid, ids, stage_id, context=None):
435 """ Set the new stage. This methods is the right method to call
436 when changing states. It also checks whether an onchange is
437 defined, and execute it.
440 if hasattr(self, 'onchange_stage_id'):
441 value = self.onchange_stage_id(cr, uid, ids, stage_id, context=context)['value']
442 value['stage_id'] = stage_id
443 return self.write(cr, uid, ids, value, context=context)
445 def stage_change(self, cr, uid, ids, op, order, context=None):
446 """ Change the stage and take the next one, based on a condition
447 writen for the 'sequence' field and an operator. This methods
448 checks whether the case has a current stage, and takes its
449 sequence. Otherwise, a default 0 sequence is chosen and this
450 method will therefore choose the first available stage.
451 For example if op is '>' and current stage has a sequence of
452 10, this will call stage_find, with [('sequence', '>', '10')].
454 for case in self.browse(cr, uid, ids, context=context):
457 seq = case.stage_id.sequence or 0
459 next_stage_id = self.stage_find(cr, uid, [case], None, [('sequence', op, seq)],order, context=context)
461 return self.stage_set(cr, uid, [case.id], next_stage_id, context=context)
464 def stage_next(self, cr, uid, ids, context=None):
465 """ This function computes next stage for case from its current stage
466 using available stage for that case type
468 return self.stage_change(cr, uid, ids, '>','sequence', context)
470 def stage_previous(self, cr, uid, ids, context=None):
471 """ This function computes previous stage for case from its current
472 stage using available stage for that case type
474 return self.stage_change(cr, uid, ids, '<', 'sequence desc', context)
476 def copy(self, cr, uid, id, default=None, context=None):
477 """ Overrides orm copy method to avoid copying messages,
478 as well as date_closed and date_open columns if they
483 if hasattr(self, '_columns'):
484 if self._columns.get('date_closed'):
485 default.update({ 'date_closed': False, })
486 if self._columns.get('date_open'):
487 default.update({ 'date_open': False })
488 return super(crm_case, self).copy(cr, uid, id, default, context=context)
490 def case_escalate(self, cr, uid, ids, context=None):
491 """ Escalates case to parent level """
492 cases = self.browse(cr, uid, ids, context=context)
493 cases[0].state # fill browse record cache, for _action having old and new values
495 data = {'active': True}
496 if case.section_id.parent_id:
497 data['section_id'] = case.section_id.parent_id.id
498 if case.section_id.parent_id.change_responsible:
499 if case.section_id.parent_id.user_id:
500 data['user_id'] = case.section_id.parent_id.user_id.id
502 raise osv.except_osv(_('Error !'), _('You can not escalate, you are already at the top level regarding your sales-team category.'))
503 self.write(cr, uid, [case.id], data, context=context)
504 case.case_escalate_send_note(case.section_id.parent_id, context=context)
505 cases = self.browse(cr, uid, ids, context=context)
506 self._action(cr, uid, cases, 'escalate', context=context)
509 def case_open(self, cr, uid, ids, context=None):
511 cases = self.browse(cr, uid, ids, context=context)
513 data = {'active': True}
514 if case.stage_id and case.stage_id.state == 'draft':
515 data['date_open'] = fields.datetime.now()
517 data['user_id'] = uid
518 self.case_set(cr, uid, [case.id], 'open', data, context=context)
519 self.case_open_send_note(cr, uid, [case.id], context=context)
522 def case_close(self, cr, uid, ids, context=None):
524 self.case_set(cr, uid, ids, 'done', {'active': True, 'date_closed': fields.datetime.now()}, context=context)
525 self.case_close_send_note(cr, uid, ids, context=context)
528 def case_cancel(self, cr, uid, ids, context=None):
530 self.case_set(cr, uid, ids, 'cancel', {'active': True}, context=context)
531 self.case_cancel_send_note(cr, uid, ids, context=context)
534 def case_pending(self, cr, uid, ids, context=None):
535 """ Set case as pending """
536 self.case_set(cr, uid, ids, 'pending', {'active': True}, context=context)
537 self.case_pending_send_note(cr, uid, ids, context=context)
540 def case_reset(self, cr, uid, ids, context=None):
541 """ Resets case as draft """
542 self.case_set(cr, uid, ids, 'draft', {'active': True}, context=context)
543 self.case_reset_send_note(cr, uid, ids, context=context)
546 def case_set(self, cr, uid, ids, new_state_name=None, values_to_update=None, new_stage_id=None, context=None):
548 cases = self.browse(cr, uid, ids, context=context)
549 cases[0].state # fill browse record cache, for _action having old and new values
550 # 1. update the stage
552 self.stage_set_with_state_name(cr, uid, cases, new_state_name, context=context)
553 elif not (new_stage_id is None):
554 self.stage_set(cr, uid, ids, new_stage_id, context=context)
557 self.write(cr, uid, ids, values_to_update, context=context)
558 # 3. call _action for base action rule
560 self._action(cr, uid, cases, new_state_name, context=context)
561 elif not (new_stage_id is None):
562 new_state_name = self.read(cr, uid, ids, ['state'], context=context)[0]['state']
563 self._action(cr, uid, cases, new_state_name, context=context)
566 def _action(self, cr, uid, cases, state_to, scrit=None, context=None):
569 context['state_to'] = state_to
570 rule_obj = self.pool.get('base.action.rule')
573 model_obj = self.pool.get('ir.model')
574 model_ids = model_obj.search(cr, uid, [('model','=',self._name)], context=context)
575 rule_ids = rule_obj.search(cr, uid, [('model_id','=',model_ids[0])], context=context)
576 return rule_obj._action(cr, uid, rule_ids, cases, scrit=scrit, context=context)
578 def remind_partner(self, cr, uid, ids, context=None, attach=False):
579 return self.remind_user(cr, uid, ids, context, attach,
582 def remind_user(self, cr, uid, ids, context=None, attach=False, destination=True):
583 mail_message = self.pool.get('mail.message')
584 for case in self.browse(cr, uid, ids, context=context):
585 if not destination and not case.email_from:
587 if not case.user_id.user_email:
589 if destination and case.section_id.user_id:
590 case_email = case.section_id.user_id.user_email
592 case_email = case.user_id.user_email
595 dest = case.user_id.user_email or ""
596 body = case.description or ""
597 for message in case.message_ids:
598 if message.email_from and message.body_text:
599 body = message.body_text
603 src, dest = dest, case.email_from
604 if body and case.user_id.signature:
606 body += '\n\n%s' % (case.user_id.signature)
608 body = '\n\n%s' % (case.user_id.signature)
610 body = self.format_body(body)
615 attach_ids = self.pool.get('ir.attachment').search(cr, uid, [('res_model', '=', self._name), ('res_id', '=', case.id)])
616 attach_to_send = self.pool.get('ir.attachment').read(cr, uid, attach_ids, ['datas_fname', 'datas'])
617 attach_to_send = dict(map(lambda x: (x['datas_fname'], base64.decodestring(x['datas'])), attach_to_send))
620 subject = "Reminder: [%s] %s" % (str(case.id), case.name, )
621 mail_message.schedule_with_attach(cr, uid,
627 reply_to=case.section_id.reply_to,
629 attachments=attach_to_send,
634 def _check(self, cr, uid, ids=False, context=None):
635 """Function called by the scheduler to process cases for date actions
636 Only works on not done and cancelled cases
638 cr.execute('select * from crm_case \
639 where (date_action_last<%s or date_action_last is null) \
640 and (date_action_next<=%s or date_action_next is null) \
641 and state not in (\'cancel\',\'done\')',
642 (time.strftime("%Y-%m-%d %H:%M:%S"),
643 time.strftime('%Y-%m-%d %H:%M:%S')))
645 ids2 = map(lambda x: x[0], cr.fetchall() or [])
646 cases = self.browse(cr, uid, ids2, context=context)
647 return self._action(cr, uid, cases, False, context=context)
649 def format_body(self, body):
650 return self.pool.get('base.action.rule').format_body(body)
652 def format_mail(self, obj, body):
653 return self.pool.get('base.action.rule').format_mail(obj, body)
655 def message_thread_followers(self, cr, uid, ids, context=None):
657 for case in self.browse(cr, uid, ids, context=context):
660 l.append(case.email_cc)
661 if case.user_id and case.user_id.user_email:
662 l.append(case.user_id.user_email)
666 # ******************************
668 # ******************************
670 def case_get_note_msg_prefix(self, cr, uid, id, context=None):
673 def case_open_send_note(self, cr, uid, ids, context=None):
675 msg = _('%s has been <b>opened</b>.') % (self.case_get_note_msg_prefix(cr, uid, id, context=context))
676 self.message_append_note(cr, uid, [id], body=msg, context=context)
679 def case_close_send_note(self, cr, uid, ids, context=None):
681 msg = _('%s has been <b>closed</b>.') % (self.case_get_note_msg_prefix(cr, uid, id, context=context))
682 self.message_append_note(cr, uid, [id], body=msg, context=context)
685 def case_cancel_send_note(self, cr, uid, ids, context=None):
687 msg = _('%s has been <b>canceled</b>.') % (self.case_get_note_msg_prefix(cr, uid, id, context=context))
688 self.message_append_note(cr, uid, [id], body=msg, context=context)
691 def case_pending_send_note(self, cr, uid, ids, context=None):
693 msg = _('%s is now <b>pending</b>.') % (self.case_get_note_msg_prefix(cr, uid, id, context=context))
694 self.message_append_note(cr, uid, [id], body=msg, context=context)
697 def case_reset_send_note(self, cr, uid, ids, context=None):
699 msg = _('%s has been <b>renewed</b>.') % (self.case_get_note_msg_prefix(cr, uid, id, context=context))
700 self.message_append_note(cr, uid, [id], body=msg, context=context)
703 def case_escalate_send_note(self, cr, uid, ids, new_section=None, context=None):
706 msg = '%s has been <b>escalated</b> to <b>%s</b>.' % (self.case_get_note_msg_prefix(cr, uid, id, context=context), new_section.name)
708 msg = '%s has been <b>escalated</b>.' % (self.case_get_note_msg_prefix(cr, uid, id, context=context))
709 self.message_append_note(cr, uid, [id], 'System Notification', msg, context=context)
712 def _links_get(self, cr, uid, context=None):
713 """Gets links value for reference field"""
714 obj = self.pool.get('res.request.link')
715 ids = obj.search(cr, uid, [])
716 res = obj.read(cr, uid, ids, ['object', 'name'], context)
717 return [(r['object'], r['name']) for r in res]
719 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: