1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
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.
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.
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/>.
20 ##############################################################################
22 from osv import fields, osv
23 from datetime import datetime
26 from tools.translate import _
27 from crm import crm_case
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
38 class crm_lead(crm_case, osv.osv):
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={}):
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
52 cal_obj = self.pool.get('resource.calendar')
53 res_obj = self.pool.get('resource.resource')
56 for lead in self.browse(cr, uid, ids , context):
61 if field == 'day_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':
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
76 resource_ids = res_obj.search(cr, uid, [('user_id','=',lead.user_id.id)])
78 resource_id = resource_ids[0]
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,
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'),
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:
97 duration = len(no_days)
98 res[lead.id][field] = abs(int(duration))
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"),
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),
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'),
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([
133 ('opportunity','Opportunity'),
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)]),
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],
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
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}}
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
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':
193 stage_id = super(crm_lead, self).stage_next(cr, uid, ids, *args)
195 value.update(self.onchange_stage_id(cr, uid, ids, stage_id, context={})['value'])
196 value.update({'date_open': time.strftime('%Y-%m-%d %H:%M:%S')})
197 self.write(cr, uid, ids, value)
199 for case in self.browse(cr, uid, ids):
200 if case.type == 'lead':
201 message = _("The lead '%s' has been opened.") % case.name
202 elif case.type == 'opportunity':
203 message = _("The opportunity '%s' has been opened.") % case.name
205 message = _("The case '%s' has been opened.") % case.name
206 self.log(cr, uid, case.id, message)
209 def case_close(self, cr, uid, ids, *args):
210 """Overrides close for crm_case for setting close date
211 @param self: The object pointer
212 @param cr: the current row, from the database cursor,
213 @param uid: the current user’s ID for security checks,
214 @param ids: List of case Ids
215 @param *args: Tuple Value for additional Params
217 res = super(crm_lead, self).case_close(cr, uid, ids, args)
218 self.write(cr, uid, ids, {'date_closed': time.strftime('%Y-%m-%d %H:%M:%S')})
219 for case in self.browse(cr, uid, ids):
220 if case.type == 'lead':
221 message = _("The lead '%s' has been closed.") % case.name
222 elif case.type == 'opportunity':
223 message = _("The opportunity '%s' has been closed.") % case.name
225 message = _("The case '%s' has been closed.") % case.name
226 self.log(cr, uid, case.id, message)
229 def convert_opportunity(self, cr, uid, ids, context=None):
230 """ Precomputation for converting lead to opportunity
231 @param cr: the current row, from the database cursor,
232 @param uid: the current user’s ID for security checks,
233 @param ids: List of closeday’s IDs
234 @param context: A standard dictionary for contextual values
235 @return: Value of action in dict
239 context.update({'active_ids': ids})
241 data_obj = self.pool.get('ir.model.data')
242 data_id = data_obj._get_id(cr, uid, 'crm', 'view_crm_lead2opportunity_action')
247 view_id = data_obj.browse(cr, uid, data_id, context=context).res_id
249 for case in self.browse(cr, uid, ids):
250 context.update({'active_id': case.id})
251 if not case.partner_id:
252 data_id = data_obj._get_id(cr, uid, 'crm', 'view_crm_lead2opportunity_partner')
255 view_id1 = data_obj.browse(cr, uid, data_id, context=context).res_id
257 'name': _('Create Partner'),
259 'view_mode': 'form,tree',
260 'res_model': 'crm.lead2opportunity.partner',
263 'views': [(view_id1, 'form')],
264 'type': 'ir.actions.act_window',
271 'name': _('Create Opportunity'),
273 'view_mode': 'form,tree',
274 'res_model': 'crm.lead2opportunity.action',
277 'views': [(view_id, 'form')],
278 'type': 'ir.actions.act_window',
284 def write(self, cr, uid, ids, vals, context={}):
285 if 'date_closed' in vals:
286 return super(crm_lead,self).write(cr, uid, ids, vals, context)
288 if 'stage_id' in vals and vals['stage_id']:
289 stage_obj = self.pool.get('crm.case.stage').browse(cr, uid, vals['stage_id'], context=context)
290 self.history(cr, uid, ids, _('Stage'), details=stage_obj.name)
291 for case in self.browse(cr, uid, ids, context=context):
292 if case.type == 'lead':
293 message = _("The stage of lead '%s' has been changed to '%s'.") % (case.name, case.stage_id.name)
294 elif case.type == 'opportunity':
295 message = _("The stage of opportunity '%s' has been changed to '%s'.") % (case.name, case.stage_id.name)
296 self.log(cr, uid, case.id, message)
297 return super(crm_lead,self).write(cr, uid, ids, vals, context)
299 def stage_next(self, cr, uid, ids, context=None):
300 stage = super(crm_lead, self).stage_next(cr, uid, ids, context)
302 stage_obj = self.pool.get('crm.case.stage').browse(cr, uid, stage, context=context)
303 if stage_obj.on_change:
304 data = {'probability': stage_obj.probability}
305 self.write(cr, uid, ids, data)
308 def message_new(self, cr, uid, msg, context):
310 Automatically calls when new email message arrives
312 @param self: The object pointer
313 @param cr: the current row, from the database cursor,
314 @param uid: the current user’s ID for security checks
317 mailgate_pool = self.pool.get('email.server.tools')
319 subject = msg.get('subject')
320 body = msg.get('body')
321 msg_from = msg.get('from')
322 priority = msg.get('priority')
326 'email_from': msg_from,
327 'email_cc': msg.get('cc'),
331 if msg.get('priority', False):
332 vals['priority'] = priority
334 res = mailgate_pool.get_partner(cr, uid, msg.get('from') or msg.get_unixfrom())
338 res = self.create(cr, uid, vals, context)
339 attachents = msg.get('attachments', [])
340 for attactment in attachents or []:
343 'datas':binascii.b2a_base64(str(attachents.get(attactment))),
344 'datas_fname': attactment,
345 'description': 'Mail attachment',
346 'res_model': self._name,
349 self.pool.get('ir.attachment').create(cr, uid, data_attach)
353 def message_update(self, cr, uid, ids, vals={}, msg="", default_act='pending', context={}):
355 @param self: The object pointer
356 @param cr: the current row, from the database cursor,
357 @param uid: the current user’s ID for security checks,
358 @param ids: List of update mail’s IDs
361 if isinstance(ids, (str, int, long)):
364 if msg.get('priority') in dict(crm.AVAILABLE_PRIORITIES):
365 vals['priority'] = msg.get('priority')
368 'cost':'planned_cost',
369 'revenue': 'planned_revenue',
370 'probability':'probability'
373 for line in msg['body'].split('\n'):
375 res = tools.misc.command_re.match(line)
376 if res and maps.get(res.group(1).lower()):
377 key = maps.get(res.group(1).lower())
378 vls[key] = res.group(2).lower()
381 # Unfortunately the API is based on lists
382 # but we want to update the state based on the
383 # previous state, so we have to loop:
384 for case in self.browse(cr, uid, ids, context=context):
386 if case.state in CRM_LEAD_PENDING_STATES:
387 values.update(state=crm.AVAILABLE_STATES[1][0]) #re-open
388 res = self.write(cr, uid, [case.id], values, context=context)
392 def msg_send(self, cr, uid, id, *args, **argv):
395 @param self: The object pointer
396 @param cr: the current row, from the database cursor,
397 @param uid: the current user’s ID for security checks,
398 @param ids: List of email’s IDs
399 @param *args: Return Tuple Value
400 @param **args: Return Dictionary of Keyword Value
405 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: