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 ##############################################################################
22 from osv import fields
23 from tools.translate import _
25 class base_stage(object):
26 """ Base utility mixin class for objects willing to manage their stages.
27 Object that inherit from this class should inherit from mailgate.thread
28 to have access to the mail gateway, as well as Chatter. Objects
29 subclassing this class should define the following colums:
30 - ``date_open`` (datetime field)
31 - ``date_closed`` (datetime field)
32 - ``user_id`` (many2one to res.users)
33 - ``partner_id`` (many2one to res.partner)
34 - ``stage_id`` (many2one to a stage definition model)
35 - ``state`` (selection field, related to the stage_id.state)
38 def _get_default_partner(self, cr, uid, context=None):
39 """ Gives id of partner for current user
40 :param context: if portal in context is false return false anyway
44 if not context or not context.get('portal'):
46 user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
47 if hasattr(user, 'partner_address_id') and user.partner_address_id:
48 return user.partner_address_id
49 return user.company_id.partner_id.id
51 def _get_default_email(self, cr, uid, context=None):
52 """ Gives default email address for current user
53 :param context: if portal in context is false return false anyway
57 if not context or not context.get('portal'):
59 user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
60 return user.user_email
62 def _get_default_user(self, cr, uid, context=None):
63 """ Gives current user id
64 :param context: if portal in context is false return false anyway
68 if not context or not context.get('portal'):
72 def onchange_partner_address_id(self, cr, uid, ids, add, email=False):
73 """ This function returns value of partner email based on Partner Address
74 :param add: Id of Partner's address
75 :param email: Partner's email ID
77 data = {'value': {'email_from': False, 'phone':False}}
79 address = self.pool.get('res.partner').browse(cr, uid, add)
80 data['value'] = {'email_from': address and address.email or False ,
81 'phone': address and address.phone or False}
82 if 'phone' not in self._columns:
83 del data['value']['phone']
86 def onchange_partner_id(self, cr, uid, ids, part, email=False):
87 """ This function returns value of partner address based on partner
88 :param part: Partner's id
89 :param email: Partner's email ID
93 addr = self.pool.get('res.partner').address_get(cr, uid, [part], ['contact'])
94 data.update(self.onchange_partner_address_id(cr, uid, ids, addr['contact'])['value'])
95 return {'value': data}
97 def _get_default_section_id(self, cr, uid, context=None):
98 """ Gives default section """
101 def _get_default_stage_id(self, cr, uid, context=None):
102 """ Gives default stage_id """
103 return self.stage_find(cr, uid, [], None, [('state', '=', 'draft')], context=context)
105 def stage_find(self, cr, uid, cases, section_id, domain=[], order='sequence', context=None):
106 """ Find stage, with a given (optional) domain on the search,
107 ordered by the order parameter. If several stages match the
108 search criterions, the first one will be returned, according
109 to the requested search order.
110 This method is meant to be overriden by subclasses. That way
111 specific behaviors can be achieved for every class inheriting
114 :param cases: browse_record of cases
115 :param section_id: section limitating the search, given for
116 a generic search (for example default search).
117 A section models concepts such as Sales team
118 (for CRM), ou departments (for HR).
119 :param domain: a domain on the search of stages
120 :param order: order of the search
124 def stage_set_with_state_name(self, cr, uid, cases, state_name, context=None):
125 """ Set a new stage, with a state_name instead of a stage_id
126 :param cases: browse_record of cases
128 if isinstance(cases, (int, long)):
129 cases = self.browse(cr, uid, cases, context=context)
131 stage_id = self.stage_find(cr, uid, [case], None, [('state', '=', state_name)], context=context)
133 self.stage_set(cr, uid, [case.id], stage_id, context=context)
136 def stage_set(self, cr, uid, ids, stage_id, context=None):
137 """ Set the new stage. This methods is the right method to call
138 when changing states. It also checks whether an onchange is
139 defined, and execute it.
142 if hasattr(self, 'onchange_stage_id'):
143 value = self.onchange_stage_id(cr, uid, ids, stage_id, context=context)['value']
144 value['stage_id'] = stage_id
145 self.stage_set_send_note(cr, uid, ids, stage_id, context=context)
146 return self.write(cr, uid, ids, value, context=context)
148 def stage_change(self, cr, uid, ids, op, order, context=None):
149 """ Change the stage and take the next one, based on a condition
150 writen for the 'sequence' field and an operator. This methods
151 checks whether the case has a current stage, and takes its
152 sequence. Otherwise, a default 0 sequence is chosen and this
153 method will therefore choose the first available stage.
154 For example if op is '>' and current stage has a sequence of
155 10, this will call stage_find, with [('sequence', '>', '10')].
157 for case in self.browse(cr, uid, ids, context=context):
160 seq = case.stage_id.sequence or 0
162 next_stage_id = self.stage_find(cr, uid, [case], None, [('sequence', op, seq)],order, context=context)
164 return self.stage_set(cr, uid, [case.id], next_stage_id, context=context)
167 def stage_next(self, cr, uid, ids, context=None):
168 """ This function computes next stage for case from its current stage
169 using available stage for that case type
171 return self.stage_change(cr, uid, ids, '>','sequence', context)
173 def stage_previous(self, cr, uid, ids, context=None):
174 """ This function computes previous stage for case from its current
175 stage using available stage for that case type
177 return self.stage_change(cr, uid, ids, '<', 'sequence desc', context)
179 def copy(self, cr, uid, id, default=None, context=None):
180 """ Overrides orm copy method to avoid copying messages,
181 as well as date_closed and date_open columns if they
186 if hasattr(self, '_columns'):
187 if self._columns.get('date_closed'):
188 default.update({ 'date_closed': False, })
189 if self._columns.get('date_open'):
190 default.update({ 'date_open': False })
191 return super(base_stage, self).copy(cr, uid, id, default, context=context)
193 def case_escalate(self, cr, uid, ids, context=None):
194 """ Escalates case to parent level """
195 cases = self.browse(cr, uid, ids, context=context)
196 cases[0].state # fill browse record cache, for _action having old and new values
198 data = {'active': True}
199 if case.section_id.parent_id:
200 data['section_id'] = case.section_id.parent_id.id
201 if case.section_id.parent_id.change_responsible:
202 if case.section_id.parent_id.user_id:
203 data['user_id'] = case.section_id.parent_id.user_id.id
205 raise osv.except_osv(_('Error !'), _('You can not escalate, you are already at the top level regarding your sales-team category.'))
206 self.write(cr, uid, [case.id], data, context=context)
207 case.case_escalate_send_note(case.section_id.parent_id, context=context)
208 cases = self.browse(cr, uid, ids, context=context)
209 self._action(cr, uid, cases, 'escalate', context=context)
212 def case_open(self, cr, uid, ids, context=None):
214 cases = self.browse(cr, uid, ids, context=context)
216 data = {'active': True}
217 if case.stage_id and case.stage_id.state == 'draft':
218 data['date_open'] = fields.datetime.now()
220 data['user_id'] = uid
221 self.case_set(cr, uid, [case.id], 'open', data, context=context)
222 self.case_open_send_note(cr, uid, [case.id], context=context)
225 def case_close(self, cr, uid, ids, context=None):
227 self.case_set(cr, uid, ids, 'done', {'active': True, 'date_closed': fields.datetime.now()}, context=context)
228 self.case_close_send_note(cr, uid, ids, context=context)
231 def case_cancel(self, cr, uid, ids, context=None):
233 self.case_set(cr, uid, ids, 'cancel', {'active': True}, context=context)
234 self.case_cancel_send_note(cr, uid, ids, context=context)
237 def case_pending(self, cr, uid, ids, context=None):
238 """ Set case as pending """
239 self.case_set(cr, uid, ids, 'pending', {'active': True}, context=context)
240 self.case_pending_send_note(cr, uid, ids, context=context)
243 def case_reset(self, cr, uid, ids, context=None):
244 """ Resets case as draft """
245 self.case_set(cr, uid, ids, 'draft', {'active': True}, context=context)
246 self.case_reset_send_note(cr, uid, ids, context=context)
249 def case_set(self, cr, uid, ids, new_state_name=None, values_to_update=None, new_stage_id=None, context=None):
251 cases = self.browse(cr, uid, ids, context=context)
252 cases[0].state # fill browse record cache, for _action having old and new values
253 # 1. update the stage
255 self.stage_set_with_state_name(cr, uid, cases, new_state_name, context=context)
256 elif not (new_stage_id is None):
257 self.stage_set(cr, uid, ids, new_stage_id, context=context)
260 self.write(cr, uid, ids, values_to_update, context=context)
261 # 3. call _action for base action rule
263 self._action(cr, uid, cases, new_state_name, context=context)
264 elif not (new_stage_id is None):
265 new_state_name = self.read(cr, uid, ids, ['state'], context=context)[0]['state']
266 self._action(cr, uid, cases, new_state_name, context=context)
269 def _action(self, cr, uid, cases, state_to, scrit=None, context=None):
272 context['state_to'] = state_to
273 rule_obj = self.pool.get('base.action.rule')
276 model_obj = self.pool.get('ir.model')
277 model_ids = model_obj.search(cr, uid, [('model','=',self._name)], context=context)
278 rule_ids = rule_obj.search(cr, uid, [('model_id','=',model_ids[0])], context=context)
279 return rule_obj._action(cr, uid, rule_ids, cases, scrit=scrit, context=context)
281 def remind_partner(self, cr, uid, ids, context=None, attach=False):
282 return self.remind_user(cr, uid, ids, context, attach,
285 def remind_user(self, cr, uid, ids, context=None, attach=False, destination=True):
286 mail_message = self.pool.get('mail.message')
287 for case in self.browse(cr, uid, ids, context=context):
288 if not destination and not case.email_from:
290 if not case.user_id.user_email:
292 if destination and case.section_id.user_id:
293 case_email = case.section_id.user_id.user_email
295 case_email = case.user_id.user_email
298 dest = case.user_id.user_email or ""
299 body = case.description or ""
300 for message in case.message_ids:
301 if message.email_from and message.body_text:
302 body = message.body_text
306 src, dest = dest, case.email_from
307 if body and case.user_id.signature:
309 body += '\n\n%s' % (case.user_id.signature)
311 body = '\n\n%s' % (case.user_id.signature)
313 body = self.format_body(body)
318 attach_ids = self.pool.get('ir.attachment').search(cr, uid, [('res_model', '=', self._name), ('res_id', '=', case.id)])
319 attach_to_send = self.pool.get('ir.attachment').read(cr, uid, attach_ids, ['datas_fname', 'datas'])
320 attach_to_send = dict(map(lambda x: (x['datas_fname'], base64.decodestring(x['datas'])), attach_to_send))
323 subject = "Reminder: [%s] %s" % (str(case.id), case.name, )
324 mail_message.schedule_with_attach(cr, uid,
330 reply_to=case.section_id.reply_to,
332 attachments=attach_to_send,
337 def _check(self, cr, uid, ids=False, context=None):
338 """ Function called by the scheduler to process cases for date actions.
339 Must be overriden by inheriting classes.
343 def format_body(self, body):
344 return self.pool.get('base.action.rule').format_body(body)
346 def format_mail(self, obj, body):
347 return self.pool.get('base.action.rule').format_mail(obj, body)
349 def message_thread_followers(self, cr, uid, ids, context=None):
351 for case in self.browse(cr, uid, ids, context=context):
354 l.append(case.email_cc)
355 if case.user_id and case.user_id.user_email:
356 l.append(case.user_id.user_email)
360 # ******************************
362 # ******************************
364 def case_get_note_msg_prefix(self, cr, uid, id, context=None):
367 def stage_set_send_note(self, cr, uid, ids, stage_id, context=None):
368 """ Send a notification when the stage changes. This method has
369 to be overriden, because each document will have its particular
370 behavior and/or stage model (such as project.task.type or
375 def case_open_send_note(self, cr, uid, ids, context=None):
377 msg = _('%s has been <b>opened</b>.') % (self.case_get_note_msg_prefix(cr, uid, id, context=context))
378 self.message_append_note(cr, uid, [id], body=msg, context=context)
381 def case_close_send_note(self, cr, uid, ids, context=None):
383 msg = _('%s has been <b>closed</b>.') % (self.case_get_note_msg_prefix(cr, uid, id, context=context))
384 self.message_append_note(cr, uid, [id], body=msg, context=context)
387 def case_cancel_send_note(self, cr, uid, ids, context=None):
389 msg = _('%s has been <b>canceled</b>.') % (self.case_get_note_msg_prefix(cr, uid, id, context=context))
390 self.message_append_note(cr, uid, [id], body=msg, context=context)
393 def case_pending_send_note(self, cr, uid, ids, context=None):
395 msg = _('%s is now <b>pending</b>.') % (self.case_get_note_msg_prefix(cr, uid, id, context=context))
396 self.message_append_note(cr, uid, [id], body=msg, context=context)
399 def case_reset_send_note(self, cr, uid, ids, context=None):
401 msg = _('%s has been <b>renewed</b>.') % (self.case_get_note_msg_prefix(cr, uid, id, context=context))
402 self.message_append_note(cr, uid, [id], body=msg, context=context)
405 def case_escalate_send_note(self, cr, uid, ids, new_section=None, context=None):
408 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)
410 msg = '%s has been <b>escalated</b>.' % (self.case_get_note_msg_prefix(cr, uid, id, context=context))
411 self.message_append_note(cr, uid, [id], 'System Notification', msg, context=context)