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