[FIX] crm: Revert back change of Task ID-1796
[odoo/odoo.git] / addons / crm / crm_lead.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 from osv import fields, osv
23 from datetime import datetime
24 import crm
25 import time
26 from tools.translate import _
27 from crm import crm_case
28 import binascii
29 import tools
30
31
32 CRM_LEAD_PENDING_STATES = (
33     crm.AVAILABLE_STATES[2][0], # Cancelled
34     crm.AVAILABLE_STATES[3][0], # Done
35     crm.AVAILABLE_STATES[4][0], # Pending
36 )
37
38 class crm_lead(crm_case, osv.osv):
39     """ CRM Lead Case """
40     _name = "crm.lead"
41     _description = "Lead/Opportunity"
42     _order = "date_action, priority, id desc"
43     _inherit = ['mailgate.thread','res.partner.address']
44     def _compute_day(self, cr, uid, ids, fields, args, context=None):
45         """
46         @param cr: the current row, from the database cursor,
47         @param uid: the current user’s ID for security checks,
48         @param ids: List of Openday’s IDs
49         @return: difference between current date and log date
50         @param context: A standard dictionary for contextual values
51         """
52         cal_obj = self.pool.get('resource.calendar')
53         res_obj = self.pool.get('resource.resource')
54
55         res = {}
56         for lead in self.browse(cr, uid, ids, context=context):
57             for field in fields:
58                 res[lead.id] = {}
59                 duration = 0
60                 ans = False
61                 if field == 'day_open':
62                     if lead.date_open:
63                         date_create = datetime.strptime(lead.create_date, "%Y-%m-%d %H:%M:%S")
64                         date_open = datetime.strptime(lead.date_open, "%Y-%m-%d %H:%M:%S")
65                         ans = date_open - date_create
66                         date_until = lead.date_open
67                 elif field == 'day_close':
68                     if lead.date_closed:
69                         date_create = datetime.strptime(lead.create_date, "%Y-%m-%d %H:%M:%S")
70                         date_close = datetime.strptime(lead.date_closed, "%Y-%m-%d %H:%M:%S")
71                         date_until = lead.date_closed
72                         ans = date_close - date_create
73                 if ans:
74                     resource_id = False
75                     if lead.user_id:
76                         resource_ids = res_obj.search(cr, uid, [('user_id','=',lead.user_id.id)])
77                         if len(resource_ids):
78                             resource_id = resource_ids[0]
79
80                     duration = float(ans.days)
81                     if lead.section_id and lead.section_id.resource_calendar_id:
82                         duration =  float(ans.days) * 24
83                         new_dates = cal_obj.interval_get(cr,
84                             uid,
85                             lead.section_id.resource_calendar_id and lead.section_id.resource_calendar_id.id or False,
86                             datetime.strptime(lead.create_date, '%Y-%m-%d %H:%M:%S'),
87                             duration,
88                             resource=resource_id
89                         )
90                         no_days = []
91                         date_until = datetime.strptime(date_until, '%Y-%m-%d %H:%M:%S')
92                         for in_time, out_time in new_dates:
93                             if in_time.date not in no_days:
94                                 no_days.append(in_time.date)
95                             if out_time > date_until:
96                                 break
97                         duration =  len(no_days)
98                 res[lead.id][field] = abs(int(duration))
99         return res
100
101     _columns = {
102         # Overridden from res.partner.address:
103         'partner_id': fields.many2one('res.partner', 'Partner', ondelete='set null', 
104             select=True, help="Optional linked partner, usually after conversion of the lead"),
105
106         # From crm.case
107         'id': fields.integer('ID'),
108         'name': fields.char('Name', size=64),
109         'active': fields.boolean('Active', required=False),
110         'date_action_last': fields.datetime('Last Action', readonly=1),
111         'date_action_next': fields.datetime('Next Action', readonly=1),
112         'email_from': fields.char('Email', size=128, help="E-mail address of the contact"),
113         'section_id': fields.many2one('crm.case.section', 'Sales Team', \
114                         select=True, help='Sales team to which this case belongs to. Defines responsible user and e-mail address for the mail gateway.'),
115         'create_date': fields.datetime('Creation Date' , readonly=True),
116         'email_cc': fields.text('Global CC', size=252 , help="These email addresses will be added to the CC field of all inbound and outbound emails for this record before being sent. Separate multiple email addresses with a comma"),
117         'description': fields.text('Notes'),
118         'write_date': fields.datetime('Update Date' , readonly=True),
119
120         # Lead fields
121         'categ_id': fields.many2one('crm.case.categ', 'Category', \
122             domain="['|',('section_id','=',section_id),('section_id','=',False), ('object_id.model', '=', 'crm.lead')]"),
123         'type_id': fields.many2one('crm.case.resource.type', 'Campaign', \
124             domain="['|',('section_id','=',section_id),('section_id','=',False)]"),
125         'channel_id': fields.many2one('res.partner.canal', 'Channel'),
126
127         'contact_name': fields.char('Contact Name', size=64), 
128         'partner_name': fields.char("Customer Name", size=64,help='The name of the future partner that will be created while converting the into opportunity'),
129         'optin': fields.boolean('Opt-In', help="If opt-in is checked, this contact has accepted to receive emails."),
130         'optout': fields.boolean('Opt-Out', help="If opt-out is checked, this contact has refused to receive emails or unsubscribed to a campaign."),
131         'type':fields.selection([
132             ('lead','Lead'),
133             ('opportunity','Opportunity'),
134
135         ],'Type', help="Type is used to separate Leads and Opportunities"),
136         'priority': fields.selection(crm.AVAILABLE_PRIORITIES, 'Priority'),
137         'date_closed': fields.datetime('Closed', readonly=True),
138         'stage_id': fields.many2one('crm.case.stage', 'Stage', domain="[('type','=','lead')]"),
139         'user_id': fields.many2one('res.users', 'Salesman'),
140         'referred': fields.char('Referred By', size=64),
141         'date_open': fields.datetime('Opened', readonly=True),
142         'day_open': fields.function(_compute_day, string='Days to Open', \
143                                 method=True, multi='day_open', type="float", store=True),
144         'day_close': fields.function(_compute_day, string='Days to Close', \
145                                 method=True, multi='day_close', type="float", store=True),
146         'state': fields.selection(crm.AVAILABLE_STATES, 'State', size=16, readonly=True,
147                                   help='The state is set to \'Draft\', when a case is created.\
148                                   \nIf the case is in progress the state is set to \'Open\'.\
149                                   \nWhen the case is over, the state is set to \'Done\'.\
150                                   \nIf the case needs to be reviewed then the state is set to \'Pending\'.'), 
151         'message_ids': fields.one2many('mailgate.message', 'res_id', 'Messages', domain=[('model','=',_name)]),
152     }
153     
154
155     _defaults = {
156         'active': lambda *a: 1,
157         'user_id': crm_case._get_default_user,
158         'email_from': crm_case._get_default_email,
159         'state': lambda *a: 'draft',
160         'type': lambda *a: 'lead',
161         'section_id': crm_case._get_section,
162         'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'crm.lead', context=c),
163         'priority': lambda *a: crm.AVAILABLE_PRIORITIES[2][0],
164         #'stage_id': _get_stage_id,
165     }
166     
167     
168
169     def onchange_partner_address_id(self, cr, uid, ids, add, email=False):
170         """This function returns value of partner email based on Partner Address
171         @param self: The object pointer
172         @param cr: the current row, from the database cursor,
173         @param uid: the current user’s ID for security checks,
174         @param ids: List of case IDs
175         @param add: Id of Partner's address
176         @email: Partner's email ID
177         """
178         if not add:
179             return {'value': {'email_from': False, 'country_id': False}}
180         address = self.pool.get('res.partner.address').browse(cr, uid, add)
181         return {'value': {'email_from': address.email, 'phone': address.phone, 'country_id': address.country_id.id}}
182
183     def case_open(self, cr, uid, ids, *args):
184         """Overrides cancel for crm_case for setting Open Date
185         @param self: The object pointer
186         @param cr: the current row, from the database cursor,
187         @param uid: the current user’s ID for security checks,
188         @param ids: List of case's Ids
189         @param *args: Give Tuple Value
190         """
191         leads = self.browse(cr, uid, ids)
192         
193         
194         
195         for i in xrange(0, len(ids)): 
196             if leads[i].state == 'draft':
197                 value = {}
198                 if not leads[i].stage_id :
199                     stage_id = self._find_first_stage(cr, uid, leads[i].type, leads[i].section_id.id or False)
200                     value.update({'stage_id' : stage_id})
201                 value.update({'date_open': time.strftime('%Y-%m-%d %H:%M:%S')})
202                 self.write(cr, uid, [ids[i]], value)
203             self.log_open( cr, uid, leads[i])
204         res = super(crm_lead, self).case_open(cr, uid, ids, *args)
205         return res
206         
207     def log_open(self, cr, uid, case):
208         if case.type == 'lead':
209             message = _("The lead '%s' has been opened.") % case.name
210         elif case.type == 'opportunity':
211             message = _("The opportunity '%s' has been opened.") % case.name
212         else:
213             message = _("The case '%s' has been opened.") % case.name
214         self.log(cr, uid, case.id, message)
215
216     def case_close(self, cr, uid, ids, *args):
217         """Overrides close for crm_case for setting close date
218         @param self: The object pointer
219         @param cr: the current row, from the database cursor,
220         @param uid: the current user’s ID for security checks,
221         @param ids: List of case Ids
222         @param *args: Tuple Value for additional Params
223         """
224         res = super(crm_lead, self).case_close(cr, uid, ids, *args)
225         self.write(cr, uid, ids, {'date_closed': time.strftime('%Y-%m-%d %H:%M:%S')})
226         for case in self.browse(cr, uid, ids):
227             if case.type == 'lead':
228                 message = _("The lead '%s' has been closed.") % case.name
229             elif case.type == 'opportunity':
230                 message = _("The opportunity '%s' has been closed.") % case.name
231             else:
232                 message = _("The case '%s' has been closed.") % case.name
233             self.log(cr, uid, case.id, message)
234         return res
235
236     def convert_opportunity(self, cr, uid, ids, context=None):
237         """ Precomputation for converting lead to opportunity
238         @param cr: the current row, from the database cursor,
239         @param uid: the current user’s ID for security checks,
240         @param ids: List of closeday’s IDs
241         @param context: A standard dictionary for contextual values
242         @return: Value of action in dict
243         """
244         if context is None:
245             context = {}
246         context.update({'active_ids': ids})
247
248         data_obj = self.pool.get('ir.model.data')
249         data_id = data_obj._get_id(cr, uid, 'crm', 'view_crm_lead2opportunity_action')
250         value = {}
251
252         view_id = False
253         if data_id:
254             view_id = data_obj.browse(cr, uid, data_id, context=context).res_id
255
256         for case in self.browse(cr, uid, ids):
257             context.update({'active_id': case.id})
258             if not case.partner_id:
259                 data_id = data_obj._get_id(cr, uid, 'crm', 'view_crm_lead2opportunity_partner')
260                 view_id1 = False
261                 if data_id:
262                     view_id1 = data_obj.browse(cr, uid, data_id, context=context).res_id
263                 value = {
264                         'name': _('Create Partner'),
265                         'view_type': 'form',
266                         'view_mode': 'form,tree',
267                         'res_model': 'crm.lead2opportunity.partner',
268                         'view_id': False,
269                         'context': context,
270                         'views': [(view_id1, 'form')],
271                         'type': 'ir.actions.act_window',
272                         'target': 'new',
273                         'nodestroy': True
274                         }
275                 break
276             else:
277                 value = {
278                         'name': _('Create Opportunity'),
279                         'view_type': 'form',
280                         'view_mode': 'form,tree',
281                         'res_model': 'crm.lead2opportunity.action',
282                         'view_id': False,
283                         'context': context,
284                         'views': [(view_id, 'form')],
285                         'type': 'ir.actions.act_window',
286                         'target': 'new',
287                         'nodestroy': True
288                         }
289         return value
290
291     def write(self, cr, uid, ids, vals, context=None):
292         if not context:
293             context = {}
294             
295         if 'date_closed' in vals:
296             return super(crm_lead,self).write(cr, uid, ids, vals, context=context)
297         return super(crm_lead,self).write(cr, uid, ids, vals, context)
298     
299     def stage_next(self, cr, uid, ids, context=None):
300         stage = super(crm_lead, self).stage_next(cr, uid, ids, context=context)
301         if stage:
302             stage_obj = self.pool.get('crm.case.stage').browse(cr, uid, stage, context=context)
303             self.history(cr, uid, ids, _('Stage'), details=stage_obj.name)
304             for case in self.browse(cr, uid, ids, context=context):
305                 if case.type == 'lead':
306                     message = _("The stage of lead '%s' has been changed to '%s'.") % (case.name, case.stage_id.name)
307                 elif case.type == 'opportunity':
308                     message = _("The stage of opportunity '%s' has been changed to '%s'.") % (case.name, case.stage_id.name)
309                 self.log(cr, uid, case.id, message)            
310             if stage_obj.on_change:
311                 data = {'probability': stage_obj.probability}
312                 self.write(cr, uid, ids, data)
313         return stage
314     
315     def stage_previous(self, cr, uid, ids, context=None):
316         stage = super(crm_lead, self).stage_previous(cr, uid, ids, context)
317         if stage:
318             stage_obj = self.pool.get('crm.case.stage').browse(cr, uid, stage, context=context)
319             self.history(cr, uid, ids, _('Stage'), details=stage_obj.name)
320             for case in self.browse(cr, uid, ids, context=context):
321                 if case.type == 'lead':
322                     message = _("The stage of lead '%s' has been changed to '%s'.") % (case.name, case.stage_id.name)
323                 elif case.type == 'opportunity':
324                     message = _("The stage of opportunity '%s' has been changed to '%s'.") % (case.name, case.stage_id.name)
325                 self.log(cr, uid, case.id, message)            
326         return stage
327     
328    
329     def message_new(self, cr, uid, msg, context=None):
330         """
331         Automatically calls when new email message arrives
332
333         @param self: The object pointer
334         @param cr: the current row, from the database cursor,
335         @param uid: the current user’s ID for security checks
336         """
337         mailgate_pool = self.pool.get('email.server.tools')
338
339         subject = msg.get('subject')
340         body = msg.get('body')
341         msg_from = msg.get('from')
342         priority = msg.get('priority')
343
344         vals = {
345             'name': subject,
346             'email_from': msg_from,
347             'email_cc': msg.get('cc'),
348             'description': body,
349             'user_id': False,
350         }
351         if msg.get('priority', False):
352             vals['priority'] = priority
353
354         res = mailgate_pool.get_partner(cr, uid, msg.get('from') or msg.get_unixfrom())
355         if res:
356             vals.update(res)
357
358         res = self.create(cr, uid, vals, context)
359         attachents = msg.get('attachments', [])
360         for attactment in attachents or []:
361             data_attach = {
362                 'name': attactment,
363                 'datas':binascii.b2a_base64(str(attachents.get(attactment))),
364                 'datas_fname': attactment,
365                 'description': 'Mail attachment',
366                 'res_model': self._name,
367                 'res_id': res,
368             }
369             self.pool.get('ir.attachment').create(cr, uid, data_attach)
370
371         return res
372
373     def message_update(self, cr, uid, ids, vals={}, msg="", default_act='pending', context=None):
374         """
375         @param self: The object pointer
376         @param cr: the current row, from the database cursor,
377         @param uid: the current user’s ID for security checks,
378         @param ids: List of update mail’s IDs 
379         """
380         if isinstance(ids, (str, int, long)):
381             ids = [ids]
382
383         if msg.get('priority') in dict(crm.AVAILABLE_PRIORITIES):
384             vals['priority'] = msg.get('priority')
385
386         maps = {
387             'cost':'planned_cost',
388             'revenue': 'planned_revenue',
389             'probability':'probability'
390         }
391         vls = {}
392         for line in msg['body'].split('\n'):
393             line = line.strip()
394             res = tools.misc.command_re.match(line)
395             if res and maps.get(res.group(1).lower()):
396                 key = maps.get(res.group(1).lower())
397                 vls[key] = res.group(2).lower()
398         vals.update(vls)
399
400         # Unfortunately the API is based on lists
401         # but we want to update the state based on the
402         # previous state, so we have to loop:
403         for case in self.browse(cr, uid, ids, context=context):
404             values = dict(vals)
405             if case.state in CRM_LEAD_PENDING_STATES:
406                 values.update(state=crm.AVAILABLE_STATES[1][0]) #re-open
407             res = self.write(cr, uid, [case.id], values, context=context)
408         return res
409
410     def msg_send(self, cr, uid, id, *args, **argv):
411
412         """ Send The Message
413             @param self: The object pointer
414             @param cr: the current row, from the database cursor,
415             @param uid: the current user’s ID for security checks,
416             @param ids: List of email’s IDs
417             @param *args: Return Tuple Value
418             @param **args: Return Dictionary of Keyword Value
419         """
420         return True
421 crm_lead()
422
423 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: