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=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),
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)]),
154 def _get_stage_id(self, cr, uid, context=None):
155 """Finds type of stage according to object.
156 @param self: The object pointer
157 @param cr: the current row, from the database cursor,
158 @param uid: the current user’s ID for security checks,
159 @param context: A standard dictionary for contextual values
163 type = context and context.get('stage_type', '')
164 stage_ids = self.pool.get('crm.case.stage').search(cr, uid, [('type','=',type),('sequence','>=',1)])
165 return stage_ids and stage_ids[0] or False
168 'active': lambda *a: 1,
169 'user_id': crm_case._get_default_user,
170 'email_from': crm_case._get_default_email,
171 'state': lambda *a: 'draft',
172 'type': lambda *a: 'lead',
173 'section_id': crm_case._get_section,
174 'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'crm.lead', context=c),
175 'priority': lambda *a: crm.AVAILABLE_PRIORITIES[2][0],
176 'stage_id': _get_stage_id,
179 def onchange_partner_address_id(self, cr, uid, ids, add, email=False):
180 """This function returns value of partner email based on Partner Address
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 IDs
185 @param add: Id of Partner's address
186 @email: Partner's email ID
189 return {'value': {'email_from': False, 'country_id': False}}
190 address = self.pool.get('res.partner.address').browse(cr, uid, add)
191 return {'value': {'email_from': address.email, 'phone': address.phone, 'country_id': address.country_id.id}}
193 def case_open(self, cr, uid, ids, *args):
194 """Overrides cancel for crm_case for setting Open Date
195 @param self: The object pointer
196 @param cr: the current row, from the database cursor,
197 @param uid: the current user’s ID for security checks,
198 @param ids: List of case's Ids
199 @param *args: Give Tuple Value
201 old_state = self.read(cr, uid, ids, ['state'])[0]['state']
202 old_stage_id = self.read(cr, uid, ids, ['stage_id'])[0]['stage_id']
203 res = super(crm_lead, self).case_open(cr, uid, ids, *args)
204 if old_state == 'draft':
207 stage_id = super(crm_lead, self).stage_next(cr, uid, ids, *args)
209 value.update(self.onchange_stage_id(cr, uid, ids, stage_id, context={})['value'])
210 value.update({'date_open': time.strftime('%Y-%m-%d %H:%M:%S')})
211 self.write(cr, uid, ids, value)
213 for case in self.browse(cr, uid, ids):
214 if case.type == 'lead':
215 message = _("The lead '%s' has been opened.") % case.name
216 elif case.type == 'opportunity':
217 message = _("The opportunity '%s' has been opened.") % case.name
219 message = _("The case '%s' has been opened.") % case.name
220 self.log(cr, uid, case.id, message)
223 def case_close(self, cr, uid, ids, *args):
224 """Overrides close for crm_case for setting close date
225 @param self: The object pointer
226 @param cr: the current row, from the database cursor,
227 @param uid: the current user’s ID for security checks,
228 @param ids: List of case Ids
229 @param *args: Tuple Value for additional Params
231 res = super(crm_lead, self).case_close(cr, uid, ids, args)
232 self.write(cr, uid, ids, {'date_closed': time.strftime('%Y-%m-%d %H:%M:%S')})
233 for case in self.browse(cr, uid, ids):
234 if case.type == 'lead':
235 message = _("The lead '%s' has been closed.") % case.name
236 elif case.type == 'opportunity':
237 message = _("The opportunity '%s' has been closed.") % case.name
239 message = _("The case '%s' has been closed.") % case.name
240 self.log(cr, uid, case.id, message)
243 def convert_opportunity(self, cr, uid, ids, context=None):
244 """ Precomputation for converting lead to opportunity
245 @param cr: the current row, from the database cursor,
246 @param uid: the current user’s ID for security checks,
247 @param ids: List of closeday’s IDs
248 @param context: A standard dictionary for contextual values
249 @return: Value of action in dict
253 context.update({'active_ids': ids})
255 data_obj = self.pool.get('ir.model.data')
256 data_id = data_obj._get_id(cr, uid, 'crm', 'view_crm_lead2opportunity_action')
261 view_id = data_obj.browse(cr, uid, data_id, context=context).res_id
263 for case in self.browse(cr, uid, ids):
264 context.update({'active_id': case.id})
265 if not case.partner_id:
266 data_id = data_obj._get_id(cr, uid, 'crm', 'view_crm_lead2opportunity_partner')
269 view_id1 = data_obj.browse(cr, uid, data_id, context=context).res_id
271 'name': _('Create Partner'),
273 'view_mode': 'form,tree',
274 'res_model': 'crm.lead2opportunity.partner',
277 'views': [(view_id1, 'form')],
278 'type': 'ir.actions.act_window',
285 'name': _('Create Opportunity'),
287 'view_mode': 'form,tree',
288 'res_model': 'crm.lead2opportunity.action',
291 'views': [(view_id, 'form')],
292 'type': 'ir.actions.act_window',
298 def write(self, cr, uid, ids, vals, context=None):
299 if 'date_closed' in vals:
300 return super(crm_lead,self).write(cr, uid, ids, vals, context=context)
302 if 'stage_id' in vals and vals['stage_id']:
303 stage_obj = self.pool.get('crm.case.stage').browse(cr, uid, vals['stage_id'], context=context)
304 self.history(cr, uid, ids, _('Stage'), details=stage_obj.name)
305 for case in self.browse(cr, uid, ids, context=context):
306 if case.type == 'lead':
307 message = _("The stage of lead '%s' has been changed to '%s'.") % (case.name, case.stage_id.name)
308 elif case.type == 'opportunity':
309 message = _("The stage of opportunity '%s' has been changed to '%s'.") % (case.name, case.stage_id.name)
310 self.log(cr, uid, case.id, message)
311 return super(crm_lead,self).write(cr, uid, ids, vals, context)
313 def stage_next(self, cr, uid, ids, context=None):
314 stage = super(crm_lead, self).stage_next(cr, uid, ids, context=context)
316 stage_obj = self.pool.get('crm.case.stage').browse(cr, uid, stage, context=context)
317 if stage_obj.on_change:
318 data = {'probability': stage_obj.probability}
319 self.write(cr, uid, ids, data)
322 def message_new(self, cr, uid, msg, context=None):
324 Automatically calls when new email message arrives
326 @param self: The object pointer
327 @param cr: the current row, from the database cursor,
328 @param uid: the current user’s ID for security checks
330 mailgate_pool = self.pool.get('email.server.tools')
332 subject = msg.get('subject')
333 body = msg.get('body')
334 msg_from = msg.get('from')
335 priority = msg.get('priority')
339 'email_from': msg_from,
340 'email_cc': msg.get('cc'),
344 if msg.get('priority', False):
345 vals['priority'] = priority
347 res = mailgate_pool.get_partner(cr, uid, msg.get('from') or msg.get_unixfrom())
351 res = self.create(cr, uid, vals, context)
352 attachents = msg.get('attachments', [])
353 for attactment in attachents or []:
356 'datas':binascii.b2a_base64(str(attachents.get(attactment))),
357 'datas_fname': attactment,
358 'description': 'Mail attachment',
359 'res_model': self._name,
362 self.pool.get('ir.attachment').create(cr, uid, data_attach)
366 def message_update(self, cr, uid, ids, vals={}, msg="", default_act='pending', context=None):
368 @param self: The object pointer
369 @param cr: the current row, from the database cursor,
370 @param uid: the current user’s ID for security checks,
371 @param ids: List of update mail’s IDs
373 if isinstance(ids, (str, int, long)):
376 if msg.get('priority') in dict(crm.AVAILABLE_PRIORITIES):
377 vals['priority'] = msg.get('priority')
380 'cost':'planned_cost',
381 'revenue': 'planned_revenue',
382 'probability':'probability'
385 for line in msg['body'].split('\n'):
387 res = tools.misc.command_re.match(line)
388 if res and maps.get(res.group(1).lower()):
389 key = maps.get(res.group(1).lower())
390 vls[key] = res.group(2).lower()
393 # Unfortunately the API is based on lists
394 # but we want to update the state based on the
395 # previous state, so we have to loop:
396 for case in self.browse(cr, uid, ids, context=context):
398 if case.state in CRM_LEAD_PENDING_STATES:
399 values.update(state=crm.AVAILABLE_STATES[1][0]) #re-open
400 res = self.write(cr, uid, [case.id], values, context=context)
404 def msg_send(self, cr, uid, id, *args, **argv):
407 @param self: The object pointer
408 @param cr: the current row, from the database cursor,
409 @param uid: the current user’s ID for security checks,
410 @param ids: List of email’s IDs
411 @param *args: Return Tuple Value
412 @param **args: Return Dictionary of Keyword Value
417 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: