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