[ADD]: crm: Added new wizard to merge Opportunity with Other Opportunities of the...
[odoo/odoo.git] / addons / crm / crm.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2010 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 time
23 from datetime import datetime
24 from datetime import timedelta
25 import base64
26 import tools
27 from osv import fields
28 from osv import osv
29 from tools.translate import _
30
31 MAX_LEVEL = 15
32 AVAILABLE_STATES = [
33     ('draft', 'Draft'),
34     ('open', 'Open'),
35     ('cancel', 'Cancelled'),
36     ('done', 'Closed'),
37     ('pending', 'Pending'),
38 ]
39
40 AVAILABLE_PRIORITIES = [
41     ('1', 'Highest'),
42     ('2', 'High'),
43     ('3', 'Normal'),
44     ('4', 'Low'),
45     ('5', 'Lowest'),
46 ]
47
48 class crm_case(object):
49     """A simple python class to be used for common functions """
50
51     def _get_default_partner_address(self, cr, uid, context):
52         """Gives id of default address for current user
53         @param self: The object pointer
54         @param cr: the current row, from the database cursor,
55         @param uid: the current user’s ID for security checks,
56         @param context: A standard dictionary for contextual values
57         """
58         if not context.get('portal', False):
59             return False
60         return self.pool.get('res.users').browse(cr, uid, uid, context).address_id.id
61
62     def _get_default_partner(self, cr, uid, context):
63         """Gives id of partner for current user
64         @param self: The object pointer
65         @param cr: the current row, from the database cursor,
66         @param uid: the current user’s ID for security checks,
67         @param context: A standard dictionary for contextual values
68         """
69         if not context.get('portal', False):
70             return False
71         user = self.pool.get('res.users').browse(cr, uid, uid, context)
72         if not user.address_id:
73             return False
74         return user.address_id.partner_id.id
75
76     def _get_default_email(self, cr, uid, context):
77         """Gives default email address for current user
78         @param self: The object pointer
79         @param cr: the current row, from the database cursor,
80         @param uid: the current user’s ID for security checks,
81         @param context: A standard dictionary for contextual values
82         """
83         if not context.get('portal', False):
84             return False
85         user = self.pool.get('res.users').browse(cr, uid, uid, context)
86         if not user.address_id:
87             return False
88         return user.address_id.email
89
90     def _get_default_user(self, cr, uid, context):
91         """Gives current user id
92         @param self: The object pointer
93         @param cr: the current row, from the database cursor,
94         @param uid: the current user’s ID for security checks,
95         @param context: A standard dictionary for contextual values
96         """
97         if context.get('portal', False):
98             return False
99         return uid
100
101     def _get_section(self, cr, uid, context):
102         """Gives section id for current User
103         @param self: The object pointer
104         @param cr: the current row, from the database cursor,
105         @param uid: the current user’s ID for security checks,
106         @param context: A standard dictionary for contextual values
107         """
108         user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
109         return user.context_section_id.id or False
110
111     def stage_next(self, cr, uid, ids, context=None):
112         """This function computes next stage for case from its current stage
113              using available stage for that case type
114         @param self: The object pointer
115         @param cr: the current row, from the database cursor,
116         @param uid: the current user’s ID for security checks,
117         @param ids: List of case IDs
118         @param context: A standard dictionary for contextual values"""
119         if not context:
120             context = {}
121         s = self.get_stage_dict(cr, uid, ids, context=context)
122         section = self._name
123         stage = False
124         for case in self.browse(cr, uid, ids, context):
125             if section in s:
126                 st =  not context.get('force_domain', False) and case.stage_id.id  or False
127                 if st in s[section]:
128                     data = {'stage_id': s[section][st]}
129                     stage = s[section][st]
130                     self.write(cr, uid, [case.id], data)
131         return stage
132
133     def get_stage_dict(self, cr, uid, ids, context=None):
134         """This function gives dictionary for stage according to stage levels
135         @param self: The object pointer
136         @param cr: the current row, from the database cursor,
137         @param uid: the current user’s ID for security checks,
138         @param ids: List of case IDs
139         @param context: A standard dictionary for contextual values"""
140         if not context:
141             context = {}
142         stage_obj = self.pool.get('crm.case.stage')
143         res = self.read(cr, uid, ids, ['section_id', 'stage_id'], context)[0]
144         section_id = res['section_id'] and res['section_id'][0] or False
145         stage_id = res['stage_id'] and res['stage_id'][0] or False
146
147         # We select either the stages in the same section as the current stage
148         # if it a stage that does not have a section, or the stages of the 
149         # current section of the case
150         if stage_id:
151             stage_record = stage_obj.browse(cr, uid, stage_id)
152             if not stage_record.section_id:
153                 section_id = False # only select stages without section
154
155         domain = [('object_id.model', '=', self._name), ('section_id', '=', section_id)]
156         if 'force_domain' in context and context['force_domain']:
157             domain += context['force_domain']
158         sid = stage_obj.search(cr, uid, domain, context=context)
159         s = {}
160         previous = {}
161         section = self._name
162
163         for stage in stage_obj.browse(cr, uid, sid, context=context):
164             s.setdefault(section, {})
165             s[section][previous.get(section, False)] = stage.id
166             previous[section] = stage.id
167         return s
168
169     def stage_previous(self, cr, uid, ids, context=None):
170         """This function computes previous stage for case from its current stage
171              using available stage for that case type
172         @param self: The object pointer
173         @param cr: the current row, from the database cursor,
174         @param uid: the current user’s ID for security checks,
175         @param ids: List of case IDs
176         @param context: A standard dictionary for contextual values"""
177         if not context:
178             context = {}
179
180         s = self.get_stage_dict(cr, uid, ids, context=context)
181         section = self._name
182         stage_pool = self.pool.get('crm.case.stage')
183         for case in self.browse(cr, uid, ids, context):
184             if section in s:
185                 st = not context.get('force_domain', False) and case.stage_id.id or False
186                 s[section] = dict([(v, k) for (k, v) in s[section].iteritems()])
187                 if st in s[section]:
188                     data = {'stage_id': s[section][st]}
189                     if s[section][st]:
190                         stage = stage_pool.browse(cr, uid, s[section][st], context=context)
191                         if stage.on_change:
192                             data.update({'probability': stage.probability})
193                     self.write(cr, uid, [case.id], data)
194         return True
195
196     def onchange_partner_id(self, cr, uid, ids, part, email=False):
197         """This function returns value of partner address based on partner
198         @param self: The object pointer
199         @param cr: the current row, from the database cursor,
200         @param uid: the current user’s ID for security checks,
201         @param ids: List of case IDs
202         @param part: Partner's id
203         @email: Partner's email ID
204         """
205         if not part:
206             return {'value': {'partner_address_id': False,
207                             'email_from': False, 
208                             'phone': False
209                             }}
210         addr = self.pool.get('res.partner').address_get(cr, uid, [part], ['contact'])
211         data = {'partner_address_id': addr['contact']}
212         data.update(self.onchange_partner_address_id(cr, uid, ids, addr['contact'])['value'])
213         return {'value': data}
214
215     def onchange_partner_address_id(self, cr, uid, ids, add, email=False):
216         """This function returns value of partner email based on Partner Address
217         @param self: The object pointer
218         @param cr: the current row, from the database cursor,
219         @param uid: the current user’s ID for security checks,
220         @param ids: List of case IDs
221         @param add: Id of Partner's address
222         @email: Partner's email ID
223         """
224         if not add:
225             return {'value': {'email_from': False}}
226         address = self.pool.get('res.partner.address').browse(cr, uid, add)
227         return {'value': {'email_from': address.email, 'phone': address.phone}}
228
229     def _history(self, cr, uid, cases, keyword, history=False, subject=None, email=False, details=None, email_from=False, message_id=False, attach=[], context={}):
230         mailgate_pool = self.pool.get('mailgate.thread')
231         return mailgate_pool.history(cr, uid, cases, keyword, history=history,\
232                                        subject=subject, email=email, \
233                                        details=details, email_from=email_from,\
234                                        message_id=message_id, attach=attach, \
235                                        context=context)
236
237     def case_open(self, cr, uid, ids, *args):
238         """Opens Case
239         @param self: The object pointer
240         @param cr: the current row, from the database cursor,
241         @param uid: the current user’s ID for security checks,
242         @param ids: List of case Ids
243         @param *args: Tuple Value for additional Params
244         """
245         cases = self.browse(cr, uid, ids)
246         self._history(cr, uid, cases, _('Open'))
247         for case in cases:
248             data = {'state': 'open', 'active': True}
249             if not case.user_id:
250                 data['user_id'] = uid
251             self.write(cr, uid, case.id, data)
252         self._action(cr, uid, cases, 'open')
253         return True
254
255     def case_close(self, cr, uid, ids, *args):
256         """Closes Case
257         @param self: The object pointer
258         @param cr: the current row, from the database cursor,
259         @param uid: the current user’s ID for security checks,
260         @param ids: List of case Ids
261         @param *args: Tuple Value for additional Params
262         """
263         cases = self.browse(cr, uid, ids)
264         cases[0].state # to fill the browse record cache
265         self._history(cr, uid, cases, _('Close'))
266         self.write(cr, uid, ids, {'state': 'done',
267                                   'date_closed': time.strftime('%Y-%m-%d %H:%M:%S'),
268                                   })
269         #
270         # We use the cache of cases to keep the old case state
271         #
272         self._action(cr, uid, cases, 'done')
273         return True
274
275     def case_escalate(self, cr, uid, ids, *args):
276         """Escalates case to top level
277         @param self: The object pointer
278         @param cr: the current row, from the database cursor,
279         @param uid: the current user’s ID for security checks,
280         @param ids: List of case Ids
281         @param *args: Tuple Value for additional Params
282         """
283         cases = self.browse(cr, uid, ids)
284         for case in cases:
285             data = {'active': True}
286
287             if case.section_id.parent_id:
288                 data['section_id'] = case.section_id.parent_id.id
289                 if case.section_id.parent_id.change_responsible:
290                     if case.section_id.parent_id.user_id:
291                         data['user_id'] = case.section_id.parent_id.user_id.id
292             else:
293                 raise osv.except_osv(_('Error !'), _('You can not escalate, You are already at the top level regarding your sales-team category.'))
294             self.write(cr, uid, [case.id], data)
295         cases = self.browse(cr, uid, ids)
296         self._history(cr, uid, cases, _('Escalate'))
297         self._action(cr, uid, cases, 'escalate')
298         return True
299
300     def case_cancel(self, cr, uid, ids, *args):
301         """Cancels Case
302         @param self: The object pointer
303         @param cr: the current row, from the database cursor,
304         @param uid: the current user’s ID for security checks,
305         @param ids: List of case Ids
306         @param *args: Tuple Value for additional Params
307         """
308         cases = self.browse(cr, uid, ids)
309         cases[0].state # to fill the browse record cache
310         self._history(cr, uid, cases, _('Cancel'))
311         self.write(cr, uid, ids, {'state': 'cancel',
312                                   'active': True})
313         self._action(cr, uid, cases, 'cancel')
314         for case in cases:
315             message = "The " + self._description + " '" + case.name + "' has been Cancelled."
316             #TODO: Need to differentiate lead and opportunity
317 #            if hasattr(case, 'type'):
318 #                #TO CHECK: hasattr gives warning for other crm objects that don't have field 'type'
319 #                message = "The " + (case.type or 'Case').title() + " '" + case.name + "' has been Cancelled."
320             self.log(cr, uid, case.id, message)
321         return True
322
323     def case_pending(self, cr, uid, ids, *args):
324         """Marks case as pending
325         @param self: The object pointer
326         @param cr: the current row, from the database cursor,
327         @param uid: the current user’s ID for security checks,
328         @param ids: List of case Ids
329         @param *args: Tuple Value for additional Params
330         """
331         cases = self.browse(cr, uid, ids)
332         cases[0].state # to fill the browse record cache
333         self._history(cr, uid, cases, _('Pending'))
334         self.write(cr, uid, ids, {'state': 'pending', 'active': True})
335         self._action(cr, uid, cases, 'pending')
336         return True
337
338     def case_reset(self, cr, uid, ids, *args):
339         """Resets case as draft
340         @param self: The object pointer
341         @param cr: the current row, from the database cursor,
342         @param uid: the current user’s ID for security checks,
343         @param ids: List of case Ids
344         @param *args: Tuple Value for additional Params
345         """
346         cases = self.browse(cr, uid, ids)
347         cases[0].state # to fill the browse record cache
348         self._history(cr, uid, cases, _('Draft'))
349         self.write(cr, uid, ids, {'state': 'draft', 'active': True})
350         self._action(cr, uid, cases, 'draft')
351         return True
352
353     def remind_partner(self, cr, uid, ids, context={}, attach=False):
354
355         """
356         @param self: The object pointer
357         @param cr: the current row, from the database cursor,
358         @param uid: the current user’s ID for security checks,
359         @param ids: List of Remind Partner's IDs
360         @param context: A standard dictionary for contextual values
361
362         """
363         return self.remind_user(cr, uid, ids, context, attach,
364                 destination=False)
365
366     def remind_user(self, cr, uid, ids, context={}, attach=False,destination=True):
367         """
368         @param self: The object pointer
369         @param cr: the current row, from the database cursor,
370         @param uid: the current user’s ID for security checks,
371         @param ids: List of Remind user's IDs
372         @param context: A standard dictionary for contextual values
373
374         """
375         for case in self.browse(cr, uid, ids):
376             if not case.section_id.reply_to:
377                 raise osv.except_osv(_('Error!'), ("Reply To is not specified in the sales team"))
378             if not case.email_from:
379                 raise osv.except_osv(_('Error!'), ("Partner Email is not specified in Case"))
380             if case.section_id.reply_to and case.email_from:
381                 src = case.email_from
382                 dest = case.section_id.reply_to
383                 body = case.description or ""
384                 if case.message_ids:
385                     body = case.message_ids[0].description or ""
386                 if not destination:
387                     src, dest = dest, src
388                     if body and case.user_id.signature:
389                         if body:
390                             body += '\n\n%s' % (case.user_id.signature)
391                         else:
392                             body = '\n\n%s' % (case.user_id.signature)
393
394                 body = self.format_body(body)
395
396                 attach_to_send = None
397
398                 if attach:
399                     attach_ids = self.pool.get('ir.attachment').search(cr, uid, [('res_model', '=', self._name), ('res_id', '=', case.id)])
400                     attach_to_send = self.pool.get('ir.attachment').read(cr, uid, attach_ids, ['datas_fname','datas'])
401                     attach_to_send = map(lambda x: (x['datas_fname'], base64.decodestring(x['datas'])), attach_to_send)
402
403                 # Send an email
404                 subject = "Reminder: [%s] %s" % (str(case.id), case.name, )
405                 flag = tools.email_send(
406                     src,
407                     [dest],
408                     subject, 
409                     body,
410                     reply_to=case.section_id.reply_to,
411                     openobject_id=str(case.id),
412                     attach=attach_to_send
413                 )
414                 self._history(cr, uid, [case], _('Send'), history=True, subject=subject, email=dest, details=body, email_from=src)
415         return True
416
417     def _check(self, cr, uid, ids=False, context={}):
418         """
419         Function called by the scheduler to process cases for date actions
420         Only works on not done and cancelled cases
421
422         @param self: The object pointer
423         @param cr: the current row, from the database cursor,
424         @param uid: the current user’s ID for security checks,
425         @param context: A standard dictionary for contextual values
426         """
427         cr.execute('select * from crm_case \
428                 where (date_action_last<%s or date_action_last is null) \
429                 and (date_action_next<=%s or date_action_next is null) \
430                 and state not in (\'cancel\',\'done\')',
431                 (time.strftime("%Y-%m-%d %H:%M:%S"),
432                     time.strftime('%Y-%m-%d %H:%M:%S')))
433
434         ids2 = map(lambda x: x[0], cr.fetchall() or [])
435         cases = self.browse(cr, uid, ids2, context)
436         return self._action(cr, uid, cases, False, context=context)
437
438     def _action(self, cr, uid, cases, state_to, scrit=None, context={}):
439         if not context:
440             context = {}
441         context['state_to'] = state_to
442         rule_obj = self.pool.get('base.action.rule')
443         model_obj = self.pool.get('ir.model')
444         model_ids = model_obj.search(cr, uid, [('model','=',self._name)])
445         rule_ids = rule_obj.search(cr, uid, [('model_id','=',model_ids[0])])
446         return rule_obj._action(cr, uid, rule_ids, cases, scrit=scrit, context=context)
447
448     def format_body(self, body):
449         return self.pool.get('base.action.rule').format_body(body)
450
451     def format_mail(self, obj, body):
452         return self.pool.get('base.action.rule').format_mail(obj, body)
453
454 class crm_case_section(osv.osv):
455     """Sales Team"""
456
457     _name = "crm.case.section"
458     _description = "Sales Teams"
459     _order = "name"
460
461     _columns = {
462         'name': fields.char('Sales Team', size=64, required=True, translate=True),
463         'code': fields.char('Code', size=8),
464         'active': fields.boolean('Active', help="If the active field is set to "\
465                         "true, it will allow you to hide the sales team without removing it."),
466         'allow_unlink': fields.boolean('Allow Delete', help="Allows to delete non draft cases"),
467         'change_responsible': fields.boolean('Change Responsible', help="Thick this box if you want that on escalation, the responsible of this sale team automatically becomes responsible of the lead/opportunity escaladed"),
468         'user_id': fields.many2one('res.users', 'Responsible User'),
469         'member_ids':fields.many2many('res.users', 'sale_member_rel', 'section_id', 'member_id', 'Team Members'),
470         '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"),
471         'parent_id': fields.many2one('crm.case.section', 'Parent Team'),
472         'child_ids': fields.one2many('crm.case.section', 'parent_id', 'Child Teams'),
473         'resource_calendar_id': fields.many2one('resource.calendar', "Resource's Calendar"),
474         'note': fields.text('Description'),
475         'working_hours': fields.float('Working Hours', digits=(16,2 )),
476     }
477
478     _defaults = {
479         'active': lambda *a: 1,
480         'allow_unlink': lambda *a: 1,
481     }
482
483     _sql_constraints = [
484         ('code_uniq', 'unique (code)', 'The code of the sales team must be unique !')
485     ]
486
487     def _check_recursion(self, cr, uid, ids):
488
489         """
490         Checks for recursion level for sales team
491         @param self: The object pointer
492         @param cr: the current row, from the database cursor,
493         @param uid: the current user’s ID for security checks,
494         @param ids: List of Sales team ids
495         """
496         level = 100
497
498         while len(ids):
499             cr.execute('select distinct parent_id from crm_case_section where id IN %s', (tuple(ids),))
500             ids = filter(None, map(lambda x: x[0], cr.fetchall()))
501             if not level:
502                 return False
503             level -= 1
504
505         return True
506
507     _constraints = [
508         (_check_recursion, 'Error ! You cannot create recursive Sales team.', ['parent_id'])
509     ]
510
511     def name_get(self, cr, uid, ids, context=None):
512         """Overrides orm name_get method
513         @param self: The object pointer
514         @param cr: the current row, from the database cursor,
515         @param uid: the current user’s ID for security checks,
516         @param ids: List of sales team ids
517         """
518         if not context:
519             context = {}
520
521         res = []
522         if not ids:
523             return res
524         reads = self.read(cr, uid, ids, ['name', 'parent_id'], context)
525
526         for record in reads:
527             name = record['name']
528             if record['parent_id']:
529                 name = record['parent_id'][1] + ' / ' + name
530             res.append((record['id'], name))
531         return res
532
533 crm_case_section()
534
535
536 class crm_case_categ(osv.osv):
537     """ Category of Case """
538
539     _name = "crm.case.categ"
540     _description = "Category of case"
541
542     _columns = {
543         'name': fields.char('Case Category Name', size=64, required=True, translate=True),
544         'section_id': fields.many2one('crm.case.section', 'Sales Team'),
545         'object_id': fields.many2one('ir.model', 'Object Name'),
546     }
547
548     def _find_object_id(self, cr, uid, context=None):
549         """Finds id for case object
550         @param self: The object pointer
551         @param cr: the current row, from the database cursor,
552         @param uid: the current user’s ID for security checks,
553         @param context: A standard dictionary for contextual values
554         """
555
556         object_id = context and context.get('object_id', False) or False
557         ids = self.pool.get('ir.model').search(cr, uid, [('model', '=', object_id)])
558         return ids and ids[0]
559
560     _defaults = {
561         'object_id' : _find_object_id
562
563     }
564 crm_case_categ()
565
566
567 class crm_case_resource_type(osv.osv):
568     """ Resource Type of case """
569
570     _name = "crm.case.resource.type"
571     _description = "Resource Type of case"
572     _rec_name = "name"
573
574     _columns = {
575         'name': fields.char('Resource Type', size=64, required=True, translate=True),
576         'section_id': fields.many2one('crm.case.section', 'Sales Team'),
577         'object_id': fields.many2one('ir.model', 'Object Name'),
578     }
579     def _find_object_id(self, cr, uid, context=None):
580         """Finds id for case object
581         @param self: The object pointer
582         @param cr: the current row, from the database cursor,
583         @param uid: the current user’s ID for security checks,
584         @param context: A standard dictionary for contextual values
585         """
586         object_id = context and context.get('object_id', False) or False
587         ids = self.pool.get('ir.model').search(cr, uid, [('model', '=', object_id)])
588         return ids and ids[0]
589
590     _defaults = {
591         'object_id' : _find_object_id
592     }
593
594 crm_case_resource_type()
595
596
597 class crm_case_stage(osv.osv):
598     """ Stage of case """
599
600     _name = "crm.case.stage"
601     _description = "Stage of case"
602     _rec_name = 'name'
603     _order = "sequence"
604
605     _columns = {
606         'name': fields.char('Stage Name', size=64, required=True, translate=True),
607         'section_id': fields.many2one('crm.case.section', 'Sales Team'),
608         'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of case stages."),
609         'object_id': fields.many2one('ir.model', 'Object Name'),
610         'probability': fields.float('Probability (%)', required=True),
611         'on_change': fields.boolean('Change Probability Automatically', \
612                          help="Change Probability on next and previous stages."),
613         'requirements': fields.text('Requirements')
614     }
615     def _find_object_id(self, cr, uid, context=None):
616         """Finds id for case object
617         @param self: The object pointer
618         @param cr: the current row, from the database cursor,
619         @param uid: the current user’s ID for security checks,
620         @param context: A standard dictionary for contextual values
621         """
622         object_id = context and context.get('object_id', False) or False
623         ids = self.pool.get('ir.model').search(cr, uid, [('model', '=', object_id)])
624         return ids and ids[0]
625
626     _defaults = {
627         'sequence': lambda *args: 1,
628         'probability': lambda *args: 0.0,
629         'object_id' : _find_object_id
630     }
631
632 crm_case_stage()
633
634 def _links_get(self, cr, uid, context=None):
635     """Gets links value for reference field
636     @param self: The object pointer
637     @param cr: the current row, from the database cursor,
638     @param uid: the current user’s ID for security checks,
639     @param context: A standard dictionary for contextual values
640     """
641     if not context:
642         context = {}
643     obj = self.pool.get('res.request.link')
644     ids = obj.search(cr, uid, [])
645     res = obj.read(cr, uid, ids, ['object', 'name'], context)
646     return [(r['object'], r['name']) for r in res]
647
648 class users(osv.osv):
649     _inherit = 'res.users'
650     _description = "Users"
651     _columns = {
652         'context_section_id': fields.many2one('crm.case.section', 'Sales Team'),
653     }
654 users()
655
656
657 class res_partner(osv.osv):
658     _inherit = 'res.partner'
659     _columns = {
660         'section_id': fields.many2one('crm.case.section', 'Sales Team'),
661     }
662 res_partner()
663
664
665 class crm_case_section_custom(osv.osv):
666     _name = "crm.case.section.custom"
667     _description = 'Custom CRM Case Section' 
668
669     _columns = {
670         'name': fields.char('Case Section',size=64, required=True, translate=True),
671         'code': fields.char('Section Code',size=8),
672         'active': fields.boolean('Active'),
673         'allow_unlink': fields.boolean('Allow Delete', help="Allows to delete non draft cases"),
674         'sequence': fields.integer('Sequence'),
675         'user_id': fields.many2one('res.users', 'Responsible User'),
676         '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 section"),
677         'parent_id': fields.many2one('crm.case.section.custom', 'Parent Section'), 
678         'note': fields.text('Notes'),
679     }
680
681     _defaults = {
682         'active': 1,
683         'allow_unlink': 1,
684     }
685
686     _sql_constraints = [
687         ('code_uniq', 'unique (code)', 'The code of the section must be unique !')
688     ]
689
690     def _check_recursion(self, cr, uid, ids):
691         level = 100
692         while len(ids):
693             cr.execute('SELECT DISTINCT parent_id FROM crm_case_section_custom '\
694                        'WHERE id IN %s',
695                        (tuple(ids),))
696             ids = filter(None, map(lambda x:x[0], cr.fetchall()))
697             if not level:
698                 return False
699             level -= 1
700         return True
701     _constraints = [
702         (_check_recursion, 'Error ! You cannot create recursive sections.', ['parent_id'])
703     ]
704
705 crm_case_section_custom()
706
707
708 class crm_case_custom(osv.osv, crm_case):
709     _name = 'crm.case.custom'
710     _inherit = 'mailgate.thread'
711     _description = "Custom CRM Case"
712
713     _columns = {
714             'id': fields.integer('ID', readonly=True),
715             'name': fields.char('Name',size=64,required=True),
716             'priority': fields.selection(AVAILABLE_PRIORITIES, 'Priority'),
717             'active': fields.boolean('Active'),
718             'description': fields.text('Description'),
719             'section_id': fields.many2one('crm.case.section.custom', 'Section', required=True, select=True),
720             'probability': fields.float('Probability (%)'),
721             'email_from': fields.char('Partner Email', size=128),
722             'email_cc': fields.char('CC', size=252),
723             'partner_id': fields.many2one('res.partner', 'Partner'),
724             'partner_address_id': fields.many2one('res.partner.address', 'Partner Contact', domain="[('partner_id','=',partner_id)]"),
725             'date': fields.datetime('Date'),
726             'create_date': fields.datetime('Created' ,readonly=True),
727             'date_deadline': fields.datetime('Deadline'),
728             'date_closed': fields.datetime('Closed', readonly=True),
729             'user_id': fields.many2one('res.users', 'Responsible'),
730             'state': fields.selection(AVAILABLE_STATES, 'Status', size=16, readonly=True),
731             'ref' : fields.reference('Reference', selection=_links_get, size=128),
732             'date_action_last': fields.datetime('Last Action', readonly=1),
733             'date_action_next': fields.datetime('Next Action', readonly=1),
734         }
735
736     _defaults = {
737         'active': 1,
738         'state': 'draft',
739         'priority': AVAILABLE_PRIORITIES[2][0],
740         'date': time.strftime('%Y-%m-%d %H:%M:%S'),
741     }
742
743 crm_case_custom()
744
745
746 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: