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):
41 _description = "Lead/Opportunity"
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=None):
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=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,help='The name of the future partner that will be created while converting the into opportunity'),
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', domain="[('type','=','lead')]"),
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)]),
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 #'stage_id': _get_stage_id,
169 def onchange_partner_address_id(self, cr, uid, ids, add, email=False):
170 """This function returns value of partner email based on Partner Address
171 @param self: The object pointer
172 @param cr: the current row, from the database cursor,
173 @param uid: the current user’s ID for security checks,
174 @param ids: List of case IDs
175 @param add: Id of Partner's address
176 @email: Partner's email ID
179 return {'value': {'email_from': False, 'country_id': False}}
180 address = self.pool.get('res.partner.address').browse(cr, uid, add)
181 return {'value': {'email_from': address.email, 'phone': address.phone, 'country_id': address.country_id.id}}
183 def case_open(self, cr, uid, ids, *args):
184 """Overrides cancel for crm_case for setting Open Date
185 @param self: The object pointer
186 @param cr: the current row, from the database cursor,
187 @param uid: the current user’s ID for security checks,
188 @param ids: List of case's Ids
189 @param *args: Give Tuple Value
191 leads = self.browse(cr, uid, ids)
195 for i in xrange(0, len(ids)):
196 if leads[i].state == 'draft':
198 if not leads[i].stage_id :
199 stage_id = self._find_first_stage(cr, uid, leads[i].type, leads[i].section_id.id or False)
200 value.update({'stage_id' : stage_id})
201 value.update({'date_open': time.strftime('%Y-%m-%d %H:%M:%S')})
202 self.write(cr, uid, [ids[i]], value)
203 self.log_open( cr, uid, leads[i])
204 res = super(crm_lead, self).case_open(cr, uid, ids, *args)
207 def log_open(self, cr, uid, case):
208 if case.type == 'lead':
209 message = _("The lead '%s' has been opened.") % case.name
210 elif case.type == 'opportunity':
211 message = _("The opportunity '%s' has been opened.") % case.name
213 message = _("The case '%s' has been opened.") % case.name
214 self.log(cr, uid, case.id, message)
216 def case_close(self, cr, uid, ids, *args):
217 """Overrides close for crm_case for setting close date
218 @param self: The object pointer
219 @param cr: the current row, from the database cursor,
220 @param uid: the current user’s ID for security checks,
221 @param ids: List of case Ids
222 @param *args: Tuple Value for additional Params
224 res = super(crm_lead, self).case_close(cr, uid, ids, *args)
225 self.write(cr, uid, ids, {'date_closed': time.strftime('%Y-%m-%d %H:%M:%S')})
226 for case in self.browse(cr, uid, ids):
227 if case.type == 'lead':
228 message = _("The lead '%s' has been closed.") % case.name
229 elif case.type == 'opportunity':
230 message = _("The opportunity '%s' has been closed.") % case.name
232 message = _("The case '%s' has been closed.") % case.name
233 self.log(cr, uid, case.id, message)
236 def convert_opportunity(self, cr, uid, ids, context=None):
237 """ Precomputation for converting lead to opportunity
238 @param cr: the current row, from the database cursor,
239 @param uid: the current user’s ID for security checks,
240 @param ids: List of closeday’s IDs
241 @param context: A standard dictionary for contextual values
242 @return: Value of action in dict
246 context.update({'active_ids': ids})
248 data_obj = self.pool.get('ir.model.data')
249 data_id = data_obj._get_id(cr, uid, 'crm', 'view_crm_lead2opportunity_action')
254 view_id = data_obj.browse(cr, uid, data_id, context=context).res_id
256 for case in self.browse(cr, uid, ids):
257 context.update({'active_id': case.id})
258 if not case.partner_id:
259 data_id = data_obj._get_id(cr, uid, 'crm', 'view_crm_lead2opportunity_partner')
262 view_id1 = data_obj.browse(cr, uid, data_id, context=context).res_id
264 'name': _('Create Partner'),
266 'view_mode': 'form,tree',
267 'res_model': 'crm.lead2opportunity.partner',
270 'views': [(view_id1, 'form')],
271 'type': 'ir.actions.act_window',
278 'name': _('Create Opportunity'),
280 'view_mode': 'form,tree',
281 'res_model': 'crm.lead2opportunity.action',
284 'views': [(view_id, 'form')],
285 'type': 'ir.actions.act_window',
291 def write(self, cr, uid, ids, vals, context=None):
295 if 'date_closed' in vals:
296 return super(crm_lead,self).write(cr, uid, ids, vals, context=context)
298 if 'stage_id' in vals and vals['stage_id']:
299 stage_obj = self.pool.get('crm.case.stage').browse(cr, uid, vals['stage_id'], context=context)
300 self.history(cr, uid, ids, _('Stage'), details=stage_obj.name)
302 for case in self.browse(cr, uid, ids, context=context):
303 if case.type == 'lead' or context.get('stage_type',False)=='lead':
304 message = _("The stage of lead '%s' has been changed to '%s'.") % (case.name, stage_obj.name)
305 elif case.type == 'opportunity':
306 message = _("The stage of opportunity '%s' has been changed to '%s'.") % (case.name, stage_obj.name)
307 self.log(cr, uid, case.id, message)
308 return super(crm_lead,self).write(cr, uid, ids, vals, context)
310 def stage_next(self, cr, uid, ids, context=None):
311 stage = super(crm_lead, self).stage_next(cr, uid, ids, context=context)
313 stage_obj = self.pool.get('crm.case.stage').browse(cr, uid, stage, context=context)
314 if stage_obj.on_change:
315 data = {'probability': stage_obj.probability}
316 self.write(cr, uid, ids, data)
319 def message_new(self, cr, uid, msg, context=None):
321 Automatically calls when new email message arrives
323 @param self: The object pointer
324 @param cr: the current row, from the database cursor,
325 @param uid: the current user’s ID for security checks
327 mailgate_pool = self.pool.get('email.server.tools')
329 subject = msg.get('subject') or _("No Subject")
330 body = msg.get('body')
331 msg_from = msg.get('from')
332 priority = msg.get('priority')
336 'email_from': msg_from,
337 'email_cc': msg.get('cc'),
341 if msg.get('priority', False):
342 vals['priority'] = priority
344 res = mailgate_pool.get_partner(cr, uid, msg.get('from') or msg.get_unixfrom())
348 res = self.create(cr, uid, vals, context)
349 attachents = msg.get('attachments', [])
350 for attactment in attachents or []:
353 'datas':binascii.b2a_base64(str(attachents.get(attactment))),
354 'datas_fname': attactment,
355 'description': 'Mail attachment',
356 'res_model': self._name,
359 self.pool.get('ir.attachment').create(cr, uid, data_attach)
363 def message_update(self, cr, uid, ids, vals={}, msg="", default_act='pending', context=None):
365 @param self: The object pointer
366 @param cr: the current row, from the database cursor,
367 @param uid: the current user’s ID for security checks,
368 @param ids: List of update mail’s IDs
370 if isinstance(ids, (str, int, long)):
373 if msg.get('priority') in dict(crm.AVAILABLE_PRIORITIES):
374 vals['priority'] = msg.get('priority')
377 'cost':'planned_cost',
378 'revenue': 'planned_revenue',
379 'probability':'probability'
382 for line in msg['body'].split('\n'):
384 res = tools.misc.command_re.match(line)
385 if res and maps.get(res.group(1).lower()):
386 key = maps.get(res.group(1).lower())
387 vls[key] = res.group(2).lower()
390 # Unfortunately the API is based on lists
391 # but we want to update the state based on the
392 # previous state, so we have to loop:
393 for case in self.browse(cr, uid, ids, context=context):
395 if case.state in CRM_LEAD_PENDING_STATES:
396 values.update(state=crm.AVAILABLE_STATES[1][0]) #re-open
397 res = self.write(cr, uid, [case.id], values, context=context)
400 def msg_send(self, cr, uid, id, *args, **argv):
403 @param self: The object pointer
404 @param cr: the current row, from the database cursor,
405 @param uid: the current user’s ID for security checks,
406 @param ids: List of email’s IDs
407 @param *args: Return Tuple Value
408 @param **args: Return Dictionary of Keyword Value
413 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: