[IMP]: crm: Added tooltip for opt-in and opt-out
[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('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"),
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),('section_id','=',False), ('object_id.model', '=', 'crm.project.bug')]"),
124         'type_id': fields.many2one('crm.case.resource.type', 'Campaign', \
125             domain="['|',('section_id','=',section_id),('section_id','=',False)]"),
126         'channel_id': fields.many2one('res.partner.canal', 'Channel'),
127
128         'contact_name': fields.char('Contact Name', size=64), 
129         'partner_name': fields.char("Partner Name", size=64),
130         'optin': fields.boolean('Opt-In', help="If opt-in is checked, this contact has accepted to receive emails."),
131         'optout': fields.boolean('Opt-Out', help="If opt-out is checked, this contact has refused to receive emails or unsubscribed to a campaign."),
132         'type':fields.selection([
133             ('lead','Lead'),
134             ('opportunity','Opportunity'),
135
136         ],'Type', help="Type is used to separate Leads and Opportunities"),
137         'priority': fields.selection(crm.AVAILABLE_PRIORITIES, 'Priority'),
138         'date_closed': fields.datetime('Closed', readonly=True),
139         'stage_id': fields.many2one('crm.case.stage', 'Stage'),
140         'user_id': fields.many2one('res.users', 'Salesman',help='By Default Salesman is Administrator when create New User'),
141         'referred': fields.char('Referred By', size=64),
142         'date_open': fields.datetime('Opened', readonly=True),
143         'day_open': fields.function(_compute_day, string='Days to Open', \
144                                 method=True, multi='day_open', type="float", store=True),
145         'day_close': fields.function(_compute_day, string='Days to Close', \
146                                 method=True, multi='day_close', type="float", store=True),
147         'state': fields.selection(crm.AVAILABLE_STATES, 'State', size=16, readonly=True,
148                                   help='The state is set to \'Draft\', when a case is created.\
149                                   \nIf the case is in progress the state is set to \'Open\'.\
150                                   \nWhen the case is over, the state is set to \'Done\'.\
151                                   \nIf the case needs to be reviewed then the state is set to \'Pending\'.'), 
152         'message_ids': fields.one2many('mailgate.message', 'res_id', 'Messages', domain=[('model','=',_name)]),
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     }
165
166     def onchange_partner_address_id(self, cr, uid, ids, add, email=False):
167         """This function returns value of partner email based on Partner Address
168         @param self: The object pointer
169         @param cr: the current row, from the database cursor,
170         @param uid: the current user’s ID for security checks,
171         @param ids: List of case IDs
172         @param add: Id of Partner's address
173         @email: Partner's email ID
174         """
175         if not add:
176             return {'value': {'email_from': False, 'country_id': False}}
177         address = self.pool.get('res.partner.address').browse(cr, uid, add)
178         return {'value': {'email_from': address.email, 'phone': address.phone, 'country_id': address.country_id.id}}
179
180     def case_open(self, cr, uid, ids, *args):
181         """Overrides cancel for crm_case for setting Open Date
182         @param self: The object pointer
183         @param cr: the current row, from the database cursor,
184         @param uid: the current user’s ID for security checks,
185         @param ids: List of case's Ids
186         @param *args: Give Tuple Value
187         """
188         old_state = self.read(cr, uid, ids, ['state'])[0]['state']
189         res = super(crm_lead, self).case_open(cr, uid, ids, *args)
190         if old_state == 'draft':
191             stage_id = super(crm_lead, self).stage_next(cr, uid, ids, *args)
192             if stage_id:
193                 value = self.onchange_stage_id(cr, uid, ids, stage_id, context={})['value']
194             else:
195                 value = {}
196             value.update({'date_open': time.strftime('%Y-%m-%d %H:%M:%S'), 'stage_id': stage_id})
197             self.write(cr, uid, ids, value)
198
199         for (id, name) in self.name_get(cr, uid, ids):
200             type = self.browse(cr, uid, id).type or 'Lead'
201             message = (_('The ') + type.title()) + " '" + name + "' "+ _("has been Opened.")
202             self.log(cr, uid, id, message)
203         return res
204
205     def case_close(self, cr, uid, ids, *args):
206         """Overrides close for crm_case for setting close date
207         @param self: The object pointer
208         @param cr: the current row, from the database cursor,
209         @param uid: the current user’s ID for security checks,
210         @param ids: List of case Ids
211         @param *args: Tuple Value for additional Params
212         """
213         res = super(crm_lead, self).case_close(cr, uid, ids, args)
214         self.write(cr, uid, ids, {'date_closed': time.strftime('%Y-%m-%d %H:%M:%S')})
215         for (id, name) in self.name_get(cr, uid, ids):
216             lead = self.browse(cr, uid, id)
217             if lead.type == 'lead':
218                 message = _('The Lead') + " '" + name + "' "+ _("has been 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_action')
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
242         for case in self.browse(cr, uid, ids):
243             context.update({'active_id': case.id})
244             if not case.partner_id:
245                 data_id = data_obj._get_id(cr, uid, 'crm', 'view_crm_lead2opportunity_partner')
246                 view_id1 = False
247                 if data_id:
248                     view_id1 = data_obj.browse(cr, uid, data_id, context=context).res_id
249                 value = {
250                         'name': _('Create Partner'),
251                         'view_type': 'form',
252                         'view_mode': 'form,tree',
253                         'res_model': 'crm.lead2opportunity.partner',
254                         'view_id': False,
255                         'context': context,
256                         'views': [(view_id1, 'form')],
257                         'type': 'ir.actions.act_window',
258                         'target': 'new',
259                         'nodestroy': True
260                         }
261                 break
262             else:
263                 value = {
264                         'name': _('Create Opportunity'),
265                         'view_type': 'form',
266                         'view_mode': 'form,tree',
267                         'res_model': 'crm.lead2opportunity.action',
268                         'view_id': False,
269                         'context': context,
270                         'views': [(view_id, 'form')],
271                         'type': 'ir.actions.act_window',
272                         'target': 'new',
273                         'nodestroy': True
274                         }
275         return value
276
277     def stage_next(self, cr, uid, ids, context=None):
278         stage = super(crm_lead, self).stage_next(cr, uid, ids, context)
279         if stage:
280             stage_obj = self.pool.get('crm.case.stage').browse(cr, uid, stage, context=context)
281             if stage_obj.on_change:
282                 data = {'probability': stage_obj.probability}
283                 self.write(cr, uid, ids, data)
284         return stage
285
286     def message_new(self, cr, uid, msg, context):
287         """
288         Automatically calls when new email message arrives
289
290         @param self: The object pointer
291         @param cr: the current row, from the database cursor,
292         @param uid: the current user’s ID for security checks
293         """
294
295         mailgate_pool = self.pool.get('email.server.tools')
296
297         subject = msg.get('subject')
298         body = msg.get('body')
299         msg_from = msg.get('from')
300         priority = msg.get('priority')
301
302         vals = {
303             'name': subject,
304             'email_from': msg_from,
305             'email_cc': msg.get('cc'),
306             'description': body,
307             'user_id': False,
308         }
309         if msg.get('priority', False):
310             vals['priority'] = priority
311
312         res = mailgate_pool.get_partner(cr, uid, msg.get('from') or msg.get_unixfrom())
313         if res:
314             vals.update(res)
315
316         res = self.create(cr, uid, vals, context)
317         
318         message = _('A Lead created') + " '" + subject + "' " + _("from Mailgate.")
319         self.log(cr, uid, res, message)
320         
321         attachents = msg.get('attachments', [])
322         for attactment in attachents or []:
323             data_attach = {
324                 'name': attactment,
325                 'datas':binascii.b2a_base64(str(attachents.get(attactment))),
326                 'datas_fname': attactment,
327                 'description': 'Mail attachment',
328                 'res_model': self._name,
329                 'res_id': res,
330             }
331             self.pool.get('ir.attachment').create(cr, uid, data_attach)
332
333         return res
334
335     def message_update(self, cr, uid, ids, vals={}, msg="", default_act='pending', context={}):
336         """
337         @param self: The object pointer
338         @param cr: the current row, from the database cursor,
339         @param uid: the current user’s ID for security checks,
340         @param ids: List of update mail’s IDs 
341         """
342
343         if isinstance(ids, (str, int, long)):
344             ids = [ids]
345
346         if msg.get('priority') in dict(crm.AVAILABLE_PRIORITIES):
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: