Launchpad automatic translations update.
[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',help='By Default Salesman is Administrator when create New User'),
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             
298         if 'stage_id' in vals and vals['stage_id']:
299             stage_obj = self.pool.get('crm.case.stage').browse(cr, uid, vals['stage_id'], context=context)
300             self.history(cr, uid, ids, _('Stage'), details=stage_obj.name)
301             message=''
302             for case in self.browse(cr, uid, ids, context=context):
303                 if case.type == 'lead' or  context.get('stage_type',False)=='lead':
304                     message = _("The stage of lead '%s' has been changed to '%s'.") % (case.name, stage_obj.name)
305                 elif case.type == 'opportunity':
306                     message = _("The stage of opportunity '%s' has been changed to '%s'.") % (case.name, stage_obj.name)
307                 self.log(cr, uid, case.id, message)
308         return super(crm_lead,self).write(cr, uid, ids, vals, context)
309     
310     def stage_next(self, cr, uid, ids, context=None):
311         stage = super(crm_lead, self).stage_next(cr, uid, ids, context=context)
312         if stage:
313             stage_obj = self.pool.get('crm.case.stage').browse(cr, uid, stage, context=context)
314             if stage_obj.on_change:
315                 data = {'probability': stage_obj.probability}
316                 self.write(cr, uid, ids, data)
317         return stage
318     
319     def message_new(self, cr, uid, msg, context=None):
320         """
321         Automatically calls when new email message arrives
322
323         @param self: The object pointer
324         @param cr: the current row, from the database cursor,
325         @param uid: the current user’s ID for security checks
326         """
327         mailgate_pool = self.pool.get('email.server.tools')
328
329         subject = msg.get('subject') or _("No Subject")
330         body = msg.get('body')
331         msg_from = msg.get('from')
332         priority = msg.get('priority')
333
334         vals = {
335             'name': subject,
336             'email_from': msg_from,
337             'email_cc': msg.get('cc'),
338             'description': body,
339             'user_id': False,
340         }
341         if msg.get('priority', False):
342             vals['priority'] = priority
343
344         res = mailgate_pool.get_partner(cr, uid, msg.get('from') or msg.get_unixfrom())
345         if res:
346             vals.update(res)
347
348         res = self.create(cr, uid, vals, context)
349         attachents = msg.get('attachments', [])
350         for attactment in attachents or []:
351             data_attach = {
352                 'name': attactment,
353                 'datas':binascii.b2a_base64(str(attachents.get(attactment))),
354                 'datas_fname': attactment,
355                 'description': 'Mail attachment',
356                 'res_model': self._name,
357                 'res_id': res,
358             }
359             self.pool.get('ir.attachment').create(cr, uid, data_attach)
360
361         return res
362
363     def message_update(self, cr, uid, ids, vals={}, msg="", default_act='pending', context=None):
364         """
365         @param self: The object pointer
366         @param cr: the current row, from the database cursor,
367         @param uid: the current user’s ID for security checks,
368         @param ids: List of update mail’s IDs 
369         """
370         if isinstance(ids, (str, int, long)):
371             ids = [ids]
372
373         if msg.get('priority') in dict(crm.AVAILABLE_PRIORITIES):
374             vals['priority'] = msg.get('priority')
375
376         maps = {
377             'cost':'planned_cost',
378             'revenue': 'planned_revenue',
379             'probability':'probability'
380         }
381         vls = {}
382         for line in msg['body'].split('\n'):
383             line = line.strip()
384             res = tools.misc.command_re.match(line)
385             if res and maps.get(res.group(1).lower()):
386                 key = maps.get(res.group(1).lower())
387                 vls[key] = res.group(2).lower()
388         vals.update(vls)
389
390         # Unfortunately the API is based on lists
391         # but we want to update the state based on the
392         # previous state, so we have to loop:
393         for case in self.browse(cr, uid, ids, context=context):
394             values = dict(vals)
395             if case.state in CRM_LEAD_PENDING_STATES:
396                 values.update(state=crm.AVAILABLE_STATES[1][0]) #re-open
397             res = self.write(cr, uid, [case.id], values, context=context)
398         return res
399
400     def msg_send(self, cr, uid, id, *args, **argv):
401
402         """ Send The Message
403             @param self: The object pointer
404             @param cr: the current row, from the database cursor,
405             @param uid: the current user’s ID for security checks,
406             @param ids: List of email’s IDs
407             @param *args: Return Tuple Value
408             @param **args: Return Dictionary of Keyword Value
409         """
410         return True
411 crm_lead()
412
413 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: