[FIX]: Add type, storage_id and calendar_collection field in caldav view.
[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.lead')]"),
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             self.history(cr, uid, ids, _('Stage'), details=stage_obj.name)
290             for case in self.browse(cr, uid, ids, context=context):
291                 if case.type == 'lead':
292                     message = _("The stage of lead '%s' has been changed to '%s'.") % (case.name, case.stage_id.name)
293                 elif case.type == 'opportunity':
294                     message = _("The stage of opportunity '%s' has been changed to '%s'.") % (case.name, case.stage_id.name)
295                 self.log(cr, uid, case.id, message)
296             if stage_obj.on_change:
297                 data = {'probability': stage_obj.probability}
298                 self.write(cr, uid, ids, data)
299         return stage
300     
301     def stage_previous(self, cr, uid, ids, context=None):
302         stage = super(crm_lead, self).stage_previous(cr, uid, ids, context)
303         if stage:
304             stage_obj = self.pool.get('crm.case.stage').browse(cr, uid, stage, context=context)
305             self.history(cr, uid, ids, _('Stage'), details=stage_obj.name)
306             for case in self.browse(cr, uid, ids, context=context):
307                 if case.type == 'lead':
308                     message = _("The stage of lead '%s' has been changed to '%s'.") % (case.name, case.stage_id.name)
309                 elif case.type == 'opportunity':
310                     message = _("The stage of opportunity '%s' has been changed to '%s'.") % (case.name, case.stage_id.name)
311                 self.log(cr, uid, case.id, message)
312         return stage
313     
314     def message_new(self, cr, uid, msg, context):
315         """
316         Automatically calls when new email message arrives
317
318         @param self: The object pointer
319         @param cr: the current row, from the database cursor,
320         @param uid: the current user’s ID for security checks
321         """
322
323         mailgate_pool = self.pool.get('email.server.tools')
324
325         subject = msg.get('subject')
326         body = msg.get('body')
327         msg_from = msg.get('from')
328         priority = msg.get('priority')
329
330         vals = {
331             'name': subject,
332             'email_from': msg_from,
333             'email_cc': msg.get('cc'),
334             'description': body,
335             'user_id': False,
336         }
337         if msg.get('priority', False):
338             vals['priority'] = priority
339
340         res = mailgate_pool.get_partner(cr, uid, msg.get('from') or msg.get_unixfrom())
341         if res:
342             vals.update(res)
343
344         res = self.create(cr, uid, vals, context)
345         attachents = msg.get('attachments', [])
346         for attactment in attachents or []:
347             data_attach = {
348                 'name': attactment,
349                 'datas':binascii.b2a_base64(str(attachents.get(attactment))),
350                 'datas_fname': attactment,
351                 'description': 'Mail attachment',
352                 'res_model': self._name,
353                 'res_id': res,
354             }
355             self.pool.get('ir.attachment').create(cr, uid, data_attach)
356
357         return res
358
359     def message_update(self, cr, uid, ids, vals={}, msg="", default_act='pending', context={}):
360         """
361         @param self: The object pointer
362         @param cr: the current row, from the database cursor,
363         @param uid: the current user’s ID for security checks,
364         @param ids: List of update mail’s IDs 
365         """
366
367         if isinstance(ids, (str, int, long)):
368             ids = [ids]
369
370         if msg.get('priority') in dict(crm.AVAILABLE_PRIORITIES):
371             vals['priority'] = msg.get('priority')
372
373         maps = {
374             'cost':'planned_cost',
375             'revenue': 'planned_revenue',
376             'probability':'probability'
377         }
378         vls = {}
379         for line in msg['body'].split('\n'):
380             line = line.strip()
381             res = tools.misc.command_re.match(line)
382             if res and maps.get(res.group(1).lower()):
383                 key = maps.get(res.group(1).lower())
384                 vls[key] = res.group(2).lower()
385         vals.update(vls)
386
387         # Unfortunately the API is based on lists
388         # but we want to update the state based on the
389         # previous state, so we have to loop:
390         for case in self.browse(cr, uid, ids, context=context):
391             values = dict(vals)
392             if case.state in CRM_LEAD_PENDING_STATES:
393                 values.update(state=crm.AVAILABLE_STATES[1][0]) #re-open
394             res = self.write(cr, uid, [case.id], values, context=context)
395
396         return res
397
398     def msg_send(self, cr, uid, id, *args, **argv):
399
400         """ Send The Message
401             @param self: The object pointer
402             @param cr: the current row, from the database cursor,
403             @param uid: the current user’s ID for security checks,
404             @param ids: List of email’s IDs
405             @param *args: Return Tuple Value
406             @param **args: Return Dictionary of Keyword Value
407         """
408         return True
409 crm_lead()
410
411 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: