[MERGE]: Merge with lp:openobject-trunk-dev-addons2
[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         'partner_assigned_id': fields.many2one('res.partner', 'Assigned Partner', help="Partner this case has been forwarded/assigned to.", select=True),
153         'date_assign': fields.date('Assignation Date', help="Last date this case was forwarded/assigned to a partner"),
154     }
155
156     _defaults = {
157         'active': lambda *a: 1,
158         'user_id': crm_case._get_default_user,
159         'email_from': crm_case._get_default_email,
160         'state': lambda *a: 'draft',
161         'type': lambda *a: 'lead',
162         'section_id': crm_case._get_section,
163         'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'crm.lead', context=c),
164         'priority': lambda *a: crm.AVAILABLE_PRIORITIES[2][0],
165     }
166
167     def onchange_partner_address_id(self, cr, uid, ids, add, email=False):
168         """This function returns value of partner email based on Partner Address
169         @param self: The object pointer
170         @param cr: the current row, from the database cursor,
171         @param uid: the current user’s ID for security checks,
172         @param ids: List of case IDs
173         @param add: Id of Partner's address
174         @email: Partner's email ID
175         """
176         if not add:
177             return {'value': {'email_from': False, 'country_id': False}}
178         address = self.pool.get('res.partner.address').browse(cr, uid, add)
179         return {'value': {'email_from': address.email, 'phone': address.phone, 'country_id': address.country_id.id}}
180
181     def case_open(self, cr, uid, ids, *args):
182         """Overrides cancel for crm_case for setting Open Date
183         @param self: The object pointer
184         @param cr: the current row, from the database cursor,
185         @param uid: the current user’s ID for security checks,
186         @param ids: List of case's Ids
187         @param *args: Give Tuple Value
188         """
189         old_state = self.read(cr, uid, ids, ['state'])[0]['state']
190         res = super(crm_lead, self).case_open(cr, uid, ids, *args)
191         if old_state == 'draft':
192             stage_id = super(crm_lead, self).stage_next(cr, uid, ids, *args)
193             if stage_id:
194                 value = self.onchange_stage_id(cr, uid, ids, stage_id, context={})['value']
195             else:
196                 value = {}
197             value.update({'date_open': time.strftime('%Y-%m-%d %H:%M:%S'), 'stage_id': stage_id})
198             self.write(cr, uid, ids, value)
199
200         for (id, name) in self.name_get(cr, uid, ids):
201             type = self.browse(cr, uid, id).type or 'Lead'
202             message = (_('The ') + type.title()) + " '" + name + "' "+ _("has been Opened.")
203             self.log(cr, uid, id, message)
204         return res
205
206     def case_close(self, cr, uid, ids, *args):
207         """Overrides close for crm_case for setting close date
208         @param self: The object pointer
209         @param cr: the current row, from the database cursor,
210         @param uid: the current user’s ID for security checks,
211         @param ids: List of case Ids
212         @param *args: Tuple Value for additional Params
213         """
214         res = super(crm_lead, self).case_close(cr, uid, ids, args)
215         self.write(cr, uid, ids, {'date_closed': time.strftime('%Y-%m-%d %H:%M:%S')})
216         for (id, name) in self.name_get(cr, uid, ids):
217             lead = self.browse(cr, uid, id)
218             if lead.type == 'lead':
219                 message = _('The Lead') + " '" + name + "' "+ _("has been Closed.")
220                 self.log(cr, uid, id, message)
221         return res
222
223     def convert_opportunity(self, cr, uid, ids, context=None):
224         """ Precomputation for converting lead to opportunity
225         @param cr: the current row, from the database cursor,
226         @param uid: the current user’s ID for security checks,
227         @param ids: List of closeday’s IDs
228         @param context: A standard dictionary for contextual values
229         @return: Value of action in dict
230         """
231         if not context:
232             context = {}
233         context.update({'active_ids': ids})
234
235         data_obj = self.pool.get('ir.model.data')
236         data_id = data_obj._get_id(cr, uid, 'crm', 'view_crm_lead2opportunity_action')
237         value = {}
238
239         view_id = False
240         if data_id:
241             view_id = data_obj.browse(cr, uid, data_id, context=context).res_id
242
243         for case in self.browse(cr, uid, ids):
244             context.update({'active_id': case.id})
245             if not case.partner_id:
246                 data_id = data_obj._get_id(cr, uid, 'crm', 'view_crm_lead2opportunity_partner')
247                 view_id1 = False
248                 if data_id:
249                     view_id1 = data_obj.browse(cr, uid, data_id, context=context).res_id
250                 value = {
251                         'name': _('Create Partner'),
252                         'view_type': 'form',
253                         'view_mode': 'form,tree',
254                         'res_model': 'crm.lead2opportunity.partner',
255                         'view_id': False,
256                         'context': context,
257                         'views': [(view_id1, 'form')],
258                         'type': 'ir.actions.act_window',
259                         'target': 'new',
260                         'nodestroy': True
261                         }
262                 break
263             else:
264                 value = {
265                         'name': _('Create Opportunity'),
266                         'view_type': 'form',
267                         'view_mode': 'form,tree',
268                         'res_model': 'crm.lead2opportunity.action',
269                         'view_id': False,
270                         'context': context,
271                         'views': [(view_id, 'form')],
272                         'type': 'ir.actions.act_window',
273                         'target': 'new',
274                         'nodestroy': True
275                         }
276         return value
277
278     def stage_next(self, cr, uid, ids, context=None):
279         stage = super(crm_lead, self).stage_next(cr, uid, ids, context)
280         if stage:
281             stage_obj = self.pool.get('crm.case.stage').browse(cr, uid, stage, context=context)
282             if stage_obj.on_change:
283                 data = {'probability': stage_obj.probability}
284                 self.write(cr, uid, ids, data)
285         return stage
286
287     def message_new(self, cr, uid, msg, context):
288         """
289         Automatically calls when new email message arrives
290
291         @param self: The object pointer
292         @param cr: the current row, from the database cursor,
293         @param uid: the current user’s ID for security checks
294         """
295
296         mailgate_pool = self.pool.get('email.server.tools')
297
298         subject = msg.get('subject')
299         body = msg.get('body')
300         msg_from = msg.get('from')
301         priority = msg.get('priority')
302
303         vals = {
304             'name': subject,
305             'email_from': msg_from,
306             'email_cc': msg.get('cc'),
307             'description': body,
308             'user_id': False,
309         }
310         if msg.get('priority', False):
311             vals['priority'] = priority
312
313         res = mailgate_pool.get_partner(cr, uid, msg.get('from') or msg.get_unixfrom())
314         if res:
315             vals.update(res)
316
317         res = self.create(cr, uid, vals, context)
318
319         message = _('A Lead created') + " '" + subject + "' " + _("from Mailgate.")
320         self.log(cr, uid, res, message)
321
322         attachents = msg.get('attachments', [])
323         for attactment in attachents or []:
324             data_attach = {
325                 'name': attactment,
326                 'datas':binascii.b2a_base64(str(attachents.get(attactment))),
327                 'datas_fname': attactment,
328                 'description': 'Mail attachment',
329                 'res_model': self._name,
330                 'res_id': res,
331             }
332             self.pool.get('ir.attachment').create(cr, uid, data_attach)
333
334         return res
335
336     def message_update(self, cr, uid, ids, vals={}, msg="", default_act='pending', context={}):
337         """
338         @param self: The object pointer
339         @param cr: the current row, from the database cursor,
340         @param uid: the current user’s ID for security checks,
341         @param ids: List of update mail’s IDs
342         """
343
344         if isinstance(ids, (str, int, long)):
345             ids = [ids]
346
347         if msg.get('priority') in dict(crm.AVAILABLE_PRIORITIES):
348             vals['priority'] = msg.get('priority')
349
350         maps = {
351             'cost':'planned_cost',
352             'revenue': 'planned_revenue',
353             'probability':'probability'
354         }
355         vls = {}
356         for line in msg['body'].split('\n'):
357             line = line.strip()
358             res = tools.misc.command_re.match(line)
359             if res and maps.get(res.group(1).lower()):
360                 key = maps.get(res.group(1).lower())
361                 vls[key] = res.group(2).lower()
362         vals.update(vls)
363
364         # Unfortunately the API is based on lists
365         # but we want to update the state based on the
366         # previous state, so we have to loop:
367         for case in self.browse(cr, uid, ids, context=context):
368             values = dict(vals)
369             if case.state in CRM_LEAD_PENDING_STATES:
370                 values.update(state=crm.AVAILABLE_STATES[1][0]) #re-open
371             res = self.write(cr, uid, [case.id], values, context=context)
372
373         return res
374
375     def msg_send(self, cr, uid, id, *args, **argv):
376
377         """ Send The Message
378             @param self: The object pointer
379             @param cr: the current row, from the database cursor,
380             @param uid: the current user’s ID for security checks,
381             @param ids: List of email’s IDs
382             @param *args: Return Tuple Value
383             @param **args: Return Dictionary of Keyword Value
384         """
385         return True
386 crm_lead()
387
388 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: