[IMP]: mail_gateway: Added a function field in mailgate message to provide a better...
[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, orm
23 from datetime import datetime, timedelta
24 import crm
25 import math
26 import time
27 import mx.DateTime
28 from tools.translate import _
29 from crm import crm_case
30 import collections
31 import binascii
32 import tools
33
34
35 CRM_LEAD_PENDING_STATES = (
36     crm.AVAILABLE_STATES[2][0], # Cancelled
37     crm.AVAILABLE_STATES[3][0], # Done
38     crm.AVAILABLE_STATES[4][0], # Pending
39 )
40
41 class crm_lead(osv.osv, crm_case):
42     """ CRM Lead Case """
43     _name = "crm.lead"
44     _description = "Lead"
45     _order = "priority, id desc"
46     _inherit = ['mailgate.thread','res.partner.address']
47     def _compute_day(self, cr, uid, ids, fields, args, context={}):
48         """
49         @param cr: the current row, from the database cursor,
50         @param uid: the current user’s ID for security checks,
51         @param ids: List of Openday’s IDs
52         @return: difference between current date and log date
53         @param context: A standard dictionary for contextual values
54         """
55         cal_obj = self.pool.get('resource.calendar')
56         res_obj = self.pool.get('resource.resource')
57
58         res = {}
59         for lead in self.browse(cr, uid, ids , context):
60             for field in fields:
61                 res[lead.id] = {}
62                 duration = 0
63                 ans = False
64                 if field == 'day_open':
65                     if lead.date_open:
66                         date_create = datetime.strptime(lead.create_date, "%Y-%m-%d %H:%M:%S")
67                         date_open = datetime.strptime(lead.date_open, "%Y-%m-%d %H:%M:%S")
68                         ans = date_open - date_create
69                         date_until = lead.date_open
70                 elif field == 'day_close':
71                     if lead.date_closed:
72                         date_create = datetime.strptime(lead.create_date, "%Y-%m-%d %H:%M:%S")
73                         date_close = datetime.strptime(lead.date_closed, "%Y-%m-%d %H:%M:%S")
74                         date_until = lead.date_closed
75                         ans = date_close - date_create
76                 if ans:
77                     resource_id = False
78                     if lead.user_id:
79                         resource_ids = res_obj.search(cr, uid, [('user_id','=',lead.user_id.id)])
80                         if len(resource_ids):
81                             resource_id = resource_ids[0]
82
83                     duration = float(ans.days)
84                     if lead.section_id and lead.section_id.resource_calendar_id:
85                         duration =  float(ans.days) * 24
86                         new_dates = cal_obj.interval_get(cr,
87                             uid,
88                             lead.section_id.resource_calendar_id and lead.section_id.resource_calendar_id.id or False,
89                             mx.DateTime.strptime(lead.create_date, '%Y-%m-%d %H:%M:%S'),
90                             duration,
91                             resource=resource_id
92                         )
93                         no_days = []
94                         date_until = mx.DateTime.strptime(date_until, '%Y-%m-%d %H:%M:%S')
95                         for in_time, out_time in new_dates:
96                             if in_time.date not in no_days:
97                                 no_days.append(in_time.date)
98                             if out_time > date_until:
99                                 break
100                         duration =  len(no_days)
101                 res[lead.id][field] = abs(int(duration))
102         return res
103
104     _columns = {
105         # Overridden from res.partner.address:
106         'partner_id': fields.many2one('res.partner', 'Partner', ondelete='set null', 
107             select=True, help="Optional linked partner, usually after conversion of the lead"),
108         
109         # From crm.case
110         'name': fields.char('Name', size=64),
111         'active': fields.boolean('Active', required=False),
112         'date_action_last': fields.datetime('Last Action', readonly=1),
113         'date_action_next': fields.datetime('Next Action', readonly=1),
114         'email_from': fields.char('Email', size=128, help="E-mail address of the contact"),
115         'section_id': fields.many2one('crm.case.section', 'Sales Team', \
116                         select=True, help='Sales team to which this case belongs to. Defines responsible user and e-mail address for the mail gateway.'),
117         'create_date': fields.datetime('Creation Date' , readonly=True),
118         'email_cc': fields.text('Watchers Emails', size=252 , help="These \
119 addresses(Comma-separated) will receive a copy of the future e-mail communication between partner \
120 and users"),
121         'description': fields.text('Notes'),
122         'write_date': fields.datetime('Update Date' , readonly=True),
123
124         # Lead fields
125         'categ_id': fields.many2one('crm.case.categ', 'Lead Source', \
126                         domain="[('section_id','=',section_id),\
127                         ('object_id.model', '=', 'crm.lead')]"),
128         'type_id': fields.many2one('crm.case.resource.type', 'Lead Type', \
129                          domain="[('section_id','=',section_id),\
130                         ('object_id.model', '=', 'crm.lead')]"),
131         'partner_name': fields.char("Partner Name", size=64),
132         'optin': fields.boolean('Opt-In'),
133         'optout': fields.boolean('Opt-Out'),
134         'type':fields.selection([
135             ('lead','Lead'),
136             ('opportunity','Opportunity'),
137
138         ],'Type', help="Type is used to separate Leads and Opportunities"),
139         'priority': fields.selection(crm.AVAILABLE_PRIORITIES, 'Priority'),
140         'date_closed': fields.datetime('Closed', readonly=True),
141         'stage_id': fields.many2one('crm.case.stage', 'Stage'),
142         'user_id': fields.many2one('res.users', 'Salesman',help='By Default Salesman is Administrator when create New User'),
143         'referred': fields.char('Referred By', size=64),
144         'date_open': fields.datetime('Opened', readonly=True),
145         'day_open': fields.function(_compute_day, string='Days to Open', \
146                                 method=True, multi='day_open', type="float", store=True),
147         'day_close': fields.function(_compute_day, string='Days to Close', \
148                                 method=True, multi='day_close', type="float", store=True),
149         'state': fields.selection(crm.AVAILABLE_STATES, 'State', size=16, readonly=True,
150                                   help='The state is set to \'Draft\', when a case is created.\
151                                   \nIf the case is in progress the state is set to \'Open\'.\
152                                   \nWhen the case is over, the state is set to \'Done\'.\
153                                   \nIf the case needs to be reviewed then the state is set to \'Pending\'.'), 
154         'message_ids': fields.one2many('mailgate.message', 'res_id', 'Messages', domain=[('model','=',_name)], readonly=True),
155         'partner_assigned_id': fields.many2one('res.partner', 'Assigned Partner', help="Partner this case has been forwarded/assigned to.", select=True),
156         'date_assign': fields.date('Assignation Date', help="Last date this case was forwarded/assigned to a partner"),
157     }
158
159     _defaults = {
160         'active': lambda *a: 1,
161         'user_id': crm_case._get_default_user,
162         'email_from': crm_case._get_default_email,
163         'state': lambda *a: 'draft',
164         'type': lambda *a: 'lead',
165         'section_id': crm_case._get_section,
166         'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'crm.lead', context=c),
167         'priority': lambda *a: crm.AVAILABLE_PRIORITIES[2][0],
168     }
169     
170     def onchange_partner_address_id(self, cr, uid, ids, add, email=False):
171         """This function returns value of partner email based on Partner Address
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 add: Id of Partner's address
177         @email: Partner's email ID
178         """
179         if not add:
180             return {'value': {'email_from': False, 'country_id': False}}
181         address = self.pool.get('res.partner.address').browse(cr, uid, add)
182         return {'value': {'email_from': address.email, 'phone': address.phone, 'country_id': address.country_id.id}}
183     
184     def case_open(self, cr, uid, ids, *args):
185         """Overrides cancel for crm_case for setting Open Date
186         @param self: The object pointer
187         @param cr: the current row, from the database cursor,
188         @param uid: the current user’s ID for security checks,
189         @param ids: List of case's Ids
190         @param *args: Give Tuple Value
191         """
192         old_state = self.read(cr, uid, ids, ['state'])[0]['state']
193         res = super(crm_lead, self).case_open(cr, uid, ids, *args)
194         if old_state == 'draft':
195             stage_id = super(crm_lead, self).stage_next(cr, uid, ids, *args)
196             if not stage_id:
197                 raise osv.except_osv(_('Warning !'), _('There is no stage defined for this Sale Team.'))
198             value = self.onchange_stage_id(cr, uid, ids, stage_id, context={})['value']
199             value.update({'date_open': time.strftime('%Y-%m-%d %H:%M:%S'), 'stage_id': stage_id})
200             self.write(cr, uid, ids, value)
201
202         for (id, name) in self.name_get(cr, uid, ids):
203             message = _('The Lead') + " '" + name + "' "+ _("has been written as Open.")
204             self.log(cr, uid, id, message)
205         return res
206
207     def case_close(self, cr, uid, ids, *args):
208         """Overrides close for crm_case for setting close date
209         @param self: The object pointer
210         @param cr: the current row, from the database cursor,
211         @param uid: the current user’s ID for security checks,
212         @param ids: List of case Ids
213         @param *args: Tuple Value for additional Params
214         """
215         res = super(crm_lead, self).case_close(cr, uid, ids, args)
216         self.write(cr, uid, ids, {'date_closed': time.strftime('%Y-%m-%d %H:%M:%S')})
217         for (id, name) in self.name_get(cr, uid, ids):
218             message = _('The Lead') + " '" + name + "' "+ _("has been written as Closed.")
219             self.log(cr, uid, id, message)
220         return res
221
222     def convert_opportunity(self, cr, uid, ids, context=None):
223         """ Precomputation for converting lead to opportunity
224         @param cr: the current row, from the database cursor,
225         @param uid: the current user’s ID for security checks,
226         @param ids: List of closeday’s IDs
227         @param context: A standard dictionary for contextual values
228         @return: Value of action in dict
229         """
230         if not context:
231             context = {}
232         context.update({'active_ids': ids})
233
234         data_obj = self.pool.get('ir.model.data')
235         data_id = data_obj._get_id(cr, uid, 'crm', 'view_crm_lead2opportunity_create')
236         value = {}
237
238         view_id = False
239         if data_id:
240             view_id = data_obj.browse(cr, uid, data_id, context=context).res_id
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',
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         msg_from = msg['from']
346         if msg.get('priority'):
347             vals['priority'] = msg.get('priority')
348
349         maps = {
350             'cost':'planned_cost',
351             'revenue': 'planned_revenue',
352             'probability':'probability'
353         }
354         vls = {}
355         for line in msg['body'].split('\n'):
356             line = line.strip()
357             res = tools.misc.command_re.match(line)
358             if res and maps.get(res.group(1).lower()):
359                 key = maps.get(res.group(1).lower())
360                 vls[key] = res.group(2).lower()
361         vals.update(vls)
362
363         # Unfortunately the API is based on lists
364         # but we want to update the state based on the
365         # previous state, so we have to loop:
366         for case in self.browse(cr, uid, ids, context=context):
367             values = dict(vals)
368             if case.state in CRM_LEAD_PENDING_STATES:
369                 values.update(state=crm.AVAILABLE_STATES[1][0]) #re-open
370             res = self.write(cr, uid, [case.id], values, context=context)
371
372         return res
373
374     def msg_send(self, cr, uid, id, *args, **argv):
375
376         """ Send The Message
377             @param self: The object pointer
378             @param cr: the current row, from the database cursor,
379             @param uid: the current user’s ID for security checks,
380             @param ids: List of email’s IDs
381             @param *args: Return Tuple Value
382             @param **args: Return Dictionary of Keyword Value
383         """
384         return True
385 crm_lead()
386
387 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: