1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
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 ##############################################################################
30 from datetime import datetime
31 from datetime import timedelta
32 from osv import fields
35 from osv.orm import except_orm
36 from tools.translate import _
44 ('cancel', 'Cancelled'),
49 AVAILABLE_PRIORITIES = [
56 class crm_case_section(osv.osv):
57 _name = "crm.case.section"
58 _description = "Sales Teams"
61 'name': fields.char('Sales Team',size=64, required=True, translate=True),
62 'code': fields.char('Code',size=8),
63 'active': fields.boolean('Active', help="If the active field is set to true, it will allow you to hide the sales team without removing it."),
64 'allow_unlink': fields.boolean('Allow Delete', help="Allows to delete non draft cases"),
65 'user_id': fields.many2one('res.users', 'Responsible User'),
66 'reply_to': fields.char('Reply-To', size=64, help="The email address put in the 'Reply-To' of all emails sent by Open ERP about cases in this sales team"),
67 'parent_id': fields.many2one('crm.case.section', 'Parent Section'),
68 'child_ids': fields.one2many('crm.case.section', 'parent_id', 'Child Sections'),
71 'active': lambda *a: 1,
72 'allow_unlink': lambda *a: 1,
75 ('code_uniq', 'unique (code)', 'The code of the section must be unique !')
77 def _check_recursion(self, cr, uid, ids):
80 cr.execute('select distinct parent_id from crm_case_section where id =ANY(%s)',(ids,))
81 ids = filter(None, map(lambda x:x[0], cr.fetchall()))
87 (_check_recursion, 'Error ! You cannot create recursive sections.', ['parent_id'])
89 def name_get(self, cr, uid, ids, context={}):
92 reads = self.read(cr, uid, ids, ['name','parent_id'], context)
96 if record['parent_id']:
97 name = record['parent_id'][1]+' / '+name
98 res.append((record['id'], name))
102 class crm_case_categ(osv.osv):
103 _name = "crm.case.categ"
104 _description = "Category of case"
107 'name': fields.char('Case Category Name', size=64, required=True, translate=True),
108 'section_id': fields.many2one('crm.case.section', 'Sales Team'),
109 'object_id': fields.many2one('ir.model','Object Name'),
111 def _find_object_id(self, cr, uid, context=None):
112 object_id = context and context.get('object_id', False) or False
113 ids =self.pool.get('ir.model').search(cr, uid, [('model', '=', object_id)])
114 return ids and ids[0]
116 'object_id' : _find_object_id
121 class crm_case_resource_type(osv.osv):
122 _name = "crm.case.resource.type"
123 _description = "Resource Type of case"
126 'name': fields.char('Case Resource Type', size=64, required=True, translate=True),
127 'section_id': fields.many2one('crm.case.section', 'Sales Team'),
128 'object_id': fields.many2one('ir.model','Object Name'),
130 def _find_object_id(self, cr, uid, context=None):
131 object_id = context and context.get('object_id', False) or False
132 ids =self.pool.get('ir.model').search(cr, uid, [('model', '=', object_id)])
133 return ids and ids[0]
135 'object_id' : _find_object_id
137 crm_case_resource_type()
140 class crm_case_stage(osv.osv):
141 _name = "crm.case.stage"
142 _description = "Stage of case"
146 'name': fields.char('Stage Name', size=64, required=True, translate=True),
147 'section_id': fields.many2one('crm.case.section', 'Sales Team'),
148 'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of case stages."),
149 'object_id': fields.many2one('ir.model','Object Name'),
150 'probability': fields.float('Probability (%)', required=True),
151 'on_change': fields.boolean('Change Probability Automatically',help="Change Probability on next and previous stages."),
152 'requirements': fields.text('Requirements')
154 def _find_object_id(self, cr, uid, context=None):
155 object_id = context and context.get('object_id', False) or False
156 ids =self.pool.get('ir.model').search(cr, uid, [('model', '=', object_id)])
157 return ids and ids[0]
159 'sequence': lambda *args: 1,
160 'probability': lambda *args: 0.0,
161 'object_id' : _find_object_id
166 def _links_get(self, cr, uid, context={}):
167 obj = self.pool.get('res.request.link')
168 ids = obj.search(cr, uid, [])
169 res = obj.read(cr, uid, ids, ['object', 'name'], context)
170 return [(r['object'], r['name']) for r in res]
173 class crm_case(osv.osv):
175 _description = "Case"
177 def _email_last(self, cursor, user, ids, name, arg, context=None):
179 for case in self.browse(cursor, user, ids):
180 if case.history_line:
181 res[case.id] = case.history_line[0].description
186 def copy(self, cr, uid, id, default=None, context={}):
187 if not default: default = {}
188 default.update( {'state':'draft', 'id':False})
189 return super(crm_case, self).copy(cr, uid, id, default, context)
191 def _get_log_ids(self, cr, uid, ids, field_names, arg, context={}):
194 model_obj = self.pool.get('ir.model')
195 if 'history_line' in field_names:
196 history_obj = self.pool.get('crm.case.history')
197 name = 'history_line'
198 if 'log_ids' in field_names:
199 history_obj = self.pool.get('crm.case.log')
203 for case in self.browse(cr, uid, ids, context):
204 model_ids = model_obj.search(cr, uid, [('model','=',case._name)])
205 history_ids = history_obj.search(cr, uid, [('model_id','=',model_ids[0]),('res_id','=',case.id)])
207 result[case.id] = {name:history_ids}
209 result[case.id] = {name:[]}
213 'id': fields.integer('ID', readonly=True),
214 'name': fields.char('Description', size=1024, required=True),
215 'active': fields.boolean('Active', help="If the active field is set to true, it will allow you to hide the case without removing it."),
216 'description': fields.text('Description'),
217 'section_id': fields.many2one('crm.case.section', 'Sales Team', select=True, help='Sales team to which Case belongs to. Define Responsible user and Email account for mail gateway.'),
218 'email_from': fields.char('Email', size=128, help="These people will receive email."),
219 'email_cc': fields.text('Watchers Emails', size=252 , help="These people will receive a copy of the future" \
220 " communication between partner and users by email"),
221 'probability': fields.float('Probability'),
222 'email_last': fields.function(_email_last, method=True,
223 string='Latest E-Mail', type='text'),
224 'partner_id': fields.many2one('res.partner', 'Partner'),
225 'partner_address_id': fields.many2one('res.partner.address', 'Partner Contact', domain="[('partner_id','=',partner_id)]"),
226 'create_date': fields.datetime('Creation Date' ,readonly=True),
227 'write_date': fields.datetime('Update Date' ,readonly=True),
228 'date_deadline': fields.date('Deadline'),
229 'user_id': fields.many2one('res.users', 'Responsible'),
230 'history_line': fields.function(_get_log_ids, method=True, type='one2many', multi="history_line", relation="crm.case.history", string="Communication"),
231 'log_ids': fields.function(_get_log_ids, method=True, type='one2many', multi="log_ids", relation="crm.case.log", string="Logs History"),
232 'stage_id': fields.many2one ('crm.case.stage', 'Stage', domain="[('section_id','=',section_id),('object_id.model', '=', 'crm.opportunity')]"),
233 'state': fields.selection(AVAILABLE_STATES, 'State', size=16, readonly=True,
234 help='The state is set to \'Draft\', when a case is created.\
235 \nIf the case is in progress the state is set to \'Open\'.\
236 \nWhen the case is over, the state is set to \'Done\'.\
237 \nIf the case needs to be reviewed then the state is set to \'Pending\'.'),
238 'company_id': fields.many2one('res.company','Company'),
240 def _get_default_partner_address(self, cr, uid, context):
241 if not context.get('portal',False):
243 return self.pool.get('res.users').browse(cr, uid, uid, context).address_id.id
244 def _get_default_partner(self, cr, uid, context):
245 if not context.get('portal',False):
247 user = self.pool.get('res.users').browse(cr, uid, uid, context)
248 if not user.address_id:
250 return user.address_id.partner_id.id
251 def _get_default_email(self, cr, uid, context):
252 if not context.get('portal',False):
254 user = self.pool.get('res.users').browse(cr, uid, uid, context)
255 if not user.address_id:
257 return user.address_id.email
258 def _get_default_user(self, cr, uid, context):
259 if context.get('portal', False):
263 def _get_section(self, cr, uid, context):
264 user = self.pool.get('res.users').browse(cr, uid, uid,context=context)
265 return user.context_section_id.id or False
268 'active': lambda *a: 1,
269 'user_id': _get_default_user,
270 'partner_id': _get_default_partner,
271 'partner_address_id': _get_default_partner_address,
272 'email_from': _get_default_email,
273 'state': lambda *a: 'draft',
274 'date_deadline': lambda *a:(datetime.today() + timedelta(days=3)).strftime('%Y-%m-%d %H:%M:%S'),
275 'section_id': _get_section,
276 'company_id': lambda s,cr,uid,c: s.pool.get('res.company')._company_default_get(cr, uid, 'crm.case', context=c),
278 _order = 'date_deadline desc, create_date desc,id desc'
280 def unlink(self, cr, uid, ids, context={}):
281 for case in self.browse(cr, uid, ids, context):
282 if (not case.section_id.allow_unlink) and (case.state <> 'draft'):
283 raise osv.except_osv(_('Warning !'),
284 _('You can not delete this case. You should better cancel it.'))
285 return super(crm_case, self).unlink(cr, uid, ids, context)
287 def stage_next(self, cr, uid, ids, context={}):
288 s = self.get_stage_dict(cr, uid, ids, context=context)
289 for case in self.browse(cr, uid, ids, context):
290 section = (case.section_id.id or False)
292 st = case.stage_id.id or False
294 self.write(cr, uid, [case.id], {'stage_id': s[section][st]})
297 def get_stage_dict(self, cr, uid, ids, context={}):
298 sid = self.pool.get('crm.case.stage').search(cr, uid, [('object_id.model', '=', self._name)], context=context)
301 for stage in self.pool.get('crm.case.stage').browse(cr, uid, sid, context=context):
302 section = stage.section_id.id or False
303 s.setdefault(section, {})
304 s[section][previous.get(section, False)] = stage.id
305 previous[section] = stage.id
308 def stage_previous(self, cr, uid, ids, context={}):
309 s = self.get_stage_dict(cr, uid, ids, context=context)
310 for case in self.browse(cr, uid, ids, context):
311 section = (case.section_id.id or False)
313 st = case.stage_id.id or False
314 s[section] = dict([(v, k) for (k, v) in s[section].iteritems()])
316 self.write(cr, uid, [case.id], {'stage_id': s[section][st]})
319 def history(self, cr, uid, ids, keyword, history=False, email=False, details=None, email_from=False, context={}):
320 cases = self.browse(cr, uid, ids, context=context)
321 return self._history(cr, uid, cases, keyword=keyword,\
322 history=history, email=email, details=details, email_from=email_from, \
325 def __history(self, cr, uid, cases, keyword, history=False, email=False, details=None, email_from=False, context={}):
326 model_obj = self.pool.get('ir.model')
327 if email and type(email) == type([]):
328 email = ','.join(email)
329 if email_from and type(email_from) == type([]):
330 email_from = ','.join(email_from)
333 model_ids = model_obj.search(cr, uid, [('model','=',case._name)])
337 'date': time.strftime('%Y-%m-%d %H:%M:%S'),
338 'model_id' : model_ids and model_ids[0] or False,
340 'section_id': case.section_id.id
342 obj = self.pool.get('crm.case.log')
344 obj = self.pool.get('crm.case.history')
345 data['description'] = details or case.description
346 data['email_to'] = email or \
347 (case.user_id and case.user_id.address_id and \
348 case.user_id.address_id.email) or False
349 data['email_from'] = email_from or \
350 (case.user_id and case.user_id.address_id and \
351 case.user_id.address_id.email) or False
352 res = obj.create(cr, uid, data, context)
356 def create(self, cr, uid, *args, **argv):
357 res = super(crm_case, self).create(cr, uid, *args, **argv)
358 cases = self.browse(cr, uid, [res])
359 cases[0].state # to fill the browse record cache
360 self._action(cr,uid, cases, 'draft')
363 def add_reply(self, cursor, user, ids, context=None):
364 for case in self.browse(cursor, user, ids, context=context):
366 description = case.email_last
367 self.write(cursor, user, case.id, {
368 'description': '> ' + description.replace('\n','\n> '),
372 def case_log(self, cr, uid, ids,context={}, email=False, *args):
373 cases = self.browse(cr, uid, ids)
374 self.__history(cr, uid, cases, _('Historize'), history=True, email=email)
375 return self.write(cr, uid, ids, {'description': False, 'som': False,
378 def case_log_reply(self, cr, uid, ids, context={}, email=False, *args):
379 cases = self.browse(cr, uid, ids)
381 if not case.email_from:
382 raise osv.except_osv(_('Error!'),
383 _('You must put a Partner eMail to use this action!'))
385 raise osv.except_osv(_('Error!'),
386 _('You must define a responsible user for this case in order to use this action!'))
387 if not case.description:
388 raise osv.except_osv(_('Error!'),
389 _('Can not send mail with empty body,you should have description in the body'))
392 self.write(cr, uid, [case.id], {
393 'description': False,
395 emails = [case.email_from] + (case.email_cc or '').split(',')
396 emails = filter(None, emails)
397 body = case.description or ''
398 if case.user_id.signature:
399 body += '\n\n%s' % (case.user_id.signature)
401 emailfrom = case.user_id.address_id and case.user_id.address_id.email or False
403 raise osv.except_osv(_('Error!'),
404 _("No E-Mail ID Found for your Company address!"))
409 '['+str(case.id)+'] '+case.name,
410 self.format_body(body),
411 reply_to=case.section_id.reply_to,
412 openobject_id=str(case.id)
414 self.__history(cr, uid, [case], _('Send'), history=True, email=emails, details=body, email_from=emailfrom)
417 def onchange_partner_id(self, cr, uid, ids, part, email=False):
419 return {'value':{'partner_address_id': False,
422 addr = self.pool.get('res.partner').address_get(cr, uid, [part], ['contact'])
423 data = {'partner_address_id': addr['contact']}
424 data.update(self.onchange_partner_address_id(cr, uid, ids, addr['contact'])['value'])
425 return {'value':data}
427 def onchange_partner_address_id(self, cr, uid, ids, add, email=False):
430 return {'value': {'email_from': False, 'partner_name2': False}}
431 address= self.pool.get('res.partner.address').browse(cr, uid, add)
432 data['email_from'] = address.email
433 return {'value': data}
435 def case_close(self, cr, uid, ids, *args):
436 cases = self.browse(cr, uid, ids)
437 cases[0].state # to fill the browse record cache
438 self.__history(cr, uid, cases, _('Close'))
439 self.write(cr, uid, ids, {'state':'done', 'date_closed': time.strftime('%Y-%m-%d %H:%M:%S')})
441 # We use the cache of cases to keep the old case state
443 self._action(cr,uid, cases, 'done')
446 def case_escalate(self, cr, uid, ids, *args):
447 cases = self.browse(cr, uid, ids)
449 data = {'active':True, 'user_id': False}
450 if case.section_id.parent_id:
451 data['section_id'] = case.section_id.parent_id.id
452 if case.section_id.parent_id.user_id:
453 data['user_id'] = case.section_id.parent_id.user_id.id
455 raise osv.except_osv(_('Error !'), _('You can not escalate this case.\nYou are already at the top level.'))
456 self.write(cr, uid, ids, data)
457 cases = self.browse(cr, uid, ids)
458 self.__history(cr, uid, cases, _('Escalate'))
459 self._action(cr,uid, cases, 'escalate')
463 def case_open(self, cr, uid, ids, *args):
464 cases = self.browse(cr, uid, ids)
465 self.__history(cr, uid, cases, _('Open'))
467 data = {'state':'open', 'active':True}
469 data['user_id'] = uid
470 self.write(cr, uid, ids, data)
471 self._action(cr,uid, cases, 'open')
475 def case_cancel(self, cr, uid, ids, *args):
476 cases = self.browse(cr, uid, ids)
477 cases[0].state # to fill the browse record cache
478 self.__history(cr, uid, cases, _('Cancel'))
479 self.write(cr, uid, ids, {'state':'cancel', 'active':True})
480 self._action(cr,uid, cases, 'cancel')
483 def case_pending(self, cr, uid, ids, *args):
484 cases = self.browse(cr, uid, ids)
485 cases[0].state # to fill the browse record cache
486 self.__history(cr, uid, cases, _('Pending'))
487 self.write(cr, uid, ids, {'state':'pending', 'active':True})
488 self._action(cr,uid, cases, 'pending')
491 def case_reset(self, cr, uid, ids, *args):
492 cases = self.browse(cr, uid, ids)
493 cases[0].state # to fill the browse record cache
494 self.__history(cr, uid, cases, _('Draft'))
495 self.write(cr, uid, ids, {'state':'draft', 'active':True})
496 self._action(cr,uid, cases, 'draft')
502 class crm_case_log(osv.osv):
503 _name = "crm.case.log"
504 _description = "Case Communication History"
507 'name': fields.char('Status', size=64),
508 'date': fields.datetime('Date'),
509 'section_id': fields.many2one('crm.case.section', 'Section'),
510 'user_id': fields.many2one('res.users', 'User Responsible', readonly=True),
511 'model_id': fields.many2one('ir.model', "Model"),
512 'res_id': fields.integer('Resource ID'),
515 'date': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
519 class crm_case_history(osv.osv):
520 _name = "crm.case.history"
521 _description = "Case history"
523 _inherits = {'crm.case.log':"log_id"}
525 def _note_get(self, cursor, user, ids, name, arg, context=None):
527 for hist in self.browse(cursor, user, ids, context or {}):
528 res[hist.id] = (hist.email or '/') + ' (' + str(hist.date) + ')\n'
529 res[hist.id] += (hist.description or '')
532 'description': fields.text('Description'),
533 'note': fields.function(_note_get, method=True, string="Description", type="text"),
534 'email_to': fields.char('Email TO', size=84),
535 'email_from' : fields.char('Email From', size=84),
536 'log_id': fields.many2one('crm.case.log','Log',ondelete='cascade'),
540 class crm_email_add_cc_wizard(osv.osv_memory):
541 _name = "crm.email.add.cc"
542 _description = "Email Add CC"
544 'name': fields.selection([('user','User'),('partner','Partner'),('email','Email Address')], 'Send to', required=True),
545 'user_id': fields.many2one('res.users',"User"),
546 'partner_id': fields.many2one('res.partner',"Partner"),
547 'email': fields.char('Email', size=32),
548 'subject': fields.char('Subject', size=32),
551 def change_email(self, cr, uid, ids, user, partner):
552 if (not partner and not user):
553 return {'value':{'email': False}}
556 addr = self.pool.get('res.partner').address_get(cr, uid, [partner], ['contact'])
558 email = self.pool.get('res.partner.address').read(cr, uid,addr['contact'] , ['email'])['email']
560 addr = self.pool.get('res.users').read(cr, uid, user, ['address_id'])['address_id']
562 email = self.pool.get('res.partner.address').read(cr, uid,addr[0] , ['email'])['email']
563 return {'value':{'email': email}}
566 def add_cc(self, cr, uid, ids, context={}):
567 data = self.read(cr, uid, ids[0])
568 email = data['email']
569 subject = data['subject']
573 history_line = self.pool.get('crm.case.history').browse(cr, uid, context['active_id'])
574 model = history_line.log_id.model_id.model
575 model_pool = self.pool.get(model)
576 case = model_pool.browse(cr, uid, history_line.log_id.res_id)
577 body = history_line.description.replace('\n','\n> ')
578 flag = tools.email_send(
580 [case.user_id.address_id.email],
581 subject or '['+str(case.id)+'] '+case.name,
582 model_pool.format_body(body),
583 openobject_id=str(case.id),
587 model_pool.write(cr, uid, case.id, {'email_cc' : case.email_cc and case.email_cc +','+ email or email})
588 self.__history(cr, uid, [case], _('Send'), history=True, email=email, details=body, email_from=case.user_id.address_id.email)
590 raise osv.except_osv(_('Email Fail!'),("Lastest Email is not sent successfully"))
593 crm_email_add_cc_wizard()
595 class users(osv.osv):
596 _inherit = 'res.users'
597 _description = "Users"
599 'context_section_id': fields.many2one('crm.case.section', 'Sales Team'),
604 class res_partner(osv.osv):
605 _inherit = 'res.partner'
607 'section_id': fields.many2one('crm.case.section', 'Sales Team'),
613 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: